semantic update
This commit is contained in:
@@ -1,15 +1,16 @@
|
||||
<!-- [DEF:TaskLogViewer:Component] -->
|
||||
<!--
|
||||
@SEMANTICS: task, log, viewer, modal, inline
|
||||
@PURPOSE: Displays detailed logs for a specific task in a modal or 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
|
||||
@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;
|
||||
@@ -23,7 +24,8 @@
|
||||
let error = "";
|
||||
let interval;
|
||||
let autoScroll = true;
|
||||
let logContainer;
|
||||
let selectedSource = 'all';
|
||||
let selectedLevel = 'all';
|
||||
|
||||
$: shouldShow = inline || show;
|
||||
|
||||
@@ -36,12 +38,11 @@
|
||||
*/
|
||||
async function fetchLogs() {
|
||||
if (!taskId) return;
|
||||
console.log(`[fetchLogs][Action] Fetching logs for task context={{'taskId': '${taskId}'}}`);
|
||||
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);
|
||||
if (autoScroll) {
|
||||
scrollToBottom();
|
||||
}
|
||||
console.log(`[fetchLogs][Coherence:OK] Logs fetched context={{'count': ${logs.length}}}`);
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
@@ -52,35 +53,14 @@
|
||||
}
|
||||
// [/DEF:fetchLogs:Function]
|
||||
|
||||
// [DEF:scrollToBottom:Function]
|
||||
/**
|
||||
* @purpose Scrolls the log container to the bottom.
|
||||
* @pre logContainer element must be bound.
|
||||
* @post logContainer scrollTop is set to scrollHeight.
|
||||
*/
|
||||
function scrollToBottom() {
|
||||
if (logContainer) {
|
||||
setTimeout(() => {
|
||||
logContainer.scrollTop = logContainer.scrollHeight;
|
||||
}, 0);
|
||||
}
|
||||
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:scrollToBottom:Function]
|
||||
|
||||
// [DEF:handleScroll:Function]
|
||||
/**
|
||||
* @purpose Updates auto-scroll preference based on scroll position.
|
||||
* @pre logContainer scroll event fired.
|
||||
* @post autoScroll boolean is updated.
|
||||
*/
|
||||
function handleScroll() {
|
||||
if (!logContainer) return;
|
||||
// If user scrolls up, disable auto-scroll
|
||||
const { scrollTop, scrollHeight, clientHeight } = logContainer;
|
||||
const atBottom = scrollHeight - scrollTop - clientHeight < 50;
|
||||
autoScroll = atBottom;
|
||||
}
|
||||
// [/DEF:handleScroll:Function]
|
||||
|
||||
// [DEF:close:Function]
|
||||
/**
|
||||
@@ -94,23 +74,6 @@
|
||||
}
|
||||
// [/DEF:close:Function]
|
||||
|
||||
// [DEF:getLogLevelColor:Function]
|
||||
/**
|
||||
* @purpose Returns the CSS color class for a given log level.
|
||||
* @pre level string is provided.
|
||||
* @post Returns tailwind color class string.
|
||||
*/
|
||||
function getLogLevelColor(level) {
|
||||
switch (level) {
|
||||
case 'INFO': return 'text-blue-600';
|
||||
case 'WARNING': return 'text-yellow-600';
|
||||
case 'ERROR': return 'text-red-600';
|
||||
case 'DEBUG': return 'text-gray-500';
|
||||
default: return 'text-gray-800';
|
||||
}
|
||||
}
|
||||
// [/DEF:getLogLevelColor:Function]
|
||||
|
||||
// React to changes in show/taskId/taskStatus
|
||||
$: if (shouldShow && taskId) {
|
||||
if (interval) clearInterval(interval);
|
||||
@@ -120,7 +83,7 @@
|
||||
error = "";
|
||||
fetchLogs();
|
||||
|
||||
// Poll if task is running
|
||||
// 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);
|
||||
}
|
||||
@@ -150,34 +113,18 @@
|
||||
<Button variant="ghost" size="sm" on:click={fetchLogs} class="text-blue-600">{$t.tasks?.refresh}</Button>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 border rounded-md bg-gray-50 p-4 overflow-y-auto font-mono text-sm"
|
||||
bind:this={logContainer}
|
||||
on:scroll={handleScroll}>
|
||||
<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 if logs.length === 0}
|
||||
<p class="text-gray-500 text-center">{$t.tasks?.no_logs}</p>
|
||||
{:else}
|
||||
{#each logs as log}
|
||||
<div class="mb-1 hover:bg-gray-100 p-1 rounded">
|
||||
<span class="text-gray-400 text-xs mr-2">
|
||||
{new Date(log.timestamp).toLocaleTimeString()}
|
||||
</span>
|
||||
<span class="font-bold text-xs mr-2 w-16 inline-block {getLogLevelColor(log.level)}">
|
||||
[{log.level}]
|
||||
</span>
|
||||
<span class="text-gray-800 break-words">
|
||||
{log.message}
|
||||
</span>
|
||||
{#if log.context}
|
||||
<div class="ml-24 text-xs text-gray-500 mt-1 bg-gray-100 p-1 rounded overflow-x-auto">
|
||||
<pre>{JSON.stringify(log.context, null, 2)}</pre>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
<TaskLogPanel
|
||||
{taskId}
|
||||
{logs}
|
||||
{autoScroll}
|
||||
on:filterChange={handleFilterChange}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -193,39 +140,23 @@
|
||||
<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" id="modal-title">
|
||||
<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="mt-4 border rounded-md bg-gray-50 p-4 h-96 overflow-y-auto font-mono text-sm"
|
||||
bind:this={logContainer}
|
||||
on:scroll={handleScroll}>
|
||||
<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 if logs.length === 0}
|
||||
<p class="text-gray-500 text-center">{$t.tasks.no_logs}</p>
|
||||
{:else}
|
||||
{#each logs as log}
|
||||
<div class="mb-1 hover:bg-gray-100 p-1 rounded">
|
||||
<span class="text-gray-400 text-xs mr-2">
|
||||
{new Date(log.timestamp).toLocaleTimeString()}
|
||||
</span>
|
||||
<span class="font-bold text-xs mr-2 w-16 inline-block {getLogLevelColor(log.level)}">
|
||||
[{log.level}]
|
||||
</span>
|
||||
<span class="text-gray-800 break-words">
|
||||
{log.message}
|
||||
</span>
|
||||
{#if log.context}
|
||||
<div class="ml-24 text-xs text-gray-500 mt-1 bg-gray-100 p-1 rounded overflow-x-auto">
|
||||
<pre>{JSON.stringify(log.context, null, 2)}</pre>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
<TaskLogPanel
|
||||
{taskId}
|
||||
{logs}
|
||||
{autoScroll}
|
||||
on:filterChange={handleFilterChange}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user