Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
"preview": "vite preview",
"typecheck": "tsc -b",
"test": "node --test 'tests/*.test.ts'"
},
"dependencies": {
"clsx": "^2.1.1",
Expand Down
2 changes: 2 additions & 0 deletions dashboard/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import Credentials from "./pages/Credentials";
import Pools from "./pages/Pools";
import Settings from "./pages/Settings";
import MobileSync from "./pages/MobileSync";
import Usage from "./pages/Usage";

export default function App() {
return (
Expand All @@ -34,6 +35,7 @@ export default function App() {
<Route path="triggers" element={<Triggers />} />
<Route path="operations" element={<Operations />} />
<Route path="sessions" element={<Sessions />} />
<Route path="usage" element={<Usage />} />
<Route path="plugins" element={<Plugins />} />
<Route path="credentials" element={<Credentials />} />
<Route path="pools" element={<Pools />} />
Expand Down
52 changes: 52 additions & 0 deletions dashboard/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,58 @@ export async function listSequences(
return Array.isArray(raw) ? raw : raw.items;
}

export interface CreateSequenceResponse {
id: string;
warnings?: string[];
}

/**
* Register a new sequence definition. The body is the full sequence JSON
* (the server validates structure and returns lint warnings, if any).
*/
export function createSequence(
body: Record<string, unknown>,
signal?: AbortSignal,
): Promise<CreateSequenceResponse> {
return mutate("/sequences", "POST", body, undefined, signal);
}

// ─── Usage / cost ────────────────────────────────────────────────────────────

export interface UsageEntry {
kind: string;
model: string;
events: number;
input_tokens: number;
output_tokens: number;
/** Estimated list-price cost in USD; null when the model has no pricing-table entry. */
cost_usd: number | null;
}

export interface UsageResponse {
tenant: string;
start: string;
end: string;
usage: UsageEntry[];
/** Window-wide total over the known-model entries. */
total_cost_usd: number;
/** Always true — costs are computed from static list prices, not invoices. */
cost_is_estimate: boolean;
}

export interface GetUsageParams {
/** Honored only for unscoped/admin callers; scoped keys are locked to their tenant. */
tenant?: string;
/** Window start (RFC 3339). Defaults server-side to 30 days before `end`. */
start?: string;
/** Window end (RFC 3339). Defaults server-side to now. */
end?: string;
}

export function getUsage(params?: GetUsageParams, signal?: AbortSignal): Promise<UsageResponse> {
return request("/usage", params as Record<string, string | undefined>, signal);
}

// ─── Operations: DLQ / circuit breakers / cluster ────────────────────────────

export interface ListDlqParams {
Expand Down
2 changes: 2 additions & 0 deletions dashboard/src/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
IconKey,
IconDatabase,
IconPhone,
IconCost,
} from "./ui/Icons";

const CONN_TONE = {
Expand All @@ -42,6 +43,7 @@ const NAV = [
{ to: "/cron", label: "Cron", icon: IconClock },
{ to: "/triggers", label: "Triggers", icon: IconZap },
{ to: "/sessions", label: "Sessions", icon: IconSession },
{ to: "/usage", label: "Usage", icon: IconCost },
{ to: "/plugins", label: "Plugins", icon: IconPlugin },
{ to: "/credentials", label: "Credentials", icon: IconKey },
{ to: "/pools", label: "Pools", icon: IconDatabase },
Expand Down
8 changes: 8 additions & 0 deletions dashboard/src/components/ui/Icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,14 @@ export const IconDatabase = (p: P) => (
</SVG>
);

export const IconCost = (p: P) => (
<SVG {...p}>
<circle cx="12" cy="12" r="9" />
<path d="M12 6.5v11" />
<path d="M14.5 8.5c-.5-.8-1.4-1.2-2.5-1.2-1.5 0-2.7.8-2.7 2.1 0 2.7 5.4 1.5 5.4 4.2 0 1.3-1.2 2.1-2.7 2.1-1.1 0-2-.4-2.5-1.2" />
</SVG>
);

export const IconPhone = (p: P) => (
<SVG {...p}>
<rect x="7" y="2" width="10" height="20" rx="2" />
Expand Down
10 changes: 10 additions & 0 deletions dashboard/src/lib/fmt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,13 @@ export function fmtDelta(n: number): string {
const sign = n > 0 ? "+" : "−";
return `${sign}${fmtCount(Math.abs(n))}`;
}

/**
* USD amount with adaptive precision: 2 decimals at/above $1 ("$12.35"),
* 4 below ("$0.0123") so sub-cent LLM costs stay legible. `null`/`undefined`
* (unknown model — no entry in the pricing table) renders as an em-dash.
*/
export function fmtUsd(n: number | null | undefined): string {
if (n === null || n === undefined || !Number.isFinite(n)) return "—";
return `$${n.toFixed(Math.abs(n) >= 1 ? 2 : 4)}`;
}
Loading
Loading