Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,10 @@ wheels/

# Virtual environments
.venv

# Node.js / Frontend
node_modules/
web-frontend/dist/
web-frontend/.svelte-kit/
*.log
package-lock.json.bak
15 changes: 6 additions & 9 deletions web-frontend/src/components/JobSidebar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -212,18 +212,15 @@
// Track loading state
let isLoading = $derived(Array.from($managerState.hostStates.values()).some(h => h.status === 'loading'));

async function loadJobs(forceRefresh = false) {
async function loadJobs() {
if (loading) return;

try {
loading = true;

if (forceRefresh) {
await jobStateManager.forceRefresh();
} else {
await jobStateManager.syncAllHosts();
}


// Refresh all jobs (backend handles caching)
await jobStateManager.refresh();

setTimeout(() => loading = false, 500);
} catch (error) {
console.error('Error loading jobs for sidebar:', error);
Expand Down
59 changes: 18 additions & 41 deletions web-frontend/src/components/WebSocketStatus.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { allJobsWebSocketStore, connectAllJobsWebSocket, disconnectAllJobsWebSocket, getConnectionQuality } from '../stores/jobWebSocket';
import { jobStateManager } from '../lib/JobStateManager';
import { Wifi, WifiOff, RefreshCw, AlertTriangle } from 'lucide-svelte';

interface Props {
Expand All @@ -8,27 +8,27 @@

let { compact = false }: Props = $props();

// Use JobStateManager's connection status
const connectionStatus = jobStateManager.getConnectionStatus();

// Reactive connection status
let connected = $derived($allJobsWebSocketStore.connected);
let error = $derived($allJobsWebSocketStore.error);
let connected = $derived($connectionStatus.connected);
let isLoading = $derived($connectionStatus.isLoading);

let reconnecting = $state(false);
let showDetails = $state(false);

async function handleReconnect() {
reconnecting = true;
disconnectAllJobsWebSocket();

// Wait a bit before reconnecting
// Disconnect and reconnect WebSocket
jobStateManager.destroy();
await new Promise(resolve => setTimeout(resolve, 500));
jobStateManager.connectWebSocket();

connectAllJobsWebSocket();
reconnecting = false;
}
// Also refresh data
await jobStateManager.refresh();

function getConnectionQualityInfo() {
const quality = getConnectionQuality();
return quality.allJobs;
reconnecting = false;
}
</script>

Expand All @@ -37,10 +37,10 @@
<button
class="flex items-center gap-2 px-2 py-1 rounded-lg transition-colors {connected ? 'hover:bg-green-50' : 'hover:bg-red-50'}"
onclick={handleReconnect}
disabled={reconnecting}
disabled={reconnecting || isLoading}
title="{connected ? 'WebSocket connected - Click to reconnect' : 'WebSocket disconnected - Click to connect'}"
>
{#if reconnecting}
{#if reconnecting || isLoading}
<RefreshCw class="h-4 w-4 text-blue-500 animate-spin" />
{:else if connected}
<Wifi class="h-4 w-4 text-green-500" />
Expand All @@ -52,15 +52,12 @@
<!-- Full status display -->
<div class="flex items-center gap-3 px-3 py-2 bg-white rounded-lg shadow-sm border border-gray-200">
<div class="flex items-center gap-2">
{#if reconnecting}
{#if reconnecting || isLoading}
<RefreshCw class="h-5 w-5 text-blue-500 animate-spin" />
<span class="text-sm text-blue-600">Reconnecting...</span>
<span class="text-sm text-blue-600">{reconnecting ? 'Reconnecting...' : 'Loading...'}</span>
{:else if connected}
<Wifi class="h-5 w-5 text-green-500" />
<span class="text-sm text-green-600">Live updates active</span>
{:else if error}
<AlertTriangle class="h-5 w-5 text-red-500" />
<span class="text-sm text-red-600">Connection error</span>
{:else}
<WifiOff class="h-5 w-5 text-gray-500" />
<span class="text-sm text-gray-600">Not connected</span>
Expand All @@ -71,29 +68,9 @@
class="ml-2 px-3 py-1 text-xs font-medium rounded-md transition-colors
{connected ? 'bg-gray-100 hover:bg-gray-200 text-gray-700' : 'bg-blue-100 hover:bg-blue-200 text-blue-700'}"
onclick={handleReconnect}
disabled={reconnecting}
disabled={reconnecting || isLoading}
>
{connected ? 'Reconnect' : 'Connect'}
</button>

{#if !compact}
<button
class="ml-auto text-xs text-gray-500 hover:text-gray-700"
onclick={() => showDetails = !showDetails}
>
{showDetails ? 'Hide' : 'Show'} details
</button>
{/if}
</div>

{#if showDetails && !compact}
<div class="mt-2 px-3 py-2 bg-gray-50 rounded-lg text-xs text-gray-600">
{#each Object.entries(getConnectionQualityInfo()) as [key, value]}
<div class="flex justify-between">
<span class="font-medium">{key}:</span>
<span>{value}</span>
</div>
{/each}
</div>
{/if}
{/if}
{/if}
Loading