Skip to content

Commit 10db3f3

Browse files
committed
Fix fleet page: remove local instance card, clean container badges
- Remove "This Instance local" card (UI has no local containers) - Container badges: color-only status (green=running, grey=stopped), no redundant "(running)" text - Add API key reveal/hide toggle with /api/fleet/api-key endpoint - Worker cards show colored left border (green=online, grey=offline) - Reduce fleet refresh to 60s
1 parent 5aa809d commit 10db3f3

2 files changed

Lines changed: 58 additions & 39 deletions

File tree

app/main.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1295,3 +1295,10 @@ async def api_fleet_summary(request: Request) -> dict[str, Any]:
12951295
"total_containers": total_containers,
12961296
"running_containers": total_running,
12971297
}
1298+
1299+
1300+
@app.get("/api/fleet/api-key")
1301+
async def api_fleet_api_key(request: Request) -> dict[str, str]:
1302+
"""Return the configured fleet API key (owner only)."""
1303+
_require_auth_api(request)
1304+
return {"api_key": FLEET_API_KEY or ""}

app/templates/fleet.html

Lines changed: 51 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@
88
<!-- Fleet Stats -->
99
<div class="stats-grid">
1010
<div class="stat-card">
11-
<div class="stat-label">Total Workers</div>
11+
<div class="stat-label">Workers</div>
1212
<div class="stat-value highlight" id="fleet-total-workers">-</div>
1313
</div>
1414
<div class="stat-card">
1515
<div class="stat-label">Online</div>
1616
<div class="stat-value" id="fleet-online-workers">-</div>
1717
</div>
1818
<div class="stat-card">
19-
<div class="stat-label">Total Containers</div>
19+
<div class="stat-label">Containers</div>
2020
<div class="stat-value" id="fleet-total-containers">-</div>
2121
</div>
2222
<div class="stat-card">
@@ -32,16 +32,19 @@
3232
</div>
3333
<div style="padding: 0 20px 20px;">
3434
<p style="color: var(--text-secondary); margin-bottom: 12px; font-size: 0.9rem;">
35-
Deploy <code>drumsergio/cashpilot-worker</code> on each remote server with these environment variables:
35+
Deploy <code>drumsergio/cashpilot-worker</code> on each server with these environment variables:
3636
</p>
3737
<div style="background: var(--bg-card); border: 1px solid var(--border-color); border-radius: 8px; padding: 12px;">
3838
<code id="worker-env" style="display: block; word-break: break-all; color: var(--text-primary); font-size: 0.85rem; white-space: pre-wrap;">CASHPILOT_UI_URL=<span id="env-ui-url"></span>
39-
CASHPILOT_API_KEY=&lt;your-shared-api-key&gt;
39+
CASHPILOT_API_KEY=<span id="env-api-key">********</span>
4040
CASHPILOT_WORKER_NAME=server-name</code>
41-
<button class="btn btn-ghost btn-sm" style="margin-top: 8px;" onclick="copyWorkerEnv()">Copy</button>
41+
<div style="display: flex; gap: 8px; margin-top: 8px;">
42+
<button class="btn btn-ghost btn-sm" onclick="toggleApiKey()" id="reveal-btn">Reveal API Key</button>
43+
<button class="btn btn-ghost btn-sm" onclick="copyWorkerEnv()">Copy</button>
44+
</div>
4245
</div>
4346
<p style="color: var(--text-muted); margin-top: 8px; font-size: 0.82rem;">
44-
Set <code>CASHPILOT_API_KEY</code> in both the UI and workers. Workers auto-register on first heartbeat.
47+
The API key is set via <code>CASHPILOT_API_KEY</code> env var on the UI container. Use the same key on all workers.
4548
</p>
4649
</div>
4750
</div>
@@ -62,8 +65,9 @@ <h2 class="section-title">Workers</h2>
6265
<script>
6366
(function() {
6467
let fleetTimer = null;
68+
let _apiKeyRevealed = false;
69+
let _apiKey = '';
6570

66-
// Set the UI URL in the env example
6771
document.getElementById('env-ui-url').textContent = window.location.origin;
6872

6973
async function loadFleet() {
@@ -89,47 +93,34 @@ <h2 class="section-title">Workers</h2>
8993
const container = document.getElementById('fleet-workers');
9094
if (!container) return;
9195

92-
// Local instance card
93-
let html = `
94-
<div class="card" style="margin-bottom: 12px; border-left: 3px solid var(--accent);">
95-
<div style="padding: 16px; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px;">
96-
<div>
97-
<span style="font-weight: 600; color: var(--text-primary);">This Instance</span>
98-
<span class="badge badge-running">local</span>
99-
</div>
100-
</div>
101-
</div>`;
96+
let html = '';
10297

10398
if (workers.length === 0) {
104-
html += `<div class="empty-state" style="padding: 40px 20px;">
105-
<div class="empty-state-title">No remote workers</div>
106-
<div class="empty-state-text">Deploy a CashPilot Worker on your other servers to see them here.</div>
99+
html = `<div class="empty-state" style="padding: 40px 20px;">
100+
<div class="empty-state-title">No workers registered</div>
101+
<div class="empty-state-text">Deploy a CashPilot Worker on your servers to see them here.</div>
107102
</div>`;
108103
} else {
109104
for (const w of workers) {
110105
const isOnline = w.status === 'online';
111106
const statusClass = isOnline ? 'running' : 'stopped';
112-
const statusLabel = isOnline ? 'Online' : 'Offline';
107+
const borderColor = isOnline ? 'var(--success)' : 'var(--text-muted)';
113108
const sysInfo = w.system_info || {};
114109
html += `
115-
<div class="card" style="margin-bottom: 12px;">
110+
<div class="card" style="margin-bottom: 12px; border-left: 3px solid ${borderColor};">
116111
<div style="padding: 16px;">
117112
<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px; margin-bottom: 12px;">
118-
<div>
113+
<div style="display: flex; align-items: center; gap: 8px;">
114+
<span class="status-dot ${statusClass}"></span>
119115
<span style="font-weight: 600; color: var(--text-primary);">${esc(w.name)}</span>
120-
<span class="badge badge-${statusClass}"><span class="status-dot ${statusClass}"></span> ${statusLabel}</span>
121-
</div>
122-
<div style="display: flex; gap: 6px;">
123-
<button class="btn btn-danger btn-sm" onclick="removeWorker(${w.id}, '${esc(w.name)}')" title="Remove worker">Remove</button>
124116
</div>
117+
<button class="btn btn-danger btn-sm" onclick="removeWorker(${w.id}, '${esc(w.name)}')" title="Remove worker">Remove</button>
125118
</div>
126-
<div style="display: flex; gap: 16px; flex-wrap: wrap; font-size: 0.85rem; color: var(--text-muted);">
127-
<span>URL: ${esc(w.url || '-')}</span>
128-
<span>OS: ${esc(sysInfo.os || '-')}</span>
129-
<span>Arch: ${esc(sysInfo.arch || '-')}</span>
130-
<span>Docker: ${sysInfo.docker_available ? 'Yes' : 'No'}</span>
131-
<span>Containers: ${w.container_count || 0} (${w.running_count || 0} running)</span>
132-
<span>Last heartbeat: ${w.last_heartbeat || 'never'}</span>
119+
<div style="display: flex; gap: 16px; flex-wrap: wrap; font-size: 0.82rem; color: var(--text-muted);">
120+
<span>${esc(w.url || '-')}</span>
121+
<span>${esc(sysInfo.os || '-')}</span>
122+
<span>${esc(sysInfo.arch || '-')}</span>
123+
<span>Last seen: ${w.last_heartbeat || 'never'}</span>
133124
</div>
134125
${w.containers && w.containers.length > 0 ? renderContainers(w) : ''}
135126
</div>
@@ -141,11 +132,13 @@ <h2 class="section-title">Workers</h2>
141132

142133
function renderContainers(worker) {
143134
let html = '<div style="margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border-color);">';
144-
html += '<div style="font-size: 0.8rem; color: var(--text-muted); margin-bottom: 8px;">Containers:</div>';
145135
html += '<div style="display: flex; gap: 6px; flex-wrap: wrap;">';
146136
for (const c of worker.containers) {
147-
const cls = (c.status || 'stopped').toLowerCase();
148-
html += `<span class="badge badge-${cls}">${esc(c.slug || c.name)} (${cls})</span>`;
137+
const status = (c.status || 'stopped').toLowerCase();
138+
const isRunning = status === 'running';
139+
const color = isRunning ? 'var(--success)' : 'var(--text-muted)';
140+
const bg = isRunning ? 'var(--success-soft)' : 'var(--bg-hover)';
141+
html += `<span style="font-size:0.75rem; padding:3px 8px; border-radius:4px; background:${bg}; color:${color}; font-weight:500;">${esc(c.slug || c.name)}</span>`;
149142
}
150143
html += '</div></div>';
151144
return html;
@@ -157,6 +150,26 @@ <h2 class="section-title">Workers</h2>
157150
return d.innerHTML;
158151
}
159152

153+
window.toggleApiKey = async function() {
154+
const el = document.getElementById('env-api-key');
155+
const btn = document.getElementById('reveal-btn');
156+
if (_apiKeyRevealed) {
157+
el.textContent = '********';
158+
btn.textContent = 'Reveal API Key';
159+
_apiKeyRevealed = false;
160+
} else {
161+
if (!_apiKey) {
162+
try {
163+
const data = await CP.api('/api/fleet/api-key');
164+
_apiKey = data.api_key || '(not configured)';
165+
} catch { _apiKey = '(error)'; }
166+
}
167+
el.textContent = _apiKey;
168+
btn.textContent = 'Hide API Key';
169+
_apiKeyRevealed = true;
170+
}
171+
};
172+
160173
window.removeWorker = async function(workerId, name) {
161174
if (!confirm(`Remove worker "${name}"? This will unregister it from the fleet.`)) return;
162175
try {
@@ -173,9 +186,8 @@ <h2 class="section-title">Workers</h2>
173186
navigator.clipboard.writeText(text).then(() => CP.toast('Copied', 'success'));
174187
};
175188

176-
// Init
177189
loadFleet();
178-
fleetTimer = setInterval(loadFleet, 30000);
190+
fleetTimer = setInterval(loadFleet, 60000);
179191
})();
180192
</script>
181193
{% endblock %}

0 commit comments

Comments
 (0)