Files
ss-tools/frontend/src/components/tasks/LogEntryRow.svelte
2026-02-19 18:24:36 +03:00

101 lines
3.1 KiB
Svelte

<!-- [DEF:LogEntryRow:Component] -->
<!--
@TIER: STANDARD
@SEMANTICS: log, entry, row, ui
@PURPOSE: Renders a single log entry with stacked layout optimized for narrow drawer panels.
@LAYER: UI
@UX_STATE: Idle -> Displays log entry with color-coded level and source badges.
-->
<script>
/** @type {Object} log - The log entry object */
let { log, showSource = true } = $props();
// [DEF:formatTime:Function]
/** @PURPOSE Format ISO timestamp to HH:MM:SS */
function formatTime(timestamp) {
if (!timestamp) return "";
const date = new Date(timestamp);
return date.toLocaleTimeString("en-US", {
hour12: false,
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
});
}
// [/DEF:formatTime:Function]
let formattedTime = $derived(formatTime(log.timestamp));
const levelStyles = {
DEBUG: "text-log-debug bg-log-debug/15",
INFO: "text-log-info bg-log-info/10",
WARNING: "text-log-warning bg-log-warning/10",
ERROR: "text-log-error bg-log-error/10",
};
const sourceStyles = {
plugin: "bg-source-plugin/10 text-source-plugin",
"superset-api": "bg-source-api/10 text-source-api",
superset_api: "bg-source-api/10 text-source-api",
git: "bg-source-git/10 text-source-git",
system: "bg-source-system/10 text-source-system",
};
let levelClass = $derived(
levelStyles[log.level?.toUpperCase()] || levelStyles.INFO,
);
let sourceClass = $derived(
sourceStyles[log.source?.toLowerCase()] ||
"bg-log-debug/15 text-terminal-text-subtle",
);
let hasProgress = $derived(log.metadata?.progress !== undefined);
let progressPercent = $derived(log.metadata?.progress || 0);
</script>
<div
class="py-2 px-3 border-b border-terminal-surface/60 transition-colors hover:bg-terminal-surface/50"
>
<!-- Meta line: time + level + source -->
<div class="flex items-center gap-2 mb-1">
<span class="font-mono text-[0.6875rem] text-terminal-text-muted shrink-0"
>{formattedTime}</span
>
<span
class="font-mono font-semibold uppercase text-[0.625rem] px-1.5 py-px rounded-sm tracking-wider shrink-0 {levelClass}"
>{log.level || "INFO"}</span
>
{#if showSource && log.source}
<span
class="text-[0.625rem] px-1.5 py-px rounded-sm shrink-0 {sourceClass}"
>{log.source}</span
>
{/if}
</div>
<!-- Message -->
<div
class="font-mono text-[0.8125rem] leading-relaxed text-terminal-text break-words whitespace-pre-wrap"
>
{log.message}
</div>
<!-- Progress bar (if applicable) -->
{#if hasProgress}
<div class="flex items-center gap-2 mt-1.5">
<div
class="flex-1 h-1.5 bg-terminal-surface rounded-full overflow-hidden"
>
<div
class="h-full bg-gradient-to-r from-primary to-purple-500 rounded-full transition-[width] duration-300 ease-out"
style="width: {progressPercent}%"
></div>
</div>
<span class="font-mono text-[0.625rem] text-terminal-text-subtle shrink-0"
>{progressPercent.toFixed(0)}%</span
>
</div>
{/if}
</div>
<!-- [/DEF:LogEntryRow:Component] -->