175 lines
7.3 KiB
Svelte
175 lines
7.3 KiB
Svelte
<!-- [DEF:TaskLogViewer:Component] -->
|
|
<!--
|
|
@SEMANTICS: task, log, viewer, modal, inline
|
|
@PURPOSE: Displays detailed logs for a specific task in a modal or inline using TaskLogPanel.
|
|
@LAYER: UI
|
|
@RELATION: USES -> frontend/src/services/taskService.js, frontend/src/components/tasks/TaskLogPanel.svelte
|
|
-->
|
|
<script>
|
|
import { createEventDispatcher, onMount, onDestroy } from 'svelte';
|
|
import { getTaskLogs } from '../services/taskService.js';
|
|
import { t } from '../lib/i18n';
|
|
import { Button } from '../lib/ui';
|
|
import TaskLogPanel from './tasks/TaskLogPanel.svelte';
|
|
|
|
export let show = false;
|
|
export let inline = false;
|
|
export let taskId = null;
|
|
export let taskStatus = null; // To know if we should poll
|
|
|
|
const dispatch = createEventDispatcher();
|
|
|
|
let logs = [];
|
|
let loading = false;
|
|
let error = "";
|
|
let interval;
|
|
let autoScroll = true;
|
|
let selectedSource = 'all';
|
|
let selectedLevel = 'all';
|
|
|
|
$: shouldShow = inline || show;
|
|
|
|
// [DEF:fetchLogs:Function]
|
|
/**
|
|
* @purpose Fetches logs for the current task.
|
|
* @pre taskId must be set.
|
|
* @post logs array is updated with data from taskService.
|
|
* @side_effect Updates logs, loading, and error state.
|
|
*/
|
|
async function fetchLogs() {
|
|
if (!taskId) return;
|
|
console.log(`[fetchLogs][Action] Fetching logs for task context={{'taskId': '${taskId}', 'source': '${selectedSource}', 'level': '${selectedLevel}'}}`);
|
|
try {
|
|
// Note: getTaskLogs currently doesn't support filters, but we can filter client-side for now
|
|
// or update taskService later. For US1, the WebSocket handles real-time filtering.
|
|
logs = await getTaskLogs(taskId);
|
|
console.log(`[fetchLogs][Coherence:OK] Logs fetched context={{'count': ${logs.length}}}`);
|
|
} catch (e) {
|
|
error = e.message;
|
|
console.error(`[fetchLogs][Coherence:Failed] Error fetching logs context={{'error': '${e.message}'}}`);
|
|
} finally {
|
|
loading = false;
|
|
}
|
|
}
|
|
// [/DEF:fetchLogs:Function]
|
|
|
|
function handleFilterChange(event) {
|
|
const { source, level } = event.detail;
|
|
selectedSource = source;
|
|
selectedLevel = level;
|
|
// Re-fetch or re-filter if needed.
|
|
// For now, we just log it as the WebSocket will handle real-time updates with filters.
|
|
console.log(`[TaskLogViewer] Filter changed: source=${source}, level=${level}`);
|
|
}
|
|
|
|
// [DEF:close:Function]
|
|
/**
|
|
* @purpose Closes the log viewer modal.
|
|
* @pre Modal is open.
|
|
* @post Modal is closed and close event is dispatched.
|
|
*/
|
|
function close() {
|
|
dispatch('close');
|
|
show = false;
|
|
}
|
|
// [/DEF:close:Function]
|
|
|
|
// React to changes in show/taskId/taskStatus
|
|
$: if (shouldShow && taskId) {
|
|
if (interval) clearInterval(interval);
|
|
|
|
logs = [];
|
|
loading = true;
|
|
error = "";
|
|
fetchLogs();
|
|
|
|
// Poll if task is running (Fallback for when WS is not used)
|
|
if (taskStatus === 'RUNNING' || taskStatus === 'AWAITING_INPUT' || taskStatus === 'AWAITING_MAPPING') {
|
|
interval = setInterval(fetchLogs, 3000);
|
|
}
|
|
} else {
|
|
if (interval) clearInterval(interval);
|
|
}
|
|
|
|
// [DEF:onDestroy:Function]
|
|
/**
|
|
* @purpose Cleans up the polling interval.
|
|
* @pre Component is being destroyed.
|
|
* @post Polling interval is cleared.
|
|
*/
|
|
onDestroy(() => {
|
|
if (interval) clearInterval(interval);
|
|
});
|
|
// [/DEF:onDestroy:Function]
|
|
</script>
|
|
|
|
{#if shouldShow}
|
|
{#if inline}
|
|
<div class="flex flex-col h-full w-full p-4">
|
|
<div class="flex justify-between items-center mb-4">
|
|
<h3 class="text-lg font-medium text-gray-900">
|
|
{$t.tasks?.logs_title} <span class="text-sm text-gray-500 font-normal">({taskId})</span>
|
|
</h3>
|
|
<Button variant="ghost" size="sm" on:click={fetchLogs} class="text-blue-600">{$t.tasks?.refresh}</Button>
|
|
</div>
|
|
|
|
<div class="flex-1 min-h-[400px]">
|
|
{#if loading && logs.length === 0}
|
|
<p class="text-gray-500 text-center">{$t.tasks?.loading}</p>
|
|
{:else if error}
|
|
<p class="text-red-500 text-center">{error}</p>
|
|
{:else}
|
|
<TaskLogPanel
|
|
{taskId}
|
|
{logs}
|
|
{autoScroll}
|
|
on:filterChange={handleFilterChange}
|
|
/>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
{:else}
|
|
<div class="fixed inset-0 z-50 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true">
|
|
<div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
|
<!-- Background overlay -->
|
|
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" aria-hidden="true" on:click={close}></div>
|
|
|
|
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span>
|
|
|
|
<div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-4xl sm:w-full">
|
|
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
|
<div class="sm:flex sm:items-start">
|
|
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left w-full">
|
|
<h3 class="text-lg leading-6 font-medium text-gray-900 flex justify-between items-center mb-4" id="modal-title">
|
|
<span>{$t.tasks.logs_title} <span class="text-sm text-gray-500 font-normal">({taskId})</span></span>
|
|
<Button variant="ghost" size="sm" on:click={fetchLogs} class="text-blue-600">{$t.tasks.refresh}</Button>
|
|
</h3>
|
|
|
|
<div class="h-[500px]">
|
|
{#if loading && logs.length === 0}
|
|
<p class="text-gray-500 text-center">{$t.tasks.loading}</p>
|
|
{:else if error}
|
|
<p class="text-red-500 text-center">{error}</p>
|
|
{:else}
|
|
<TaskLogPanel
|
|
{taskId}
|
|
{logs}
|
|
{autoScroll}
|
|
on:filterChange={handleFilterChange}
|
|
/>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
|
|
<Button variant="secondary" on:click={close}>
|
|
{$t.common.cancel}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
{/if}
|
|
<!-- [/DEF:TaskLogViewer:Component] --> |