TaskLog fix
This commit is contained in:
@@ -1,15 +1,16 @@
|
|||||||
<!-- [DEF:TaskLogViewer:Component] -->
|
<!-- [DEF:TaskLogViewer:Component] -->
|
||||||
<!--
|
<!--
|
||||||
@SEMANTICS: task, log, viewer, modal
|
@SEMANTICS: task, log, viewer, modal, inline
|
||||||
@PURPOSE: Displays detailed logs for a specific task in a modal.
|
@PURPOSE: Displays detailed logs for a specific task in a modal or inline.
|
||||||
@LAYER: UI
|
@LAYER: UI
|
||||||
@RELATION: USES -> frontend/src/lib/api.js (inferred)
|
@RELATION: USES -> frontend/src/services/taskService.js
|
||||||
-->
|
-->
|
||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher, onMount, onDestroy } from 'svelte';
|
import { createEventDispatcher, onMount, onDestroy } from 'svelte';
|
||||||
import { getTaskLogs } from '../services/taskService.js';
|
import { getTaskLogs } from '../services/taskService.js';
|
||||||
|
|
||||||
export let show = false;
|
export let show = false;
|
||||||
|
export let inline = false;
|
||||||
export let taskId = null;
|
export let taskId = null;
|
||||||
export let taskStatus = null; // To know if we should poll
|
export let taskStatus = null; // To know if we should poll
|
||||||
|
|
||||||
@@ -22,19 +23,27 @@
|
|||||||
let autoScroll = true;
|
let autoScroll = true;
|
||||||
let logContainer;
|
let logContainer;
|
||||||
|
|
||||||
|
$: shouldShow = inline || show;
|
||||||
|
|
||||||
// [DEF:fetchLogs:Function]
|
// [DEF:fetchLogs:Function]
|
||||||
// @PURPOSE: Fetches logs for the current task.
|
/**
|
||||||
// @PRE: taskId must be set.
|
* @purpose Fetches logs for the current task.
|
||||||
// @POST: logs array is updated with data from taskService.
|
* @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() {
|
async function fetchLogs() {
|
||||||
if (!taskId) return;
|
if (!taskId) return;
|
||||||
|
console.log(`[fetchLogs][Action] Fetching logs for task context={{'taskId': '${taskId}'}}`);
|
||||||
try {
|
try {
|
||||||
logs = await getTaskLogs(taskId);
|
logs = await getTaskLogs(taskId);
|
||||||
if (autoScroll) {
|
if (autoScroll) {
|
||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
}
|
}
|
||||||
|
console.log(`[fetchLogs][Coherence:OK] Logs fetched context={{'count': ${logs.length}}}`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = e.message;
|
error = e.message;
|
||||||
|
console.error(`[fetchLogs][Coherence:Failed] Error fetching logs context={{'error': '${e.message}'}}`);
|
||||||
} finally {
|
} finally {
|
||||||
loading = false;
|
loading = false;
|
||||||
}
|
}
|
||||||
@@ -42,9 +51,11 @@
|
|||||||
// [/DEF:fetchLogs:Function]
|
// [/DEF:fetchLogs:Function]
|
||||||
|
|
||||||
// [DEF:scrollToBottom:Function]
|
// [DEF:scrollToBottom:Function]
|
||||||
// @PURPOSE: Scrolls the log container to the bottom.
|
/**
|
||||||
// @PRE: logContainer element must be bound.
|
* @purpose Scrolls the log container to the bottom.
|
||||||
// @POST: logContainer scrollTop is set to scrollHeight.
|
* @pre logContainer element must be bound.
|
||||||
|
* @post logContainer scrollTop is set to scrollHeight.
|
||||||
|
*/
|
||||||
function scrollToBottom() {
|
function scrollToBottom() {
|
||||||
if (logContainer) {
|
if (logContainer) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -55,9 +66,11 @@
|
|||||||
// [/DEF:scrollToBottom:Function]
|
// [/DEF:scrollToBottom:Function]
|
||||||
|
|
||||||
// [DEF:handleScroll:Function]
|
// [DEF:handleScroll:Function]
|
||||||
// @PURPOSE: Updates auto-scroll preference based on scroll position.
|
/**
|
||||||
// @PRE: logContainer scroll event fired.
|
* @purpose Updates auto-scroll preference based on scroll position.
|
||||||
// @POST: autoScroll boolean is updated.
|
* @pre logContainer scroll event fired.
|
||||||
|
* @post autoScroll boolean is updated.
|
||||||
|
*/
|
||||||
function handleScroll() {
|
function handleScroll() {
|
||||||
if (!logContainer) return;
|
if (!logContainer) return;
|
||||||
// If user scrolls up, disable auto-scroll
|
// If user scrolls up, disable auto-scroll
|
||||||
@@ -68,9 +81,11 @@
|
|||||||
// [/DEF:handleScroll:Function]
|
// [/DEF:handleScroll:Function]
|
||||||
|
|
||||||
// [DEF:close:Function]
|
// [DEF:close:Function]
|
||||||
// @PURPOSE: Closes the log viewer modal.
|
/**
|
||||||
// @PRE: Modal is open.
|
* @purpose Closes the log viewer modal.
|
||||||
// @POST: Modal is closed and close event is dispatched.
|
* @pre Modal is open.
|
||||||
|
* @post Modal is closed and close event is dispatched.
|
||||||
|
*/
|
||||||
function close() {
|
function close() {
|
||||||
dispatch('close');
|
dispatch('close');
|
||||||
show = false;
|
show = false;
|
||||||
@@ -78,9 +93,11 @@
|
|||||||
// [/DEF:close:Function]
|
// [/DEF:close:Function]
|
||||||
|
|
||||||
// [DEF:getLogLevelColor:Function]
|
// [DEF:getLogLevelColor:Function]
|
||||||
// @PURPOSE: Returns the CSS color class for a given log level.
|
/**
|
||||||
// @PRE: level string is provided.
|
* @purpose Returns the CSS color class for a given log level.
|
||||||
// @POST: Returns tailwind color class string.
|
* @pre level string is provided.
|
||||||
|
* @post Returns tailwind color class string.
|
||||||
|
*/
|
||||||
function getLogLevelColor(level) {
|
function getLogLevelColor(level) {
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case 'INFO': return 'text-blue-600';
|
case 'INFO': return 'text-blue-600';
|
||||||
@@ -92,8 +109,10 @@
|
|||||||
}
|
}
|
||||||
// [/DEF:getLogLevelColor:Function]
|
// [/DEF:getLogLevelColor:Function]
|
||||||
|
|
||||||
// React to changes in show/taskId
|
// React to changes in show/taskId/taskStatus
|
||||||
$: if (show && taskId) {
|
$: if (shouldShow && taskId) {
|
||||||
|
if (interval) clearInterval(interval);
|
||||||
|
|
||||||
logs = [];
|
logs = [];
|
||||||
loading = true;
|
loading = true;
|
||||||
error = "";
|
error = "";
|
||||||
@@ -108,76 +127,120 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// [DEF:onDestroy:Function]
|
// [DEF:onDestroy:Function]
|
||||||
// @PURPOSE: Cleans up the polling interval.
|
/**
|
||||||
// @PRE: Component is being destroyed.
|
* @purpose Cleans up the polling interval.
|
||||||
// @POST: Polling interval is cleared.
|
* @pre Component is being destroyed.
|
||||||
|
* @post Polling interval is cleared.
|
||||||
|
*/
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
if (interval) clearInterval(interval);
|
if (interval) clearInterval(interval);
|
||||||
});
|
});
|
||||||
// [/DEF:onDestroy:Function]
|
// [/DEF:onDestroy:Function]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if show}
|
{#if shouldShow}
|
||||||
<div class="fixed inset-0 z-50 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true">
|
{#if inline}
|
||||||
<div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
<div class="flex flex-col h-full w-full p-4">
|
||||||
<!-- Background overlay -->
|
<div class="flex justify-between items-center mb-4">
|
||||||
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" aria-hidden="true" on:click={close}></div>
|
<h3 class="text-lg font-medium text-gray-900">
|
||||||
|
Task Logs <span class="text-sm text-gray-500 font-normal">({taskId})</span>
|
||||||
|
</h3>
|
||||||
|
<button on:click={fetchLogs} class="text-sm text-indigo-600 hover:text-indigo-900">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}>
|
||||||
|
{#if loading && logs.length === 0}
|
||||||
|
<p class="text-gray-500 text-center">Loading logs...</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">No logs available.</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}
|
||||||
|
{/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>
|
<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="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="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||||
<div class="sm:flex sm:items-start">
|
<div class="sm:flex sm:items-start">
|
||||||
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left w-full">
|
<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" id="modal-title">
|
||||||
<span>Task Logs <span class="text-sm text-gray-500 font-normal">({taskId})</span></span>
|
<span>Task Logs <span class="text-sm text-gray-500 font-normal">({taskId})</span></span>
|
||||||
<button on:click={fetchLogs} class="text-sm text-indigo-600 hover:text-indigo-900">Refresh</button>
|
<button on:click={fetchLogs} class="text-sm text-indigo-600 hover:text-indigo-900">Refresh</button>
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div class="mt-4 border rounded-md bg-gray-50 p-4 h-96 overflow-y-auto font-mono text-sm"
|
<div class="mt-4 border rounded-md bg-gray-50 p-4 h-96 overflow-y-auto font-mono text-sm"
|
||||||
bind:this={logContainer}
|
bind:this={logContainer}
|
||||||
on:scroll={handleScroll}>
|
on:scroll={handleScroll}>
|
||||||
{#if loading && logs.length === 0}
|
{#if loading && logs.length === 0}
|
||||||
<p class="text-gray-500 text-center">Loading logs...</p>
|
<p class="text-gray-500 text-center">Loading logs...</p>
|
||||||
{:else if error}
|
{:else if error}
|
||||||
<p class="text-red-500 text-center">{error}</p>
|
<p class="text-red-500 text-center">{error}</p>
|
||||||
{:else if logs.length === 0}
|
{:else if logs.length === 0}
|
||||||
<p class="text-gray-500 text-center">No logs available.</p>
|
<p class="text-gray-500 text-center">No logs available.</p>
|
||||||
{:else}
|
{:else}
|
||||||
{#each logs as log}
|
{#each logs as log}
|
||||||
<div class="mb-1 hover:bg-gray-100 p-1 rounded">
|
<div class="mb-1 hover:bg-gray-100 p-1 rounded">
|
||||||
<span class="text-gray-400 text-xs mr-2">
|
<span class="text-gray-400 text-xs mr-2">
|
||||||
{new Date(log.timestamp).toLocaleTimeString()}
|
{new Date(log.timestamp).toLocaleTimeString()}
|
||||||
</span>
|
</span>
|
||||||
<span class="font-bold text-xs mr-2 w-16 inline-block {getLogLevelColor(log.level)}">
|
<span class="font-bold text-xs mr-2 w-16 inline-block {getLogLevelColor(log.level)}">
|
||||||
[{log.level}]
|
[{log.level}]
|
||||||
</span>
|
</span>
|
||||||
<span class="text-gray-800 break-words">
|
<span class="text-gray-800 break-words">
|
||||||
{log.message}
|
{log.message}
|
||||||
</span>
|
</span>
|
||||||
{#if log.context}
|
{#if log.context}
|
||||||
<div class="ml-24 text-xs text-gray-500 mt-1 bg-gray-100 p-1 rounded overflow-x-auto">
|
<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>
|
<pre>{JSON.stringify(log.context, null, 2)}</pre>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
|
||||||
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
|
||||||
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
|
on:click={close}
|
||||||
on:click={close}
|
>
|
||||||
>
|
Close
|
||||||
Close
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
<!-- [/DEF:TaskLogViewer:Component] -->
|
<!-- [/DEF:TaskLogViewer:Component] -->
|
||||||
@@ -1,3 +1,11 @@
|
|||||||
|
<!-- [DEF:TaskManagementPage:Component] -->
|
||||||
|
<!--
|
||||||
|
@SEMANTICS: tasks, management, history, logs
|
||||||
|
@PURPOSE: Page for managing and monitoring tasks.
|
||||||
|
@LAYER: Page
|
||||||
|
@RELATION: USES -> TaskList
|
||||||
|
@RELATION: USES -> TaskLogViewer
|
||||||
|
-->
|
||||||
<script>
|
<script>
|
||||||
import { onMount, onDestroy } from 'svelte';
|
import { onMount, onDestroy } from 'svelte';
|
||||||
import { getTasks, createTask, getEnvironmentsList } from '../../lib/api';
|
import { getTasks, createTask, getEnvironmentsList } from '../../lib/api';
|
||||||
@@ -14,11 +22,13 @@
|
|||||||
let selectedEnvId = '';
|
let selectedEnvId = '';
|
||||||
|
|
||||||
// [DEF:loadInitialData:Function]
|
// [DEF:loadInitialData:Function]
|
||||||
/* @PURPOSE: Loads tasks and environments on page initialization.
|
/**
|
||||||
@PRE: API must be reachable.
|
* @purpose Loads tasks and environments on page initialization.
|
||||||
@POST: tasks and environments variables are populated.
|
* @pre API must be reachable.
|
||||||
*/
|
* @post tasks and environments variables are populated.
|
||||||
|
*/
|
||||||
async function loadInitialData() {
|
async function loadInitialData() {
|
||||||
|
console.log("[loadInitialData][Action] Loading initial tasks and environments");
|
||||||
try {
|
try {
|
||||||
loading = true;
|
loading = true;
|
||||||
const [tasksData, envsData] = await Promise.all([
|
const [tasksData, envsData] = await Promise.all([
|
||||||
@@ -27,8 +37,9 @@
|
|||||||
]);
|
]);
|
||||||
tasks = tasksData;
|
tasks = tasksData;
|
||||||
environments = envsData;
|
environments = envsData;
|
||||||
|
console.log(`[loadInitialData][Coherence:OK] Data loaded context={{'tasks': ${tasks.length}, 'envs': ${environments.length}}}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load tasks data:', error);
|
console.error(`[loadInitialData][Coherence:Failed] Failed to load tasks data context={{'error': '${error.message}'}}`);
|
||||||
} finally {
|
} finally {
|
||||||
loading = false;
|
loading = false;
|
||||||
}
|
}
|
||||||
@@ -36,10 +47,11 @@
|
|||||||
// [/DEF:loadInitialData:Function]
|
// [/DEF:loadInitialData:Function]
|
||||||
|
|
||||||
// [DEF:refreshTasks:Function]
|
// [DEF:refreshTasks:Function]
|
||||||
/* @PURPOSE: Periodically refreshes the task list.
|
/**
|
||||||
@PRE: API must be reachable.
|
* @purpose Periodically refreshes the task list.
|
||||||
@POST: tasks variable is updated if data is valid.
|
* @pre API must be reachable.
|
||||||
*/
|
* @post tasks variable is updated if data is valid.
|
||||||
|
*/
|
||||||
async function refreshTasks() {
|
async function refreshTasks() {
|
||||||
try {
|
try {
|
||||||
const data = await getTasks();
|
const data = await getTasks();
|
||||||
@@ -48,40 +60,45 @@
|
|||||||
tasks = data;
|
tasks = data;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to refresh tasks:', error);
|
console.error(`[refreshTasks][Coherence:Failed] Failed to refresh tasks context={{'error': '${error.message}'}}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// [/DEF:refreshTasks:Function]
|
// [/DEF:refreshTasks:Function]
|
||||||
|
|
||||||
// [DEF:handleSelectTask:Function]
|
// [DEF:handleSelectTask:Function]
|
||||||
/* @PURPOSE: Updates the selected task ID when a task is clicked.
|
/**
|
||||||
@PRE: event.detail.id must be provided.
|
* @purpose Updates the selected task ID when a task is clicked.
|
||||||
@POST: selectedTaskId is updated.
|
* @pre event.detail.id must be provided.
|
||||||
*/
|
* @post selectedTaskId is updated.
|
||||||
|
*/
|
||||||
function handleSelectTask(event) {
|
function handleSelectTask(event) {
|
||||||
selectedTaskId = event.detail.id;
|
selectedTaskId = event.detail.id;
|
||||||
|
console.log(`[handleSelectTask][Action] Task selected context={{'taskId': '${selectedTaskId}'}}`);
|
||||||
}
|
}
|
||||||
// [/DEF:handleSelectTask:Function]
|
// [/DEF:handleSelectTask:Function]
|
||||||
|
|
||||||
// [DEF:handleRunBackup:Function]
|
// [DEF:handleRunBackup:Function]
|
||||||
/* @PURPOSE: Triggers a manual backup task for the selected environment.
|
/**
|
||||||
@PRE: selectedEnvId must not be empty.
|
* @purpose Triggers a manual backup task for the selected environment.
|
||||||
@POST: Backup task is created and task list is refreshed.
|
* @pre selectedEnvId must not be empty.
|
||||||
*/
|
* @post Backup task is created and task list is refreshed.
|
||||||
|
*/
|
||||||
async function handleRunBackup() {
|
async function handleRunBackup() {
|
||||||
if (!selectedEnvId) {
|
if (!selectedEnvId) {
|
||||||
addToast('Please select an environment', 'error');
|
addToast('Please select an environment', 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`[handleRunBackup][Action] Starting backup for env context={{'envId': '${selectedEnvId}'}}`);
|
||||||
try {
|
try {
|
||||||
const task = await createTask('superset-backup', { environment_id: selectedEnvId });
|
const task = await createTask('superset-backup', { environment_id: selectedEnvId });
|
||||||
addToast('Backup task started', 'success');
|
addToast('Backup task started', 'success');
|
||||||
showBackupModal = false;
|
showBackupModal = false;
|
||||||
selectedTaskId = task.id;
|
selectedTaskId = task.id;
|
||||||
await refreshTasks();
|
await refreshTasks();
|
||||||
|
console.log(`[handleRunBackup][Coherence:OK] Backup task created context={{'taskId': '${task.id}'}}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to start backup:', error);
|
console.error(`[handleRunBackup][Coherence:Failed] Failed to start backup context={{'error': '${error.message}'}}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// [/DEF:handleRunBackup:Function]
|
// [/DEF:handleRunBackup:Function]
|
||||||
@@ -117,7 +134,11 @@
|
|||||||
<h2 class="text-lg font-semibold mb-3 text-gray-700">Task Details & Logs</h2>
|
<h2 class="text-lg font-semibold mb-3 text-gray-700">Task Details & Logs</h2>
|
||||||
{#if selectedTaskId}
|
{#if selectedTaskId}
|
||||||
<div class="bg-white rounded-lg shadow-lg h-[600px] flex flex-col">
|
<div class="bg-white rounded-lg shadow-lg h-[600px] flex flex-col">
|
||||||
<TaskLogViewer taskId={selectedTaskId} />
|
<TaskLogViewer
|
||||||
|
taskId={selectedTaskId}
|
||||||
|
taskStatus={tasks.find(t => t.id === selectedTaskId)?.status}
|
||||||
|
inline={true}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="bg-gray-50 border-2 border-dashed border-gray-300 rounded-lg h-[600px] flex items-center justify-center text-gray-500">
|
<div class="bg-gray-50 border-2 border-dashed border-gray-300 rounded-lg h-[600px] flex items-center justify-center text-gray-500">
|
||||||
@@ -161,4 +182,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<!-- [/DEF:TaskManagementPage:Component] -->
|
||||||
Reference in New Issue
Block a user