worked backup

This commit is contained in:
2025-12-21 00:16:12 +03:00
parent d05344e604
commit 43b4c75e36
18 changed files with 273 additions and 59 deletions

View File

@@ -24,7 +24,7 @@ export const options = {
app: ({ head, body, assets, nonce, env }) => "<!DOCTYPE html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<link rel=\"icon\" href=\"" + assets + "/favicon.png\" />\n\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n\t\t" + head + "\n\t</head>\n\t<body data-sveltekit-preload-data=\"hover\">\n\t\t<div style=\"display: contents\">" + body + "</div>\n\t</body>\n</html>\n",
error: ({ status, message }) => "<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<title>" + message + "</title>\n\n\t\t<style>\n\t\t\tbody {\n\t\t\t\t--bg: white;\n\t\t\t\t--fg: #222;\n\t\t\t\t--divider: #ccc;\n\t\t\t\tbackground: var(--bg);\n\t\t\t\tcolor: var(--fg);\n\t\t\t\tfont-family:\n\t\t\t\t\tsystem-ui,\n\t\t\t\t\t-apple-system,\n\t\t\t\t\tBlinkMacSystemFont,\n\t\t\t\t\t'Segoe UI',\n\t\t\t\t\tRoboto,\n\t\t\t\t\tOxygen,\n\t\t\t\t\tUbuntu,\n\t\t\t\t\tCantarell,\n\t\t\t\t\t'Open Sans',\n\t\t\t\t\t'Helvetica Neue',\n\t\t\t\t\tsans-serif;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: center;\n\t\t\t\theight: 100vh;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t.error {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tmax-width: 32rem;\n\t\t\t\tmargin: 0 1rem;\n\t\t\t}\n\n\t\t\t.status {\n\t\t\t\tfont-weight: 200;\n\t\t\t\tfont-size: 3rem;\n\t\t\t\tline-height: 1;\n\t\t\t\tposition: relative;\n\t\t\t\ttop: -0.05rem;\n\t\t\t}\n\n\t\t\t.message {\n\t\t\t\tborder-left: 1px solid var(--divider);\n\t\t\t\tpadding: 0 0 0 1rem;\n\t\t\t\tmargin: 0 0 0 1rem;\n\t\t\t\tmin-height: 2.5rem;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t}\n\n\t\t\t.message h1 {\n\t\t\t\tfont-weight: 400;\n\t\t\t\tfont-size: 1em;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t@media (prefers-color-scheme: dark) {\n\t\t\t\tbody {\n\t\t\t\t\t--bg: #222;\n\t\t\t\t\t--fg: #ddd;\n\t\t\t\t\t--divider: #666;\n\t\t\t\t}\n\t\t\t}\n\t\t</style>\n\t</head>\n\t<body>\n\t\t<div class=\"error\">\n\t\t\t<span class=\"status\">" + status + "</span>\n\t\t\t<div class=\"message\">\n\t\t\t\t<h1>" + message + "</h1>\n\t\t\t</div>\n\t\t</div>\n\t</body>\n</html>\n"
},
version_hash: "cx7alu"
version_hash: "1eogxsl"
};
export async function get_hooks() {

View File

@@ -14,39 +14,107 @@
import { get } from 'svelte/store';
import { selectedTask, taskLogs } from '../lib/stores.js';
import { getWsUrl } from '../lib/api.js';
import { addToast } from '../lib/toasts.js';
// [/SECTION]
let ws;
let reconnectAttempts = 0;
let maxReconnectAttempts = 10;
let initialReconnectDelay = 1000;
let maxReconnectDelay = 30000;
let reconnectTimeout;
let waitingForData = false;
let dataTimeout;
let connectionStatus = 'disconnected'; // 'connecting', 'connected', 'disconnected', 'waiting', 'completed'
// [DEF:connect:Function]
/**
* @purpose Establishes WebSocket connection with exponential backoff.
*/
function connect() {
const task = get(selectedTask);
if (!task || connectionStatus === 'completed') return;
console.log(`[TaskRunner][Entry] Connecting to logs for task: ${task.id} (Attempt ${reconnectAttempts + 1})`);
connectionStatus = 'connecting';
const wsUrl = getWsUrl(task.id);
ws = new WebSocket(wsUrl);
ws.onopen = () => {
console.log('[TaskRunner][Coherence:OK] WebSocket connection established');
connectionStatus = 'connected';
reconnectAttempts = 0;
startDataTimeout();
};
ws.onmessage = (event) => {
const logEntry = JSON.parse(event.data);
taskLogs.update(logs => [...logs, logEntry]);
resetDataTimeout();
// Check for completion message (if backend sends one)
if (logEntry.message && logEntry.message.includes('Task completed successfully')) {
connectionStatus = 'completed';
ws.close();
}
};
ws.onerror = (error) => {
console.error('[TaskRunner][Coherence:Failed] WebSocket error:', error);
connectionStatus = 'disconnected';
};
ws.onclose = (event) => {
console.log(`[TaskRunner][Exit] WebSocket connection closed (Code: ${event.code})`);
clearTimeout(dataTimeout);
waitingForData = false;
if (connectionStatus !== 'completed' && reconnectAttempts < maxReconnectAttempts) {
const delay = Math.min(initialReconnectDelay * Math.pow(2, reconnectAttempts), maxReconnectDelay);
console.log(`[TaskRunner][Action] Reconnecting in ${delay}ms...`);
reconnectTimeout = setTimeout(() => {
reconnectAttempts++;
connect();
}, delay);
} else if (reconnectAttempts >= maxReconnectAttempts) {
console.error('[TaskRunner][Coherence:Failed] Max reconnect attempts reached.');
addToast('Failed to connect to log stream after multiple attempts.', 'error');
}
};
}
// [/DEF:connect]
function startDataTimeout() {
waitingForData = false;
dataTimeout = setTimeout(() => {
if (connectionStatus === 'connected') {
waitingForData = true;
}
}, 5000);
}
function resetDataTimeout() {
clearTimeout(dataTimeout);
waitingForData = false;
startDataTimeout();
}
// [DEF:onMount:Function]
/**
* @purpose Initialize WebSocket connection for task logs.
*/
onMount(() => {
const task = get(selectedTask);
if (task) {
console.log(`[TaskRunner][Entry] Connecting to logs for task: ${task.id}`);
taskLogs.set([]); // Clear previous logs
const wsUrl = getWsUrl(task.id);
ws = new WebSocket(wsUrl);
ws.onopen = () => {
console.log('[TaskRunner][Coherence:OK] WebSocket connection established');
};
ws.onmessage = (event) => {
const logEntry = JSON.parse(event.data);
taskLogs.update(logs => [...logs, logEntry]);
};
ws.onerror = (error) => {
console.error('[TaskRunner][Coherence:Failed] WebSocket error:', error);
};
ws.onclose = () => {
console.log('[TaskRunner][Exit] WebSocket connection closed');
};
}
// Subscribe to selectedTask changes
const unsubscribe = selectedTask.subscribe(task => {
if (task) {
console.log(`[TaskRunner][Action] Task selected: ${task.id}. Initializing connection.`);
if (ws) ws.close();
clearTimeout(reconnectTimeout);
reconnectAttempts = 0;
connectionStatus = 'disconnected';
taskLogs.set([]);
connect();
}
});
return unsubscribe;
});
// [/DEF:onMount]
@@ -55,6 +123,8 @@
* @purpose Close WebSocket connection when the component is destroyed.
*/
onDestroy(() => {
clearTimeout(reconnectTimeout);
clearTimeout(dataTimeout);
if (ws) {
console.log("[TaskRunner][Action] Closing WebSocket connection.");
ws.close();
@@ -66,8 +136,29 @@
<!-- [SECTION: TEMPLATE] -->
<div class="p-4 border rounded-lg bg-white shadow-md">
{#if $selectedTask}
<h2 class="text-xl font-semibold mb-2">Task: {$selectedTask.plugin_id}</h2>
<div class="bg-gray-900 text-white font-mono text-sm p-4 rounded-md h-96 overflow-y-auto">
<div class="flex justify-between items-center mb-2">
<h2 class="text-xl font-semibold">Task: {$selectedTask.plugin_id}</h2>
<div class="flex items-center space-x-2">
{#if connectionStatus === 'connecting'}
<span class="flex h-3 w-3 relative">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-yellow-400 opacity-75"></span>
<span class="relative inline-flex rounded-full h-3 w-3 bg-yellow-500"></span>
</span>
<span class="text-xs text-gray-500">Connecting...</span>
{:else if connectionStatus === 'connected'}
<span class="h-3 w-3 rounded-full bg-green-500"></span>
<span class="text-xs text-gray-500">Live</span>
{:else if connectionStatus === 'completed'}
<span class="h-3 w-3 rounded-full bg-blue-500"></span>
<span class="text-xs text-gray-500">Completed</span>
{:else}
<span class="h-3 w-3 rounded-full bg-red-500"></span>
<span class="text-xs text-gray-500">Disconnected</span>
{/if}
</div>
</div>
<div class="bg-gray-900 text-white font-mono text-sm p-4 rounded-md h-96 overflow-y-auto relative">
{#each $taskLogs as log}
<div>
<span class="text-gray-400">{new Date(log.timestamp).toLocaleTimeString()}</span>
@@ -75,6 +166,12 @@
<span>{log.message}</span>
</div>
{/each}
{#if waitingForData}
<div class="text-gray-500 italic mt-2 animate-pulse">
Waiting for data...
</div>
{/if}
</div>
{:else}
<p>No task selected.</p>

View File

@@ -14,7 +14,12 @@ const API_BASE_URL = '/api';
* @returns {string}
*/
export const getWsUrl = (taskId) => {
const baseUrl = PUBLIC_WS_URL || `ws://${window.location.hostname}:8000`;
let baseUrl = PUBLIC_WS_URL;
if (!baseUrl) {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
// Use the current host and port to allow Vite proxy to handle the connection
baseUrl = `${protocol}//${window.location.host}`;
}
return `${baseUrl}/ws/logs/${taskId}`;
};

View File

@@ -11,7 +11,8 @@ export default defineConfig({
},
'/ws': {
target: 'ws://localhost:8000',
ws: true
ws: true,
changeOrigin: true
}
}
}