@@ -321,64 +321,20 @@ <h3 class="card-title" style="color: white;"><i class="fas fa-check-circle"></i>
321321 </div>
322322 </div>
323323
324- <div class="form-group" style="margin-top: 1.5rem;">
325- <label>Quick Start Command</label>
326- <p class="text-muted" style="margin-bottom: 0.5rem;">Run this on the device to connect it to the hub:</p>
327- <div class="input-group">
328- <textarea class="form-control" readonly id="device-command-input" rows="2"
329- style="font-family: monospace; font-size: 0.85rem; resize: none;">python apps/remote_satellite/daemon.py --hub-ws-url {{ ws_url }} --device-token ${ result . token } </textarea>
330- <button type="button" class="btn btn-outline" onclick="copyToClipboard('device-command-input', 'Command')">
331- <i class="fas fa-copy"></i> Copy
332- </button>
333- </div>
324+ <div style="margin-top: 1.5rem; display: flex; gap: 1rem; align-items: center; flex-wrap: wrap;">
325+ <button type="button" class="btn btn-primary" id="launch-satellite-btn"
326+ onclick="launchSatellite('${ result . device_id } ', '${ result . token } ')"
327+ style="font-size: 1rem; padding: 0.75rem 1.5rem;">
328+ <i class="fas fa-rocket"></i> Launch Satellite
329+ </button>
330+ <span id="launch-satellite-status" style="color: var(--color-gray-300); font-size: 0.9rem;"></span>
331+ </div>
332+ <div id="launch-satellite-info" style="display: none; margin-top: 1rem; background: var(--color-gray-700); border-radius: var(--radius-md); padding: var(--spacing-md);">
334333 </div>
335-
336- <details style="margin-top: 1.5rem; background: var(--color-gray-700); border-radius: var(--radius-md); padding: 0;">
337- <summary style="cursor: pointer; color: var(--color-highlight); font-weight: 600; padding: var(--spacing-md); list-style: none; display: flex; align-items: center; gap: 0.5rem;">
338- <i class="fas fa-rocket"></i> Launch Agent Worker — Full Setup Guide
339- <i class="fas fa-chevron-down" style="margin-left: auto; font-size: 0.75rem;"></i>
340- </summary>
341- <div style="padding: 0 var(--spacing-md) var(--spacing-md);">
342- <h4 style="margin-bottom: var(--spacing-sm); margin-top: var(--spacing-sm);">1. Prerequisites</h4>
343- <ul style="margin-left: var(--spacing-lg); margin-bottom: var(--spacing-md); color: var(--color-gray-300);">
344- <li>Python 3.11+ with the <code>marvain</code> conda environment</li>
345- <li>LiveKit and OpenAI credentials configured in AWS Secrets Manager</li>
346- <li>Network access to the Hub WebSocket endpoint</li>
347- </ul>
348-
349- <h4 style="margin-bottom: var(--spacing-sm);">2. Activate Environment</h4>
350- <div class="input-group" style="margin-bottom: var(--spacing-md);">
351- <textarea class="form-control" readonly id="device-env-cmd" rows="2"
352- style="font-family: 'JetBrains Mono', monospace; font-size: 0.85rem; resize: none; background: var(--color-gray-900);">cd /path/to/marvain
353- source marvain_activate</textarea>
354- <button type="button" class="btn btn-outline" onclick="copyToClipboard('device-env-cmd', 'Env command')">
355- <i class="fas fa-copy"></i>
356- </button>
357- </div>
358-
359- <h4 style="margin-bottom: var(--spacing-sm);">3. Start the Satellite Daemon</h4>
360- <div class="input-group" style="margin-bottom: var(--spacing-md);">
361- <textarea class="form-control" readonly id="device-daemon-cmd" rows="3"
362- style="font-family: 'JetBrains Mono', monospace; font-size: 0.85rem; resize: none; background: var(--color-gray-900);">python apps/remote_satellite/daemon.py \\
363- --hub-ws-url {{ ws_url }} \\
364- --device-token ${ result . token } </textarea>
365- <button type="button" class="btn btn-outline" onclick="copyToClipboard('device-daemon-cmd', 'Daemon command')">
366- <i class="fas fa-copy"></i>
367- </button>
368- </div>
369-
370- <h4 style="margin-bottom: var(--spacing-sm);">4. Verify Connection</h4>
371- <p style="color: var(--color-gray-300); margin-bottom: var(--spacing-sm);">
372- Once the daemon connects, the device status will change to
373- <span class="badge badge-success" style="font-size: 0.75rem;"><i class="fas fa-circle"></i> Online</span>
374- on this page. You can also use the <strong>Ping Device</strong> button on the device detail page to confirm connectivity.
375- </p>
376- </div>
377- </details>
378334
379335 <div style="margin-top: 1.5rem; text-align: right;">
380- <button type="button" class="btn btn-primary " onclick="dismissTokenResult()">
381- <i class="fas fa-check"></i> Done - I've Copied the Token
336+ <button type="button" class="btn btn-outline " onclick="dismissTokenResult()">
337+ <i class="fas fa-check"></i> Done
382338 </button>
383339 </div>
384340 </div>
@@ -396,6 +352,51 @@ <h4 style="margin-bottom: var(--spacing-sm);">4. Verify Connection</h4>
396352 location . reload ( ) ;
397353}
398354
355+ async function launchSatellite ( deviceId , deviceToken ) {
356+ const btn = document . getElementById ( 'launch-satellite-btn' ) ;
357+ const status = document . getElementById ( 'launch-satellite-status' ) ;
358+ const info = document . getElementById ( 'launch-satellite-info' ) ;
359+
360+ btn . disabled = true ;
361+ btn . innerHTML = '<i class="fas fa-spinner fa-spin"></i> Launching…' ;
362+ status . textContent = '' ;
363+
364+ try {
365+ const resp = await fetch ( `/api/devices/${ deviceId } /launch-satellite` , {
366+ method : 'POST' ,
367+ headers : { 'Content-Type' : 'application/json' } ,
368+ body : JSON . stringify ( { device_token : deviceToken } ) ,
369+ } ) ;
370+ const data = await resp . json ( ) ;
371+
372+ if ( ! resp . ok ) {
373+ throw new Error ( data . detail || 'Launch failed' ) ;
374+ }
375+
376+ if ( data . status === 'already_running' ) {
377+ btn . innerHTML = '<i class="fas fa-circle" style="color: var(--color-success);"></i> Already Running' ;
378+ btn . classList . remove ( 'btn-primary' ) ;
379+ btn . classList . add ( 'btn-outline' ) ;
380+ status . innerHTML = `PID <strong>${ data . pid } </strong>` ;
381+ Marvain . showToast ( 'info' , 'Already Running' , data . message ) ;
382+ } else {
383+ btn . innerHTML = '<i class="fas fa-check-circle"></i> Satellite Running' ;
384+ btn . classList . remove ( 'btn-primary' ) ;
385+ btn . classList . add ( 'btn-outline' ) ;
386+ btn . style . borderColor = 'var(--color-success)' ;
387+ btn . style . color = 'var(--color-success)' ;
388+ status . innerHTML = `PID <strong>${ data . pid } </strong>` ;
389+ info . style . display = 'block' ;
390+ info . innerHTML = `<p style="margin:0; color: var(--color-gray-300); font-size: 0.85rem;"><i class="fas fa-file-alt"></i> Log: <code>${ data . log_file } </code></p>` ;
391+ Marvain . showToast ( 'success' , 'Launched' , data . message ) ;
392+ }
393+ } catch ( err ) {
394+ btn . disabled = false ;
395+ btn . innerHTML = '<i class="fas fa-rocket"></i> Launch Satellite' ;
396+ Marvain . showToast ( 'error' , 'Launch Failed' , err . message ) ;
397+ }
398+ }
399+
399400function copyToClipboard ( inputId , label ) {
400401 const input = document . getElementById ( inputId ) ;
401402 input . select ( ) ;
0 commit comments