chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
import React from "react";
|
||||
import { Box, Text } from "ink";
|
||||
import { HeaderSwr } from "../../tui-components/HeaderSwr.jsx";
|
||||
import { DataTable } from "../../tui-components/DataTable.jsx";
|
||||
|
||||
const SCHEMA = [
|
||||
{ key: "name", header: "Name", width: 20 },
|
||||
{ key: "strategy", header: "Strategy", width: 14 },
|
||||
{ key: "targets", header: "Targets", width: 8 },
|
||||
{ key: "enabled", header: "Enabled", width: 8, formatter: (v) => (v ? "✓" : "✗") },
|
||||
];
|
||||
|
||||
export default function Combos({ baseUrl, apiKey }) {
|
||||
const fetcher = React.useCallback(async () => {
|
||||
const res = await fetch(`${baseUrl}/api/combos`, {
|
||||
headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : {},
|
||||
});
|
||||
return res.ok ? res.json() : [];
|
||||
}, [baseUrl, apiKey]);
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<HeaderSwr
|
||||
fetcher={fetcher}
|
||||
interval={10000}
|
||||
render={(data) => {
|
||||
const rows = Array.isArray(data) ? data : (data?.combos ?? []);
|
||||
return (
|
||||
<DataTable
|
||||
rows={rows.map((c) => ({
|
||||
name: c.name,
|
||||
strategy: c.strategy,
|
||||
targets: c.targets?.length ?? 0,
|
||||
enabled: c.enabled !== false,
|
||||
}))}
|
||||
schema={SCHEMA}
|
||||
selectable
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Box marginTop={1}>
|
||||
<Text dimColor>[↑↓] select [Enter] details [r] refresh</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import React from "react";
|
||||
import { Box, Text } from "ink";
|
||||
import { HeaderSwr } from "../../tui-components/HeaderSwr.jsx";
|
||||
import { Sparkline } from "../../tui-components/Sparkline.jsx";
|
||||
|
||||
export default function Cost({ baseUrl, apiKey }) {
|
||||
const fetcher = React.useCallback(async () => {
|
||||
const res = await fetch(`${baseUrl}/api/usage?period=7d&breakdown=provider`, {
|
||||
headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : {},
|
||||
});
|
||||
return res.ok ? res.json() : null;
|
||||
}, [baseUrl, apiKey]);
|
||||
|
||||
return (
|
||||
<HeaderSwr
|
||||
fetcher={fetcher}
|
||||
interval={30000}
|
||||
render={(data) => {
|
||||
const byProvider = data?.byProvider ?? {};
|
||||
const dailyCosts = data?.dailyCosts ?? [];
|
||||
const totalCost = data?.totalCost ?? 0;
|
||||
return (
|
||||
<Box flexDirection="column" gap={1}>
|
||||
<Box gap={4}>
|
||||
<Box flexDirection="column">
|
||||
<Text bold>Total (7d)</Text>
|
||||
<Text color="yellow">${totalCost.toFixed(4)}</Text>
|
||||
</Box>
|
||||
<Box flexDirection="column">
|
||||
<Text bold>Daily Trend</Text>
|
||||
<Sparkline data={dailyCosts} width={14} color="yellow" />
|
||||
</Box>
|
||||
</Box>
|
||||
{Object.keys(byProvider).length > 0 && (
|
||||
<Box flexDirection="column">
|
||||
<Text bold>By Provider</Text>
|
||||
{Object.entries(byProvider).map(([provider, cost]) => (
|
||||
<Box key={provider} gap={2}>
|
||||
<Box width={16}>
|
||||
<Text>{provider}</Text>
|
||||
</Box>
|
||||
<Text color="yellow">${Number(cost).toFixed(4)}</Text>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import React from "react";
|
||||
import { Box, Text } from "ink";
|
||||
import { HeaderSwr } from "../../tui-components/HeaderSwr.jsx";
|
||||
import { StatusBadge } from "../../tui-components/StatusBadge.jsx";
|
||||
|
||||
export default function Health({ baseUrl, apiKey }) {
|
||||
const fetcher = React.useCallback(async () => {
|
||||
const res = await fetch(`${baseUrl}/api/monitoring/health?detail=true`, {
|
||||
headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : {},
|
||||
});
|
||||
return res.ok ? res.json() : null;
|
||||
}, [baseUrl, apiKey]);
|
||||
|
||||
return (
|
||||
<HeaderSwr
|
||||
fetcher={fetcher}
|
||||
interval={5000}
|
||||
render={(data) => {
|
||||
const components = data?.components ?? {};
|
||||
const alerts = data?.alerts ?? [];
|
||||
return (
|
||||
<Box flexDirection="column" gap={1}>
|
||||
<Box flexDirection="column">
|
||||
<Text bold>Components</Text>
|
||||
{Object.entries(components).map(([name, status]) => (
|
||||
<Box key={name} gap={2}>
|
||||
<Box width={20}>
|
||||
<Text>{name}</Text>
|
||||
</Box>
|
||||
<StatusBadge
|
||||
status={typeof status === "string" ? status : (status?.status ?? "unknown")}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
{alerts.length > 0 && (
|
||||
<Box flexDirection="column">
|
||||
<Text bold color="red">
|
||||
Alerts ({alerts.length})
|
||||
</Text>
|
||||
{alerts.map((a, i) => (
|
||||
<Text key={i} color="red">
|
||||
⚠ {a.message ?? a}
|
||||
</Text>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import React from "react";
|
||||
import { Box, Text } from "ink";
|
||||
import { HeaderSwr } from "../../tui-components/HeaderSwr.jsx";
|
||||
import { DataTable } from "../../tui-components/DataTable.jsx";
|
||||
import { KeyMaskedDisplay } from "../../tui-components/KeyMaskedDisplay.jsx";
|
||||
|
||||
const SCHEMA = [
|
||||
{ key: "label", header: "Label", width: 20 },
|
||||
{ key: "key", header: "Key", width: 24 },
|
||||
{ key: "scope", header: "Scope", width: 12 },
|
||||
{ key: "active", header: "Active", width: 8, formatter: (v) => (v ? "✓" : "✗") },
|
||||
];
|
||||
|
||||
export default function Keys({ baseUrl, apiKey }) {
|
||||
const fetcher = React.useCallback(async () => {
|
||||
const res = await fetch(`${baseUrl}/api/v1/registered-keys`, {
|
||||
headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : {},
|
||||
});
|
||||
return res.ok ? res.json() : [];
|
||||
}, [baseUrl, apiKey]);
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<HeaderSwr
|
||||
fetcher={fetcher}
|
||||
interval={15000}
|
||||
render={(data) => {
|
||||
const rows = Array.isArray(data) ? data : (data?.keys ?? []);
|
||||
return (
|
||||
<DataTable
|
||||
rows={rows.map((k) => ({
|
||||
label: k.label ?? k.id,
|
||||
key: k.key ? `${k.key.slice(0, 6)}***${k.key.slice(-4)}` : "***",
|
||||
scope: k.scope ?? "all",
|
||||
active: k.active !== false,
|
||||
}))}
|
||||
schema={SCHEMA}
|
||||
selectable
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Box marginTop={1}>
|
||||
<Text dimColor>[↑↓] select [a] add [r] revoke [R] reveal [c] copy</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Box, Text, useInput } from "ink";
|
||||
|
||||
const MAX_LINES = 40;
|
||||
|
||||
export default function Logs({ baseUrl, apiKey }) {
|
||||
const [lines, setLines] = useState([]);
|
||||
const [paused, setPaused] = useState(false);
|
||||
|
||||
useInput((input, key) => {
|
||||
if (input === "p") setPaused((v) => !v);
|
||||
if (input === "c") setLines([]);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (paused) return;
|
||||
let cancelled = false;
|
||||
const headers = { Accept: "text/event-stream" };
|
||||
if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
|
||||
|
||||
fetch(`${baseUrl}/api/v1/logs/stream?limit=50`, { headers })
|
||||
.then(async (res) => {
|
||||
if (!res.ok || !res.body) return;
|
||||
const reader = res.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
while (!cancelled) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
const text = decoder.decode(value);
|
||||
for (const line of text.split("\n")) {
|
||||
if (line.startsWith("data:")) {
|
||||
const payload = line.slice(5).trim();
|
||||
if (payload && !cancelled) {
|
||||
setLines((prev) => [...prev.slice(-(MAX_LINES - 1)), payload]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [baseUrl, apiKey, paused]);
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<Box flexDirection="column" flexGrow={1}>
|
||||
{lines.length === 0 && <Text dimColor>Waiting for log events…</Text>}
|
||||
{lines.map((line, i) => (
|
||||
<Text key={i}>{line}</Text>
|
||||
))}
|
||||
</Box>
|
||||
<Box marginTop={1}>
|
||||
<Text dimColor>{paused ? "[PAUSED]" : "[LIVE]"} [p] pause/resume [c] clear</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import React from "react";
|
||||
import { Box, Text } from "ink";
|
||||
import { HeaderSwr } from "../../tui-components/HeaderSwr.jsx";
|
||||
import { Sparkline } from "../../tui-components/Sparkline.jsx";
|
||||
import { StatusBadge } from "../../tui-components/StatusBadge.jsx";
|
||||
|
||||
function formatUptime(seconds) {
|
||||
const d = Math.floor(seconds / 86400);
|
||||
const h = Math.floor((seconds % 86400) / 3600);
|
||||
const m = Math.floor((seconds % 3600) / 60);
|
||||
if (d > 0) return `${d}d ${h}h`;
|
||||
if (h > 0) return `${h}h ${m}m`;
|
||||
return `${m}m`;
|
||||
}
|
||||
|
||||
function OverviewContent({ data }) {
|
||||
const reqs = data?.requests24h ?? 0;
|
||||
const cost = (data?.cost24h ?? 0).toFixed(4);
|
||||
const uptimeSecs = data?.uptimeSeconds ?? 0;
|
||||
const sparkData = data?.requestsSparkline ?? [];
|
||||
|
||||
return (
|
||||
<Box flexDirection="column" gap={1}>
|
||||
<Box gap={4}>
|
||||
<Box flexDirection="column">
|
||||
<Text bold>Server</Text>
|
||||
<StatusBadge status={data?.status ?? "unknown"} />
|
||||
</Box>
|
||||
<Box flexDirection="column">
|
||||
<Text bold>Uptime</Text>
|
||||
<Text>{formatUptime(uptimeSecs)}</Text>
|
||||
</Box>
|
||||
<Box flexDirection="column">
|
||||
<Text bold>Requests/24h</Text>
|
||||
<Box>
|
||||
<Text>{reqs.toLocaleString()} </Text>
|
||||
<Sparkline data={sparkData} width={12} />
|
||||
</Box>
|
||||
</Box>
|
||||
<Box flexDirection="column">
|
||||
<Text bold>Cost/24h</Text>
|
||||
<Text color="yellow">${cost}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
{data?.recentActivity?.length > 0 && (
|
||||
<Box flexDirection="column" marginTop={1}>
|
||||
<Text bold dimColor>
|
||||
Recent Activity
|
||||
</Text>
|
||||
{data.recentActivity.slice(0, 5).map((r, i) => (
|
||||
<Box key={i} gap={2}>
|
||||
<Text dimColor>{r.time}</Text>
|
||||
<Text color={r.status < 400 ? "green" : "red"}>{r.status < 400 ? "✓" : "✗"}</Text>
|
||||
<Text>{r.path}</Text>
|
||||
<Text dimColor>{r.model}</Text>
|
||||
<Text dimColor>{r.duration}ms</Text>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Overview({ baseUrl, apiKey }) {
|
||||
const fetcher = React.useCallback(async () => {
|
||||
const res = await fetch(`${baseUrl}/api/monitoring/health`, {
|
||||
headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : {},
|
||||
});
|
||||
return res.ok ? res.json() : null;
|
||||
}, [baseUrl, apiKey]);
|
||||
|
||||
return (
|
||||
<HeaderSwr
|
||||
fetcher={fetcher}
|
||||
interval={5000}
|
||||
render={(data) => <OverviewContent data={data} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import React from "react";
|
||||
import { Box, Text } from "ink";
|
||||
import { HeaderSwr } from "../../tui-components/HeaderSwr.jsx";
|
||||
import { DataTable } from "../../tui-components/DataTable.jsx";
|
||||
import { StatusBadge } from "../../tui-components/StatusBadge.jsx";
|
||||
|
||||
const SCHEMA = [
|
||||
{ key: "name", header: "Provider", width: 16 },
|
||||
{ key: "status", header: "Status", width: 12, formatter: (v) => v },
|
||||
{ key: "accounts", header: "Accounts", width: 10 },
|
||||
{ key: "models", header: "Models", width: 8 },
|
||||
];
|
||||
|
||||
export default function Providers({ baseUrl, apiKey }) {
|
||||
const fetcher = React.useCallback(async () => {
|
||||
const res = await fetch(`${baseUrl}/api/providers`, {
|
||||
headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : {},
|
||||
});
|
||||
return res.ok ? res.json() : [];
|
||||
}, [baseUrl, apiKey]);
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<HeaderSwr
|
||||
fetcher={fetcher}
|
||||
interval={10000}
|
||||
render={(data) => {
|
||||
const rows = Array.isArray(data) ? data : (data?.providers ?? []);
|
||||
return (
|
||||
<DataTable
|
||||
rows={rows.map((p) => ({
|
||||
name: p.name ?? p.id,
|
||||
status: p.status ?? "unknown",
|
||||
accounts: p.accountCount ?? 0,
|
||||
models: p.modelCount ?? 0,
|
||||
}))}
|
||||
schema={SCHEMA}
|
||||
selectable
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Box marginTop={1}>
|
||||
<Text dimColor>[↑↓] select [Enter] details [t] test [r] refresh</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user