Files
deploy-felhom-compose/TASK.md
T

12 KiB

TASK.md — v0.5.4: Monitoring Page Frontend Fixes

Version bump: v0.5.4 Scope: Frontend-only — all changes in monitoring.html and style.css No Go code changes needed.


IMPORTANT: Build & Validation

Build must happen in ~/build/felhom-controller/, NOT in the git repo:

cd ~/build/felhom-controller
git -C ~/git/deploy-felhom-compose pull
./build.sh 0.5.2 --push

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:

ssh kisfenyo@192.168.0.162 "docker exec felhom-controller cat /app/templates/monitoring.html" | head -50

Bug 1: Tooltip shows "Invalid Date"

Root cause

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".

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():

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);
}

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

Fix

Replace the tooltip callback with a more robust approach that accesses the raw data point directly:

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:

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:

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:

.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:

<!-- 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:

.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:

.chart-box-half {
    flex: 1;
    min-width: 0;         /* Same fix for flex containers */
}

Key additions:

  • min-width: 0 on .chart-boxthe 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

  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/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

  • 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