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 ">
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=<your-shared- api-key>
39+ CASHPILOT_API_KEY=< span id =" env- api-key" > ******** </ span >
4040CASHPILOT_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