0.34.1: populate the log container filter (parse compose prefix)
The whole-stack log stream sends service:null on every line, so the container filter dropdown only ever showed "All containers". docker compose logs already prefixes each line with the container name (and an RFC3339 timestamp via --timestamps); parse that prefix client-side to recover the container, populate the filter, and render time + container + message separately (cleaner than the raw prefixed line). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
efb468560e
commit
844655d1c8
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "stackpilot-frontend",
|
||||
"private": true,
|
||||
"version": "0.34.0",
|
||||
"version": "0.34.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -48,7 +48,44 @@ const levelStyles: Record<Level, { text: string; row: string }> = {
|
||||
debug: { text: "text-slate-400", row: "border-l-2 border-transparent" },
|
||||
};
|
||||
|
||||
type LogLine = { service: string | null; line: string; level: Level };
|
||||
// `docker compose logs` prefixes every line with the container name (padded
|
||||
// for alignment) then `| `; with --timestamps an RFC3339 stamp follows. The
|
||||
// whole-stack stream sends service:null, so we recover the container here.
|
||||
const prefixRe = /^(\S+)\s+\|\s?(.*)$/s;
|
||||
const tsRe = /^(\d{4}-\d{2}-\d{2}T[\d:.]+Z?)\s(.*)$/s;
|
||||
|
||||
function parseLine(
|
||||
rawService: string | null,
|
||||
raw: string
|
||||
): { service: string | null; time: string | null; msg: string } {
|
||||
let service = rawService;
|
||||
let body = raw;
|
||||
if (!service) {
|
||||
const m = prefixRe.exec(raw);
|
||||
if (m) {
|
||||
service = m[1];
|
||||
body = m[2];
|
||||
}
|
||||
}
|
||||
let time: string | null = null;
|
||||
const t = tsRe.exec(body);
|
||||
if (t) {
|
||||
const d = new Date(t[1]);
|
||||
if (!Number.isNaN(d.getTime())) {
|
||||
time = d.toLocaleTimeString();
|
||||
body = t[2];
|
||||
}
|
||||
}
|
||||
return { service, time, msg: body };
|
||||
}
|
||||
|
||||
type LogLine = {
|
||||
service: string | null;
|
||||
time: string | null;
|
||||
msg: string;
|
||||
raw: string;
|
||||
level: Level;
|
||||
};
|
||||
|
||||
export function LogViewer({ stackId, agentId }: { stackId: string; agentId?: number }) {
|
||||
const [lines, setLines] = useState<LogLine[]>([]);
|
||||
@@ -83,10 +120,11 @@ export function LogViewer({ stackId, agentId }: { stackId: string; agentId?: num
|
||||
try {
|
||||
const msg = JSON.parse(ev.data);
|
||||
if (msg.type === "log") {
|
||||
const { service, time, msg: body } = parseLine(msg.service, msg.line);
|
||||
setLines((prev) => {
|
||||
const next = [
|
||||
...prev,
|
||||
{ service: msg.service, line: msg.line, level: levelFor(msg.line) },
|
||||
{ service, time, msg: body, raw: msg.line, level: levelFor(body) },
|
||||
];
|
||||
return next.length > MAX_LINES ? next.slice(-MAX_LINES) : next;
|
||||
});
|
||||
@@ -112,7 +150,7 @@ export function LogViewer({ stackId, agentId }: { stackId: string; agentId?: num
|
||||
const q = search.trim().toLowerCase();
|
||||
return lines.filter((l) => {
|
||||
if (serviceFilter !== "__all__" && l.service !== serviceFilter) return false;
|
||||
if (q && !l.line.toLowerCase().includes(q)) return false;
|
||||
if (q && !l.msg.toLowerCase().includes(q)) return false;
|
||||
return true;
|
||||
});
|
||||
}, [lines, serviceFilter, search]);
|
||||
@@ -124,7 +162,7 @@ export function LogViewer({ stackId, agentId }: { stackId: string; agentId?: num
|
||||
}, [filtered, autoScroll]);
|
||||
|
||||
const download = () => {
|
||||
const blob = new Blob([filtered.map((l) => l.line).join("\n")], {
|
||||
const blob = new Blob([filtered.map((l) => l.raw).join("\n")], {
|
||||
type: "text/plain",
|
||||
});
|
||||
const a = document.createElement("a");
|
||||
@@ -212,10 +250,13 @@ export function LogViewer({ stackId, agentId }: { stackId: string; agentId?: num
|
||||
key={i}
|
||||
className={`whitespace-pre-wrap break-all py-0.5 pl-2 ${style.row}`}
|
||||
>
|
||||
{l.time && (
|
||||
<span className="mr-2 text-slate-500">{l.time}</span>
|
||||
)}
|
||||
{l.service && (
|
||||
<span className={`mr-2 ${colorFor(l.service)}`}>{l.service}</span>
|
||||
)}
|
||||
<span className={style.text}>{l.line}</span>
|
||||
<span className={style.text}>{l.msg}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user