diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..c4179a4 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +# Keep the repo normalized to LF, but check Windows launchers out with CRLF: +# cmd.exe can misparse LF-only batch files (labels and parenthesized blocks). +* text=auto +*.cmd text eol=crlf +*.ps1 text eol=crlf +*.png binary diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..b034b5e --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,32 @@ +# Runs the test suite on Windows (HogWatch uses Windows-only APIs: ICMP via +# iphlpapi and ETW) for every pull request and every push to main. +name: tests + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +jobs: + test: + runs-on: windows-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.13" + cache: pip + + - name: Install dependencies + run: python -m pip install -r requirements.txt + + - name: Unit and integration tests + run: python -m unittest discover -s tests -v + + - name: Dashboard script syntax + run: node --check hogwatch/static/app.js diff --git a/README.md b/README.md index 658a19c..f8b51dd 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,17 @@ # HogWatch +[![tests](https://github.com/MSabacreationslab/hogwatch/actions/workflows/tests.yml/badge.svg)](https://github.com/MSabacreationslab/hogwatch/actions/workflows/tests.yml) + Finds out **who** is slowing the home internet to a crawl, **when**, and **what they're probably doing**, and shows it on a dashboard at . + + + HogWatch dashboard during a slowdown: a roommate's upload is named as the cause, and short lag spikes are traced to the wireless link between two eeros + + +Screenshot uses demo data; device names and rooms are made up. + It watches three things at once: | What | How | Needs | diff --git a/docs/dashboard-dark.png b/docs/dashboard-dark.png new file mode 100644 index 0000000..8c739eb Binary files /dev/null and b/docs/dashboard-dark.png differ diff --git a/docs/dashboard-light.png b/docs/dashboard-light.png new file mode 100644 index 0000000..68696ae Binary files /dev/null and b/docs/dashboard-light.png differ diff --git a/hogwatch/static/app.js b/hogwatch/static/app.js index 38be047..79eaf5a 100644 --- a/hogwatch/static/app.js +++ b/hogwatch/static/app.js @@ -26,7 +26,7 @@ const s = (tag, attrs, ...kids) => build(document.createElementNS(SVGNS, tag), a /* ------------------------------------------------------------------ formatting */ const fmtRate = (x) => (x == null ? "–" : x < 0.05 ? "0" : x < 10 ? x.toFixed(1) : Math.round(x).toLocaleString()); -const fmtMs = (x) => (x == null ? "–" : Math.round(x).toLocaleString()); +const fmtMs = (x) => (x == null ? "–" : x < 1 ? "<1" : Math.round(x).toLocaleString()); function fmtVolume(mb) { if (mb == null) return "–"; if (mb >= 1000) return (mb / 1000).toFixed(mb >= 10000 ? 0 : 1) + " GB"; @@ -56,6 +56,9 @@ const KIND_LABEL = { const state = { hours: 1, now: null, timeline: null, usage: null, incidents: null, incidentLimit: 15, offline: false }; try { const saved = +localStorage.getItem("hw-hours"); if (saved) state.hours = saved; } catch (e) { /* storage blocked */ } +// A link like /?hours=24 opens that range directly (handy for bookmarks). +const linkedHours = +new URLSearchParams(location.search).get("hours"); +if ([1, 6, 24, 168].includes(linkedHours)) state.hours = linkedHours; async function getJSON(path) { const r = await fetch(path, { cache: "no-store" });