13 KiB
name, description, version, category, tags, platforms
| name | description | version | category | tags | platforms | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| cockpit-custom-packages | Build and debug custom Cockpit web console packages (dashboards, monitoring pages). Covers manifest CSP, JavaScript API quirks in Cockpit 360, and the data-flow pattern for live dashboards. | 1.1.0 | devops |
|
|
Cockpit Custom Packages
Build and debug custom package pages for Cockpit (the Linux web console). Covers manifest setup, JavaScript API quirks in Cockpit 360, and the data-flow pattern for live dashboards.
Architecture Pattern
Cockpit packages live under /usr/share/cockpit/<name>/ with:
manifest.json— registers the package in Cockpit's sidebarindex.html— the dashboard page
Data flow for live monitoring: a systemd service writes data to files every N seconds; the page reads those files to display. The page cannot run nvidia-smi or other commands directly — Cockpit 360's bridge sandboxes execution.
Manifest (manifest.json)
{
"version": 1,
"requires": {
"cockpit": "260"
},
"menu": {
"index": {
"label": "My Dashboard",
"order": 90
}
},
"content-security-policy": "default-src 'self'; script-src 'self' 'unsafe-inline'; connect-src 'self' https://localhost:9090 wss://localhost:9090"
}
Critical CSP Rule
The CSP MUST include 'self' in script-src. If you write only script-src 'unsafe-inline' (a common mistake), it OVERRIDES the 'self' inherited from default-src, and Cockpit blocks ALL external script files including cockpit.js. Symptom: page renders HTML but NO JavaScript executes — cockpit is undefined, no API calls work, page appears frozen.
CRITICAL: cockpit.js Path in Cockpit 360+
Cockpit ≥360 stores cockpit.js as cockpit.js.gz. The relative path ../base1/cockpit.js does NOT resolve through Cockpit's transparent gzip decompression from inside a package directory. The script silently fails to load, and NONE of the Cockpit JavaScript API works.
Always use the absolute path:
<!-- WRONG — fails in Cockpit 360+ -->
<script src="../base1/cockpit.js"></script>
<!-- RIGHT — always works -->
<script src="/cockpit/base1/cockpit.js"></script>
Symptom when broken: Page title/heading renders but no JavaScript executes. No console errors may be visible. All API calls silently do nothing.
Note: The umbrella
cockpit-custom-packagesabsorbedcockpit-monitoringandcockpit-plugin-dev. The cockpit.js.gz insight came fromcockpit-monitoring; detailed API sandboxing notes are inreferences/cockpit-360-quirks.md(fromcockpit-plugin-dev).
Cockpit 360 JavaScript API
What works
| API | Works? | Notes |
|---|---|---|
cockpit.spawn(["cat", path], {err:"message"}) |
YES | Use to read pre-written data files |
cockpit.script("cmd") |
NO — hangs silently | Command never resolves or rejects |
cockpit.file(path).read() |
NO — hangs silently | Both relative and absolute paths fail |
cockpit.info |
YES | Version, user info, etc. |
Reading data files
var PKG = "/usr/share/cockpit/my-package";
cockpit.spawn(["cat", PKG + "/data.txt"], {err: "message"})
.done(function(data) {
// data contains file contents
var fields = data.trim().split(', ');
})
.fail(function(exitCode, data) {
// exitCode is the problem description string when err:"message"
});
Deployment
- Always restart cockpit after deploy:
sudo systemctl restart cockpitthen verify in an Incognito/Private window. Cockpit aggressively caches HTML — hard refresh (Ctrl+Shift+R) often isn't enough. The correct verification sequence:sudo systemctl restart cockpit- Open a private/incognito browser window → login to Cockpit → check the page
- If it works there, the old tab had stale cache. Force-reload the old tab via DevTools → right-click refresh → "Empty Cache and Hard Reload"
- For stubborn cases, add cache-busting meta tags to
<head>:<meta http-equiv="cache-control" content="no-cache, no-store, must-revalidate">— these help but Cockpit's internal caching may still serve stale content, so the private window test is the gold standard.
- Manifest changes:
sudo cpthensudo systemctl restart cockpit— same caching rules apply.
Do NOT assume a hard refresh (Ctrl+Shift+R) is enough — it often isn't for Cockpit packages. Always restart cockpit AND verify in a private window if the user reports no visual change after a deploy.
Editing files in /usr/share/cockpit/
The patch and write_file tools cannot write to /usr/share/cockpit/ because they lack sudo. Use one of these patterns instead:
# Write to a temp file first, then sudo cp
cat > /tmp/new-page.html << 'EOF'
...content...
EOF
sudo cp /tmp/new-page.html /usr/share/cockpit/<name>/index.html
# For a full rewrite, pipe through sudo tee
sudo tee /usr/share/cockpit/<name>/index.html > /dev/null << 'EOF'
...content...
EOF
For complex multi-card HTML, prefer rebuilding with a Python execute_code script (safer than multi-line sed which can miss tag boundaries and break nesting).
Dashboard Layout
Card grid layout pitfall (most common bug)
When cards appear outside their intended grid column (stacked full-width down the page), the cause is almost always a misplaced </div> closing the grid container early.
Example of the bug:
<div class="grid">
<div class="card">...</div>
<div class="card">...</div>
</div> <!-- ⚠ grid closes HERE — WRONG! -->
<div class="card">...</div> <!-- outside grid — renders full-width stacked -->
To diagnose: Run grep -c '<div class="card"' inside the grid section and compare to the expected count. Every card must be a direct child of <div class="grid">. An extra </div> between cards closes the grid and dumps subsequent cards outside it.
When removing elements like ring gauges, remove the entire container <div> — don't just delete inner content, as leftover closing tags break the nest hierarchy. Use Python string replacement on the full HTML rather than multi-line sed.
Clean multi-card grid template
<div class="grid">
<div class="card accent-blue"><h2>🌡 Thermal</h2>...</div>
<div class="card accent-cyan"><h2>🔥 CPU</h2>...</div>
<div class="card accent-orange"><h2>💾 Memory</h2>...</div>
<div class="card accent-purple"><h2>🔧 Details</h2>...</div>
<div class="card accent-red"><h2>⚡ Power</h2>...</div>
<div class="card accent-green"><h2>📊 GPU Load</h2>...</div>
<div class="card model-card">...</div>
<div class="card full"><h2>🔄 Processes</h2>...</div>
<div class="full footer-row">...</div>
</div>
Each card is one self-contained div with no nested block elements crossing card boundaries. No text nodes or wrapping divs between cards inside the grid.
Dashboard Design Preferences
For GPU/system monitoring dashboards in Cockpit, prefer:
- ✅ Stat rows: left-label/right-value flex rows with
justify-content: space-between. Compact, clean, updates live. - ✅ Temperature cards: big number (2.5rem) with mini side-stats (fan, util, PCH, ACPI) in a
temp-groupflex layout. - ❌ Ring gauges / SVG donuts: avoid unless the user explicitly requests one. They take up card space, look busy, and the user will ask you to shrink or remove them.
- Each card gets a colored
3px solidaccent top border to distinguish categories (blue=thermal, cyan=CPU, orange=memory, purple=details, red=power, green=GPU load). - All numeric values use
font-family: "SF Mono", "JetBrains Mono", "Fira Code", monospacewithfont-weight: 600. - If a ring gauge is requested per user request: default to 16-30px SVG with thin 4px stroke, and be ready to remove it on feedback.
CPU Temperature Monitoring
Add CPU temp alongside GPU stats by extending the same monitor script. Full details in references/cpu-temp-data-format.md.
Monitor script addition — insert before sleep 5:
pkg=$(cat /sys/class/thermal/thermal_zone3/temp 2>/dev/null || echo 0)
pch=$(cat /sys/class/thermal/thermal_zone2/temp 2>/dev/null || echo 0)
acpi=$(cat /sys/class/thermal/thermal_zone0/temp 2>/dev/null || echo 0)
echo "$((pkg/1000)),$((pch/1000)),$((acpi/1000))" > /usr/share/cockpit/nvidia-gpu/cpu.txt
Dashboard card — use accent-cyan for CPU:
.card.accent-cyan{border-top:3px solid #06b6d4}
NVIDIA GPU Data Format
The nvidia-gpu-monitor systemd service writes three text files every 5 seconds.
gpu.txt (main stats)
NVIDIA GeForce GTX 1050 Ti, 580.159.03, 44, 0, 445, 4096, [N/A], P0, 31
Fields (comma+space separated):
| Index | Field | Example | Notes |
|---|---|---|---|
| 0 | Model | NVIDIA GeForce GTX 1050 Ti |
Full GPU name |
| 1 | Driver | 580.159.03 |
Driver version string |
| 2 | Temp | 44 |
Integer °C |
| 3 | Util % | 0 |
Integer percentage |
| 4 | Mem Used | 445 |
MiB (not GiB) |
| 5 | Mem Total | 4096 |
MiB |
| 6 | Power | [N/A] |
Watts or [N/A] if no sensor |
| 7 | Perf State | P0 |
P0=active, P8/P12=idle |
| 8 | Fan % | 31 |
Integer or [N/A] |
Parsing: data.trim().split(', ') → 9-element array. Always check p.length >= 9.
Memory display helper:
function fmt(m){
var v=parseInt(m);
if(isNaN(v)) return m+' MiB';
return v>=1024 ? (v/1024).toFixed(1)+' GiB' : v+' MiB';
}
Fan display: if value is [N/A] or "0", show — (em dash) instead of 0% — many Pascal cards have zero-RPM mode below 50°C.
cuda.txt / processes.txt
cuda.txt: 13.0
processes.txt: 1234, python3, 256 \n 5678, ffmpeg, 1024
cpu.txt
48,43,46
Comma-separated: CPU package, PCH, ACPI temperatures in °C. See references/cpu-temp-data-format.md for full details.
SVG Ring Gauge Technique
Use only when the user requests it. Default to small (30px) with thin stroke (4):
<div class="ring-container">
<svg class="ring-svg" viewBox="0 0 36 36">
<circle class="ring-bg" cx="18" cy="18" r="15.5"/>
<circle class="ring-fg" id="uring" cx="18" cy="18" r="15.5"
stroke-dasharray="97.4" stroke-dashoffset="97.4"/>
</svg>
<div class="ring-text" id="uptxt">0%</div>
</div>
Circumference C = 2*pi*15.5 ≈ 97.4. Set stroke-dashoffset = C - (pct/100 * C) in JS. SVG container sized via .ring-svg{width:Npx;height:Npx}.
Dark mode gotcha: The ring background uses stroke: var(--pf-t--global--background--color--secondary--default,#eee) — on Cockpit's dark theme this resolves to dark gray/black, making the ring look like a "black circle." This is the background track, not the usage arc.
Troubleshooting: "NO GPU Detected" When Data Files Are Valid
If the Cockpit GPU page shows "⚠ No NVIDIA GPU detected" but cat /usr/share/cockpit/nvidia-gpu/gpu.txt has real data:
| Check | What to do |
|---|---|
| 1️⃣ Verify JS loaded | Open browser DevTools → Console. If cockpit is undefined, the <script src> path is wrong (Cockpit 360 needs absolute path /cockpit/base1/cockpit.js) |
| 2️⃣ Check package registration | cockpit-bridge --packages | grep nvidia — should show the package path |
| 3️⃣ Verify monitor service | systemctl status nvidia-gpu-monitor — check it's running and files have recent timestamps |
| 4️⃣ Browser cache (most likely) | Cockpit aggressively caches HTML/JS. Try: DevTools → right-click refresh → "Empty Cache and Hard Reload" |
| 5️⃣ Private window test | Open an Incognito/Private window, login to Cockpit, check the GPU page. If it works there, the old tab had stale cache |
| 6️⃣ Restart Cockpit | sudo systemctl restart cockpit then verify in Private window (step 5) |
Root cause when data is fine but page says "NO GPU": Usually a stale browser cache serving an older version of the HTML that had a broken JS path, or the page loaded before the data files existed (race condition on first boot). The .fail() handler in cockpit.spawn(["cat", ...]) shows the alert when the cat fails or returns empty data — Cockpit's cached HTML may reference a different file path than what's currently on disk.
Prevention: Add stream=True to the cockpit.spawn() call or add a 1-second retry in the update function. But the most reliable fix is the Private Window test to distinguish cache from actual malfunction.
Reference Files
references/nvidia-gpu-dashboard.html— deployable Cockpit dashboard page (stat-based, ring-gauge-free, cache-busting metas, CPU+GPU). Copy this to/usr/share/cockpit/nvidia-gpu/index.htmlas a starting point.references/cpu-temp-data-format.md— CPU temperature data format, sysfs sensor zones, adding CPU card to GPU dashboard (accent-cyan, temp-group layout, JS parsing).references/nvidia-fan-curves.md— GPU fan curve and power-draw reporting reference (GTX 1050 Ti).references/cockpit-360-quirks.md— Cockpit 360 JS API sandboxing notes: what works (spawn with cat), what fails (script, file.read for absolute paths), and diagnostic steps. (Absorbed fromcockpit-plugin-dev.)references/cockpit-connectivity.md— Cockpit access and connectivity: socket activation (why service shows inactive), HTTPS requirement, self-signed cert, and Tailscale access.templates/nvidia-gpu-monitor.service— systemd unit template for the monitor script.