120 lines
5.4 KiB
TypeScript
120 lines
5.4 KiB
TypeScript
"use client";
|
|
|
|
import React from "react";
|
|
import { BarChart3, Cpu, Clock, DollarSign, Users, Zap } from "lucide-react";
|
|
import { Card, CardBody, CardHeader, CardTitle } from "@/components/ui/Card";
|
|
import { Badge } from "@/components/ui/Badge";
|
|
import { MetricCard } from "@/components/ui/MetricCard";
|
|
|
|
const MONTHS = ["Jul", "Aug", "Sep", "Oct", "Nov"];
|
|
const SERIES = [
|
|
{ label: "AI tokens (M)", data: [0.1, 0.8, 1.4, 2.9, 4.2], tone: "bg-violet-400" },
|
|
{ label: "Jobs executed", data: [120, 280, 412, 688, 904], tone: "bg-brand-400" },
|
|
];
|
|
|
|
function Bar({ label, values, tone }: { label: string; values: number[]; tone: string }) {
|
|
const max = Math.max(...values, 1);
|
|
return (
|
|
<div>
|
|
<div className="text-xs text-slate-500 mb-2 font-medium">{label}</div>
|
|
<div className="flex items-end gap-2 h-32">
|
|
{values.map((v, i) => (
|
|
<div key={i} className="flex-1 flex flex-col items-center gap-1">
|
|
<div
|
|
className={["w-full rounded-t-md transition-all", tone].join(" ")}
|
|
style={{ height: `${(v / max) * 100}%`, minHeight: "4px" }}
|
|
title={`${v}`}
|
|
/>
|
|
<div className="text-[10px] text-slate-500">{MONTHS[i]}</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const ROWS = [
|
|
{ name: "Tenant default", projects: 6, users: 14, jobs_7d: 412, tokens_7d: 2.1, cost_7d: 128.4 },
|
|
{ name: "Tenant acme-corp", projects: 2, users: 6, jobs_7d: 88, tokens_7d: 0.6, cost_7d: 24.1 },
|
|
{ name: "Tenant contoso-labs", projects: 3, users: 9, jobs_7d: 180, tokens_7d: 1.1, cost_7d: 52.7 },
|
|
];
|
|
|
|
export default function UsagePage() {
|
|
return (
|
|
<div className="p-6 lg:p-8 max-w-[1400px] mx-auto">
|
|
<header className="mb-6 flex flex-wrap items-start justify-between gap-3">
|
|
<div>
|
|
<h1 className="text-2xl font-semibold tracking-tight text-slate-900 flex items-center gap-2">
|
|
<BarChart3 className="w-6 h-6 text-brand-600" />
|
|
Usage
|
|
</h1>
|
|
<p className="mt-1 text-sm text-slate-500">
|
|
Tenant-level metering: AI tokens, background jobs, projects, seats and estimated cost.
|
|
</p>
|
|
</div>
|
|
<Badge tone="neutral">Monthly view</Badge>
|
|
</header>
|
|
|
|
<section className="grid grid-cols-2 md:grid-cols-5 gap-4 mb-6">
|
|
<MetricCard label="Projects" value={11} tone="brand" icon={<BarChart3 className="w-5 h-5" />} />
|
|
<MetricCard label="Active users" value={29} tone="blue" icon={<Users className="w-5 h-5" />} />
|
|
<MetricCard label="Jobs (7d)" value={680} tone="green" icon={<Clock className="w-5 h-5" />} delta={12.4} />
|
|
<MetricCard label="AI tokens (7d, M)" value={3.8} tone="violet" icon={<Zap className="w-5 h-5" />} delta={18.7} />
|
|
<MetricCard label="Est. cost (7d, $)" value={205.2} tone="amber" icon={<DollarSign className="w-5 h-5" />} delta={9.2} />
|
|
</section>
|
|
|
|
<section className="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-6">
|
|
<Card>
|
|
<CardHeader><CardTitle>Trend · AI tokens</CardTitle><Badge tone="violet">millions</Badge></CardHeader>
|
|
<CardBody>
|
|
<Bar label="" values={SERIES[0].data} tone={SERIES[0].tone} />
|
|
</CardBody>
|
|
</Card>
|
|
<Card>
|
|
<CardHeader><CardTitle>Trend · Jobs executed</CardTitle><Badge tone="brand">count</Badge></CardHeader>
|
|
<CardBody>
|
|
<Bar label="" values={SERIES[1].data} tone={SERIES[1].tone} />
|
|
</CardBody>
|
|
</Card>
|
|
</section>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Per-tenant breakdown</CardTitle>
|
|
<Badge tone="neutral">{ROWS.length} tenants</Badge>
|
|
</CardHeader>
|
|
<CardBody className="p-0">
|
|
<table className="w-full text-sm">
|
|
<thead className="bg-slate-50 text-xs uppercase tracking-wide text-slate-500 border-b border-slate-200">
|
|
<tr>
|
|
<th className="text-left px-4 py-2">Tenant</th>
|
|
<th className="text-right px-4 py-2">Projects</th>
|
|
<th className="text-right px-4 py-2">Users</th>
|
|
<th className="text-right px-4 py-2">Jobs (7d)</th>
|
|
<th className="text-right px-4 py-2">Tokens (7d, M)</th>
|
|
<th className="text-right px-4 py-2">Cost (7d)</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-slate-100">
|
|
{ROWS.map((r) => (
|
|
<tr key={r.name} className="hover:bg-slate-50">
|
|
<td className="px-4 py-2.5 font-medium text-slate-800">{r.name}</td>
|
|
<td className="px-4 py-2.5 text-right tabular-nums">{r.projects}</td>
|
|
<td className="px-4 py-2.5 text-right tabular-nums">{r.users}</td>
|
|
<td className="px-4 py-2.5 text-right tabular-nums">{r.jobs_7d}</td>
|
|
<td className="px-4 py-2.5 text-right tabular-nums">{r.tokens_7d.toFixed(2)}</td>
|
|
<td className="px-4 py-2.5 text-right tabular-nums">${r.cost_7d.toFixed(2)}</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</CardBody>
|
|
</Card>
|
|
<div className="mt-4 flex items-center justify-end gap-3 text-xs text-slate-500">
|
|
<Badge tone="neutral"><Cpu className="w-3 h-3" /> GPU-s metered</Badge>
|
|
<Badge tone="neutral"><DollarSign className="w-3 h-3" /> Cost tables configurable per-tenant</Badge>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|