diff --git a/TASK.md b/TASK.md index 92fc76b..d82d17c 100644 --- a/TASK.md +++ b/TASK.md @@ -1,234 +1,351 @@ -# TASK.md — v0.5.1: Monitoring Page Bugfixes +# TASK.md — v0.5.4: Monitoring Page Frontend Fixes -> Version bump: **v0.5.1** -> Scope: 4 bugs in the monitoring page +> Version bump: **v0.5.4** +> Scope: Frontend-only — all changes in `monitoring.html` and `style.css` +> No Go code changes needed. --- -## Bug 1: Hostname shows container ID instead of host hostname - -### Problem - -"Gépnév" displays `75f2f2a113f3` — the Docker container ID. `os.Hostname()` inside a container returns the container's hostname, not the host's. - -### Root cause - -`sysinfo.go` line: `info.Hostname, _ = os.Hostname()` - -Inside a Docker container, `os.Hostname()` returns the container ID unless `hostname:` is set in docker-compose.yml. - -### Fix — Two options (use both for robustness) - -**Option A: Mount host's /etc/hostname** (preferred — works for all cases): - -In `controller/docker-compose.yml`, add: -```yaml -volumes: - - /etc/hostname:/host/etc/hostname:ro -``` - -In `sysinfo.go`, read host hostname first: -```go -// Hostname — try host mount first, fall back to os.Hostname() -if data, err := os.ReadFile("/host/etc/hostname"); err == nil { - info.Hostname = strings.TrimSpace(string(data)) -} else { - info.Hostname, _ = os.Hostname() -} -``` - -**Option B: Set hostname in docker-compose.yml** (simpler but requires per-customer config): - -```yaml -hostname: ${HOSTNAME:-felhom} -``` - -But this requires the env var to be set. Option A is better — it reads the actual host hostname dynamically. - -**Use Option A.** It's consistent with the `/etc/os-release` mount pattern already in place. - ---- - -## Bug 2: Tooltip timestamps show "1970. 01. 01. 01:00" - -### Problem - -Hovering over chart data points shows `1970. 01. 01. 01:00` instead of the actual timestamp. - -### Root cause - -In the tooltip callback: -```javascript -callbacks: { - title: function(items) { - if (!items.length) return ''; - return formatTimestamp(items[0].parsed.x || items[0].label); - } -} -``` - -The chart uses a **category** x-axis (default), not a time axis. `items[0].parsed.x` returns the **category index** (0, 1, 2, 3...), not the timestamp. When the index is > 0, `parsed.x || label` evaluates to the index (truthy). Then `formatTimestamp(5)` does `new Date(5 * 1000)` → `1970-01-01 01:00:00.005`. - -When the index is 0, `0 || label` falls through to `label`, which works correctly. That's why the first data point shows the right time. - -### Fix - -Always use `items[0].label` instead of `parsed.x`: - -```javascript -callbacks: { - title: function(items) { - if (!items.length) return ''; - return formatTimestamp(items[0].label); - } -} -``` - -`items[0].label` is the raw label value from the labels array, which IS the timestamp in milliseconds. - -### Files - -`internal/web/templates/monitoring.html` — tooltip callback in `chartOpts` function. - ---- - -## Bug 3: Range selector appears non-functional / 24h shows empty - -### Problem - -Default range is `24h` but the system has only ~20 minutes of data. On page load, charts appear empty (Y-axis 0-1.0, no visible lines). Clicking "1 óra" shows data. User perceives buttons as "not doing anything" because the initial state is already broken. - -### Root cause (likely) - -Two contributing factors: - -1. **Default range too wide**: `systemRange = '24h'` — for a newly deployed system with minutes of data, this either shows nothing or shows a barely visible sliver at the right edge. - -2. **Downsampling compression**: 24h range with resolution=200 → `bucketSeconds = 432`. Twenty data points spanning 20 minutes (~1200s) get grouped into ~3 buckets. Three data points CAN render as a line chart, but if Chart.js's auto-scaling or the bucket timestamps are at the very edge, the chart might not render visibly. - -### Fix - -**A. Change default range to `1h`:** - -```javascript -let systemRange = '1h'; -``` - -And move the `active` class to the `1h` button: -```html - - - -``` - -Same for container detail range: -```javascript -let detailRange = '1h'; -``` - -**B. Smart default**: After the system has been running for 24+ hours, `24h` makes more sense as a default. But for v0.5.1, just use `1h` — it's always reasonable. - -**C. Add diagnostic logging**: To understand if 24h truly returns empty, add a temporary console.log in the JS: - -```javascript -async function loadSystemMetrics() { - try { - const resp = await fetch('/api/metrics/system?range=' + systemRange + '&resolution=200'); - const json = await resp.json(); - console.log('[metrics] system range=' + systemRange + ', data points=' + (json.data?.labels?.length || 0)); - // ... rest of handler -``` - -This helps debug if the issue is no data returned vs. data not rendering. - -### Troubleshooting commands (run on demo node) - -Before implementing the fix, verify the data is in SQLite: +## IMPORTANT: Build & Validation +Build must happen in `~/build/felhom-controller/`, NOT in the git repo: ```bash -# Check how many system metric rows exist -docker exec -it felhom-controller sh -c "cat /app/data/metrics.db" | strings | head -5 -# Or directly via the API from the browser: -# https://felhom.demo-felhom.eu/api/metrics/system?range=1h&resolution=200 -# https://felhom.demo-felhom.eu/api/metrics/system?range=24h&resolution=200 +cd ~/build/felhom-controller +git -C ~/git/deploy-felhom-compose pull +./build.sh 0.5.2 --push ``` -Compare the JSON responses. If 24h returns labels but cpu/memory arrays are zeros, it's a rendering issue. If labels are empty, it's a query issue. +**Never run `go build` inside `~/git/deploy-felhom-compose/controller/`.** + +After deployment, validate all 4 fixes by: +1. Opening https://felhom.demo-felhom.eu/monitoring in browser +2. Opening the browser Developer Tools (F12) → Console tab +3. Checking each item below + +If you cannot access the browser, validate by reading the deployed HTML source: +```bash +ssh kisfenyo@192.168.0.162 "docker exec felhom-controller cat /app/templates/monitoring.html" | head -50 +``` --- -## Bug 4: Charts empty on initial page load - -### Problem - -When navigating to the monitoring page, all four system charts show empty (no data) until the user clicks a range button. +## Bug 1: Tooltip shows "Invalid Date" ### Root cause -Same as Bug 3 — the initial `loadSystemMetrics()` call uses the `24h` default range, which returns no visible data for a new system. Fixing Bug 3 (changing default to `1h`) should also fix this. +The tooltip callback uses `items[0].parsed.x` which should return a numeric timestamp on a Chart.js linear axis. However, depending on the Chart.js version/build, `parsed.x` may return something unexpected (undefined, wrong type) causing `new Date()` to produce "Invalid Date". -### Additional fix — race condition protection +### Diagnosis step + +Before fixing, add a temporary console.log to confirm what `parsed.x` actually returns. In `monitoring.html`, in the tooltip callback inside `chartOpts()`: -Ensure the init sequence is robust. Currently: ```javascript -initSystemCharts(); -initContainerCharts(); -initDetailCharts(); -loadSysInfo(); -loadSystemMetrics(); -loadContainerSummary(); +title: function(items) { + if (!items.length) return ''; + console.log('[tooltip debug]', 'parsed.x:', items[0].parsed.x, typeof items[0].parsed.x, 'raw:', items[0].raw); + return formatTimestamp(items[0].parsed.x); +} ``` -This looks correct — charts are initialized before data is loaded. No race condition here. +Deploy, hover over a data point, check browser console. Possible findings: +- `parsed.x` is `undefined` → Chart.js isn't finding the x value from `{x,y}` data +- `parsed.x` is a very small number (like an index) → linear scale isn't applied +- `parsed.x` is correct ms timestamp → bug is in `formatTimestamp` -### Edge case: very first load (0 data points) +### Fix -If the monitoring page is loaded before the collector has stored even 1 sample (within the first 60 seconds of controller start), the "Még nincsenek adatok" message should appear. Verify this works correctly. +Replace the tooltip callback with a more robust approach that accesses the raw data point directly: + +```javascript +callbacks: { + title: function(items) { + if (!items.length) return ''; + // Access raw {x, y} data point directly — most reliable across Chart.js versions + var raw = items[0].raw; + if (raw && typeof raw === 'object' && raw.x) { + return formatTimestamp(raw.x); + } + // Fallback: try parsed.x + if (items[0].parsed && items[0].parsed.x) { + return formatTimestamp(items[0].parsed.x); + } + return ''; + } +} +``` + +After deploying and verifying, remove the console.log line. + +### Verification +- Hover over any data point on any chart → tooltip title shows formatted date like "2026. 02. 16. 11:30" +- Verify on CPU, Memory, Temperature, Load charts +- Verify on container detail charts too (same `chartOpts` function is shared) + +--- + +## Bug 2: Charts fill full width regardless of data density + +### Root cause + +`setChartXBounds()` sets `chart.options.scales.x.min/max` after chart initialization. Chart.js may not pick up dynamically added `min`/`max` properties if they weren't present in the options during initialization. The scale was created without `min`/`max`, and adding them at runtime may be ignored. + +### Diagnosis step + +Add console.log in `loadSystemMetrics()` after setting bounds and updating: + +```javascript +allCharts.forEach(function(c) { setChartXBounds(c, systemRange); }); +updateLineChart(chartCPU, timestamps, d.cpu); +console.log('[bounds debug] range:', systemRange, + 'options.min:', chartCPU.options.scales.x.min, + 'options.max:', chartCPU.options.scales.x.max, + 'scale.min:', chartCPU.scales.x.min, + 'scale.max:', chartCPU.scales.x.max); +``` + +Select "7 nap", check console. If `options.min/max` are set correctly but `scales.x.min/max` show the data extent, then Chart.js is ignoring the runtime-added properties. + +### Fix + +Include `min` and `max` in the initial chart options so Chart.js registers them from creation. Then dynamic updates work. + +**Step 1**: Modify `chartOpts()` to include initial min/max: + +```javascript +function chartOpts(yLabel, beginAtZero) { + var now = Date.now(); + var defaultRangeMs = parseRangeMs('1h'); // match default systemRange + return { + responsive: true, + maintainAspectRatio: false, + animation: {duration: 300}, + plugins: { + legend: {display: false}, + tooltip: { + backgroundColor: '#1c2128', + titleColor: '#e6edf3', + bodyColor: '#8b949e', + borderColor: '#30363d', + borderWidth: 1, + callbacks: { + title: function(items) { + if (!items.length) return ''; + var raw = items[0].raw; + if (raw && typeof raw === 'object' && raw.x) { + return formatTimestamp(raw.x); + } + if (items[0].parsed && items[0].parsed.x) { + return formatTimestamp(items[0].parsed.x); + } + return ''; + } + } + } + }, + scales: { + x: { + type: 'linear', + min: now - defaultRangeMs, + max: now, + grid: {color: 'rgba(48,54,61,0.5)'}, + ticks: { + color: '#8b949e', + maxTicksLimit: 8, + callback: function(v) { + return formatTimeLabel(v); + } + } + }, + y: { + grid: {color: 'rgba(48,54,61,0.5)'}, + ticks: {color: '#8b949e'}, + beginAtZero: beginAtZero !== false, + title: {display: !!yLabel, text: yLabel || '', color: '#6e7681', font: {size: 11}} + } + } + }; +} +``` + +Key change: `min: now - defaultRangeMs, max: now` are present from creation. + +**Step 2**: `setChartXBounds()` stays the same — it updates existing properties. + +**Step 3**: Same fix for container detail charts — `initDetailCharts()` uses the same `chartOpts()` so it gets min/max automatically. + +### Verification +- Select "7 nap" → x-axis spans 7 full days (Feb 9 to Feb 16), data appears as a small cluster on the far right +- Select "1 óra" → data fills most of the chart width +- Select "24 óra" → data fills proportional to collection time +- X-axis labels for 7d show dates (02.09 .. 02.16), not times +- X-axis labels for 1h/6h/24h show times (10:00, 11:00, etc.) + +--- + +## Bug 3: System overview values not consistently right-aligned + +### Root cause + +`.sysinfo-row` uses `display: flex; justify-content: space-between` which does push values to the right of each cell. But `.sysinfo-grid` uses `repeat(auto-fill, minmax(280px, 1fr))` which creates varying cell widths — values don't align to a consistent edge across columns. + +The ` +``` + +The mobile rule `@media(max-width: 768px) { .sysinfo-grid { grid-template-columns: 1fr; } }` already exists and stays — collapses to single column on mobile. + +### Verification +- Values are consistently right-aligned within each cell +- "Debian GNU/Linux 13 (trixie)" and "6.12.69+deb13-amd64" align to the right edge +- Both grid columns have equal width +- Long values wrap without breaking layout + +--- + +## Bug 4: Charts overflow their container on mobile + +### Root cause + +`.chart-wrap` has `position: relative; height: 180px` but no overflow or width constraint. CSS grid children default to `min-width: auto`, preventing them from shrinking below their content width. Chart.js canvas may render wider than the parent on narrow screens. + +### Fix + +**In `style.css`**, update these rules: + +```css +.chart-box { + background: var(--bg-secondary); + border-radius: 8px; + padding: .75rem; + border: 1px solid rgba(48, 54, 61, 0.5); + min-width: 0; /* Allow grid children to shrink — critical fix */ + overflow: hidden; +} +.chart-wrap { + position: relative; + height: 180px; + overflow: hidden; + max-width: 100%; +} +.chart-wrap canvas { + max-width: 100%; +} +.chart-wrap-bar { + position: relative; + height: 250px; + overflow: hidden; + max-width: 100%; +} +``` + +Also add `.chart-box-half` update: +```css +.chart-box-half { + flex: 1; + min-width: 0; /* Same fix for flex containers */ +} +``` + +Key additions: +- `min-width: 0` on `.chart-box` — **the critical CSS grid fix**: prevents grid children from forcing the grid wider than the viewport +- `overflow: hidden` on `.chart-wrap` and `.chart-wrap-bar` — clips any canvas overflow +- `max-width: 100%` on `.chart-wrap` and canvas +- `min-width: 0` on `.chart-box-half` — same fix for the flex-based container charts + +### Verification +- Open monitoring page at 375px width (browser devtools responsive mode) +- All four system metric charts fit within the screen +- Container bar charts fit within the screen +- No horizontal scrollbar appears +- Charts remain interactive (hover/click works) --- ## Implementation order -### Step 1: Fix hostname -1. Add `/etc/hostname:/host/etc/hostname:ro` to `controller/docker-compose.yml` -2. Update `sysinfo.go` — read from `/host/etc/hostname` first - -### Step 2: Fix tooltip timestamps -1. Change `items[0].parsed.x || items[0].label` to `items[0].label` in `monitoring.html` - -### Step 3: Fix default range + empty charts -1. Change `systemRange = '1h'` and `detailRange = '1h'` -2. Move `active` class to "1 óra" button in both range bars -3. Add console.log diagnostic for data loading - -### Step 4: Build, deploy, verify -1. Build v0.5.1 -2. Deploy to demo node (sync docker-compose.yml for new volume mount) -3. Verify hostname shows "demo-felhom" -4. Verify tooltip shows correct timestamp -5. Verify charts show data on page load -6. Test all range buttons (1h → 6h → 24h → 7d → 30d) +1. Edit `style.css` — sysinfo alignment + chart overflow fixes +2. Edit `monitoring.html` — remove `