mirror of
https://github.com/khoaliber/dockhand.git
synced 2026-03-02 21:19:05 +00:00
95 lines
2.9 KiB
Svelte
95 lines
2.9 KiB
Svelte
<script lang="ts">
|
|
import { Sun, Moon } from 'lucide-svelte';
|
|
|
|
interface Props {
|
|
logs: string | null;
|
|
darkMode?: boolean;
|
|
onToggleTheme?: () => void;
|
|
}
|
|
|
|
let { logs, darkMode = true, onToggleTheme }: Props = $props();
|
|
|
|
// Parse log lines with timestamp and content
|
|
function parseLogLine(line: string): { timestamp: string; content: string; type: 'trivy' | 'grype' | 'error' | 'default' } {
|
|
const content = line.replace(/^\[[\d\-T:.Z]+\]\s*/, '');
|
|
const timestamp = line.match(/^\[([\d\-T:.Z]+)\]/)?.[1] || '';
|
|
|
|
let type: 'trivy' | 'grype' | 'error' | 'default' = 'default';
|
|
if (content.startsWith('[trivy]')) {
|
|
type = 'trivy';
|
|
} else if (content.startsWith('[grype]')) {
|
|
type = 'grype';
|
|
} else if (content.toLowerCase().includes('error')) {
|
|
type = 'error';
|
|
}
|
|
|
|
return { timestamp, content, type };
|
|
}
|
|
|
|
function getTypeBadge(type: 'trivy' | 'grype' | 'error' | 'default'): { label: string; class: string } {
|
|
switch (type) {
|
|
case 'trivy':
|
|
return { label: 'trivy', class: 'bg-teal-500 text-white' };
|
|
case 'grype':
|
|
return { label: 'grype', class: 'bg-violet-500 text-white' };
|
|
case 'error':
|
|
return { label: 'error', class: 'bg-red-500 text-white' };
|
|
default:
|
|
return { label: 'dockhand', class: 'bg-slate-500 text-white' };
|
|
}
|
|
}
|
|
|
|
function cleanContent(content: string, type: 'trivy' | 'grype' | 'error' | 'default'): string {
|
|
return content.replace(/^\[(trivy|grype|scan)\]\s*/i, '');
|
|
}
|
|
|
|
function formatTimestamp(timestamp: string): string {
|
|
return timestamp.split('T')[1]?.replace('Z', '') || timestamp;
|
|
}
|
|
</script>
|
|
|
|
<div class="flex-1 flex flex-col min-h-0">
|
|
<div class="flex items-center justify-between text-xs text-muted-foreground mb-1 shrink-0">
|
|
<span>Logs</span>
|
|
{#if onToggleTheme}
|
|
<button
|
|
type="button"
|
|
onclick={onToggleTheme}
|
|
class="p-1 rounded hover:bg-muted transition-colors"
|
|
title="Toggle log theme"
|
|
>
|
|
{#if darkMode}
|
|
<Sun class="w-3.5 h-3.5" />
|
|
{:else}
|
|
<Moon class="w-3.5 h-3.5" />
|
|
{/if}
|
|
</button>
|
|
{/if}
|
|
</div>
|
|
<div
|
|
class="{darkMode ? 'bg-zinc-950 text-zinc-300' : 'bg-zinc-100 text-zinc-700'} rounded p-3 font-mono text-xs flex-1 overflow-auto"
|
|
>
|
|
{#if logs}
|
|
{#each logs.split('\n') as line}
|
|
{@const parsed = parseLogLine(line)}
|
|
{@const badge = getTypeBadge(parsed.type)}
|
|
<div class="flex items-start gap-1.5 leading-relaxed">
|
|
<span
|
|
class="inline-flex items-center justify-center w-12 px-1 rounded text-[8px] font-medium {badge.class} shadow-[0_1px_1px_rgba(0,0,0,0.2)] shrink-0 mt-[3px]"
|
|
>
|
|
{badge.label}
|
|
</span>
|
|
{#if parsed.timestamp}
|
|
<span class="{darkMode ? 'text-zinc-500' : 'text-zinc-400'} shrink-0">
|
|
{formatTimestamp(parsed.timestamp)}
|
|
</span>
|
|
{/if}
|
|
<span class="break-all">{cleanContent(parsed.content, parsed.type)}</span>
|
|
</div>
|
|
{/each}
|
|
{:else}
|
|
<span class="text-muted-foreground">No logs available</span>
|
|
{/if}
|
|
</div>
|
|
</div>
|