feat: add projects groups
This commit is contained in:
@ -4,25 +4,42 @@
|
||||
|
||||
let {
|
||||
agentId,
|
||||
containerId,
|
||||
containerName,
|
||||
containerId = '',
|
||||
containerName = '',
|
||||
containers = [] as Array<{id: string; name: string}>,
|
||||
projectName = '',
|
||||
onClose,
|
||||
}: {
|
||||
agentId: string;
|
||||
containerId: string;
|
||||
containerName: string;
|
||||
containerId?: string;
|
||||
containerName?: string;
|
||||
containers?: Array<{id: string; name: string}>;
|
||||
projectName?: string;
|
||||
onClose: () => void;
|
||||
} = $props();
|
||||
|
||||
interface LogLine {
|
||||
stream: string;
|
||||
line: string;
|
||||
prefix?: string;
|
||||
prefixColor?: string;
|
||||
}
|
||||
|
||||
const PREFIX_COLORS = [
|
||||
'text-cyan-400',
|
||||
'text-green-400',
|
||||
'text-yellow-400',
|
||||
'text-orange-400',
|
||||
'text-pink-400',
|
||||
'text-violet-400',
|
||||
'text-sky-400',
|
||||
'text-lime-400',
|
||||
];
|
||||
|
||||
let lines = $state<LogLine[]>([]);
|
||||
let autoScroll = $state(true);
|
||||
let logEl = $state<HTMLElement | null>(null);
|
||||
let disconnect: (() => void) | null = null;
|
||||
let disconnects: Array<() => void> = [];
|
||||
|
||||
function stripAnsi(str: string): string {
|
||||
// eslint-disable-next-line no-control-regex
|
||||
@ -41,17 +58,38 @@
|
||||
autoScroll = atBottom;
|
||||
}
|
||||
|
||||
const isMulti = $derived(containers.length > 0);
|
||||
const title = $derived(isMulti ? projectName : containerName);
|
||||
|
||||
onMount(() => {
|
||||
disconnect = connectLogs(agentId, containerId, async (msg) => {
|
||||
if (msg.line) {
|
||||
lines.push({ stream: msg.stream, line: stripAnsi(msg.line) });
|
||||
if (lines.length > 2000) lines = lines.slice(-2000);
|
||||
await scrollToBottom();
|
||||
}
|
||||
});
|
||||
if (isMulti) {
|
||||
containers.forEach((c, idx) => {
|
||||
const prefix = c.name;
|
||||
const prefixColor = PREFIX_COLORS[idx % PREFIX_COLORS.length];
|
||||
const dc = connectLogs(agentId, c.id, async (msg) => {
|
||||
if (msg.line) {
|
||||
lines.push({ stream: msg.stream, line: stripAnsi(msg.line), prefix, prefixColor });
|
||||
if (lines.length > 2000) lines = lines.slice(-2000);
|
||||
await scrollToBottom();
|
||||
}
|
||||
});
|
||||
disconnects.push(dc);
|
||||
});
|
||||
} else {
|
||||
const dc = connectLogs(agentId, containerId, async (msg) => {
|
||||
if (msg.line) {
|
||||
lines.push({ stream: msg.stream, line: stripAnsi(msg.line) });
|
||||
if (lines.length > 2000) lines = lines.slice(-2000);
|
||||
await scrollToBottom();
|
||||
}
|
||||
});
|
||||
disconnects.push(dc);
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(() => disconnect?.());
|
||||
onDestroy(() => {
|
||||
for (const dc of disconnects) dc();
|
||||
});
|
||||
|
||||
function handleKey(e: KeyboardEvent) {
|
||||
if (e.key === "Escape") onClose();
|
||||
@ -65,7 +103,7 @@
|
||||
class="fixed inset-0 z-50 flex flex-col bg-black/70 backdrop-blur-sm"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Logs — {containerName}"
|
||||
aria-label="Logs — {title}"
|
||||
>
|
||||
<!-- Modal panel -->
|
||||
<div class="flex flex-col m-4 md:m-8 flex-1 min-h-0 card overflow-hidden">
|
||||
@ -73,7 +111,7 @@
|
||||
<!-- Header -->
|
||||
<div class="flex items-center gap-3 px-4 py-3 border-b border-white/[0.07] shrink-0">
|
||||
<span class="w-2 h-2 rounded-full bg-signal-green animate-pulse"></span>
|
||||
<span class="font-mono text-sm font-semibold text-slate-200">{containerName}</span>
|
||||
<span class="font-mono text-sm font-semibold text-slate-200">{title}</span>
|
||||
<span class="text-xs text-slate-600 ml-1">logs</span>
|
||||
|
||||
<div class="ml-auto flex items-center gap-3">
|
||||
@ -109,9 +147,12 @@
|
||||
{#if lines.length === 0}
|
||||
<p class="text-slate-700 italic">En attente des logs…</p>
|
||||
{:else}
|
||||
{#each lines as { stream, line }, i (i)}
|
||||
<div class="whitespace-pre-wrap break-all {stream === 'stderr' ? 'text-signal-red/80' : 'text-slate-300'}">
|
||||
{line}
|
||||
{#each lines as { stream, line, prefix, prefixColor }, i (i)}
|
||||
<div class="whitespace-pre-wrap break-all flex {stream === 'stderr' ? 'text-signal-red/80' : 'text-slate-300'}">
|
||||
{#if prefix}
|
||||
<span class="{prefixColor} mr-1 opacity-80 shrink-0">[{prefix}]</span>
|
||||
{/if}
|
||||
<span>{line}</span>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user