v0.5.4: Monitoring Page Frontend Fixes
This commit is contained in:
@@ -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
|
||||
<button class="filter-btn active" data-range="1h">1 óra</button>
|
||||
<button class="filter-btn" data-range="6h">6 óra</button>
|
||||
<button class="filter-btn" data-range="24h">24 óra</button>
|
||||
```
|
||||
|
||||
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 `<style>` block in `monitoring.html` sets `text-align: right` on `.sysinfo-value`, but `text-align` has no effect on flex items — flex alignment controls their position.
|
||||
|
||||
### Fix
|
||||
|
||||
**In `style.css`**, replace the sysinfo rules:
|
||||
|
||||
```css
|
||||
.sysinfo-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0;
|
||||
}
|
||||
.sysinfo-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
padding: .5rem .75rem;
|
||||
border-bottom: 1px solid rgba(48, 54, 61, 0.3);
|
||||
font-size: .9rem;
|
||||
gap: 1rem;
|
||||
}
|
||||
.sysinfo-row:last-child { border-bottom: none; }
|
||||
.sysinfo-label {
|
||||
color: var(--text-secondary);
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sysinfo-value {
|
||||
color: var(--text-primary);
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: .85rem;
|
||||
font-weight: 600;
|
||||
text-align: right;
|
||||
word-break: break-word;
|
||||
}
|
||||
```
|
||||
|
||||
Key changes from original:
|
||||
- `.sysinfo-grid`: `grid-template-columns: 1fr 1fr` (fixed 2-column instead of `auto-fill`)
|
||||
- `.sysinfo-grid`: `gap: 0` (no gap between cells — border-bottom handles separation)
|
||||
- `.sysinfo-row`: `align-items: baseline` (text baseline alignment)
|
||||
- `.sysinfo-row`: `gap: 1rem` (minimum space between label and value)
|
||||
- `.sysinfo-label`: `white-space: nowrap; flex-shrink: 0` (prevent label wrapping/shrinking)
|
||||
- `.sysinfo-value`: `font-weight: 600; word-break: break-word` (bold values, allow long values to wrap)
|
||||
|
||||
**In `monitoring.html`**, remove the `<style>` block at the top:
|
||||
```html
|
||||
<!-- REMOVE THIS -->
|
||||
<style>
|
||||
/* Monitoring page specific overrides */
|
||||
.sysinfo-value {
|
||||
text-align: right;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
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 `<style>` block, fix tooltip callback, add initial min/max to chartOpts
|
||||
3. Commit
|
||||
4. Build (`~/build/felhom-controller/build.sh 0.5.2 --push`)
|
||||
5. Deploy to demo node
|
||||
6. Verify all 4 fixes in browser
|
||||
|
||||
---
|
||||
|
||||
## Files to modify
|
||||
|
||||
```
|
||||
controller/docker-compose.yml — add /etc/hostname mount
|
||||
internal/metrics/sysinfo.go — read hostname from /host/etc/hostname
|
||||
internal/web/templates/monitoring.html — fix tooltip callback + default range
|
||||
controller/internal/web/templates/monitoring.html — tooltip fix, chartOpts min/max, remove <style> block
|
||||
controller/internal/web/templates/style.css — sysinfo alignment, chart overflow
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verification checklist
|
||||
## Verification Checklist
|
||||
|
||||
- [ ] Hostname shows "demo-felhom" (not container ID)
|
||||
- [ ] Tooltip shows correct timestamp (e.g., "2026. 02. 16. 10:21")
|
||||
- [ ] Charts show data on initial page load (1h default)
|
||||
- [ ] "1 óra" button is active/highlighted by default
|
||||
- [ ] Clicking each range button updates charts
|
||||
- [ ] "24 óra" shows data if there are 1+ hours of collected metrics
|
||||
- [ ] Container bar charts still render correctly
|
||||
- [ ] Container detail panel still works
|
||||
- [ ] No console errors in browser devtools
|
||||
- [ ] Tooltip shows correct date (e.g., "2026. 02. 16. 11:30") — no "Invalid Date"
|
||||
- [ ] "7 nap" range shows full 7-day x-axis, data clustered on the right
|
||||
- [ ] "1 óra" shows data filling most of the chart
|
||||
- [ ] "30 nap" shows full 30-day x-axis with data on far right
|
||||
- [ ] X-axis labels: HH:MM for 1h/6h/24h, MM-DD for 7d/30d
|
||||
- [ ] System overview values right-aligned, consistent across both columns
|
||||
- [ ] No `<style>` block at top of monitoring.html (moved to style.css)
|
||||
- [ ] On mobile (375px): charts fit, no horizontal scroll
|
||||
- [ ] Container detail panel works (click bar → expand)
|
||||
- [ ] Auto-refresh works (wait 60s, charts update)
|
||||
- [ ] No console.log debug lines in final deployed version
|
||||
- [ ] Build happened in `~/build/felhom-controller/`, no binary in git repo
|
||||
Reference in New Issue
Block a user