docs(admin): add dashboard feature design and mockup assets

Organize admin panel feature documentation into a dedicated directory
and include an interactive HTML mockup along with a reference
screenshot for the EZ-API Control Plane Dashboard.
This commit is contained in:
zenfun
2026-01-03 17:11:02 +08:00
parent f938abbdfa
commit 8a52d58674
3 changed files with 466 additions and 0 deletions

View File

@@ -0,0 +1,302 @@
# EZ-API admin-panel Dashboard 功能文档
## 1. 文档概述
| **更新日期** | 2026-01-02 |
---
## 2. API 端点总览
| 端点 | 方法 | 用途 | 刷新策略 |
|------|------|------|----------|
| `/status` | GET | 系统健康状态 | 轮询 30s |
| `/auth/whoami` | GET | 当前用户信息 | 页面加载 |
| `/admin/realtime` | GET | 实时 QPS/RPM | 轮询 5-10s |
| `/admin/dashboard/summary` | GET | 概览指标聚合 | 时间范围变更时 |
| `/admin/alerts` | GET | 告警列表 | 轮询 60s |
| `/admin/alerts/stats` | GET | 告警统计 | 轮询 60s |
| `/admin/logs/stats` | GET | 日志统计 (单维度) | 时间范围变更时 |
| `/admin/logs/stats/traffic-chart` | GET | 流量图表 (时间×模型) | 时间范围变更时 |
---
## 3. 功能模块对接详情
### 3.1 全局导航与用户信息
| 页面元素 | 数据来源 | 字段映射 | 备注 |
|----------|----------|----------|------|
| 用户头像 | 前端本地配置 / 默认头像 | - | 后端暂无头像字段 |
| 用户名称 | 前端本地配置 / 默认文案 | - | 后端暂无姓名字段 |
| 用户邮箱 | 前端本地配置 / 隐藏 | - | 后端暂无邮箱字段 |
| 用户角色 | `GET /auth/whoami` | `type`, `role` | `type=admin|master|key``role=admin` |
| Logout | 前端动作 | N/A | 清除本地 Token重定向登录页 |
### 3.2 顶部栏状态与工具
| UI 组件 | 端点 | 字段/逻辑 |
|---------|------|-----------|
| System Status 胶囊 | `GET /status` | `status == "ok"` → 绿色 "NOMINAL";否则红色 |
| Notifications 徽章 | `GET /admin/alerts/stats` | `active > 0` → 显示红点 |
**交互说明**:
| 功能点 | 用户操作 | 前端行为 | 后端请求 | 成功反馈 | 异常处理 | 风险/边界 |
|--------|----------|----------|----------|----------|----------|-----------|
| 系统状态 | 页面加载 / 轮询 | 显示状态胶囊 | `GET /status` | OK → NOMINAL | 请求失败 → 显示 UNKNOWN 或灰色 | 高频失败时避免闪烁 |
| 通知入口 | 点击铃铛 | 跳转到告警页面或展开侧栏 | 无 | 页面切换 | 无 | 当前无专用“通知已读”接口 |
### 3.3 核心指标卡片
#### 实时指标
| 卡片名称 | 端点 | 字段 | 渲染逻辑 |
|----------|------|------|----------|
| Requests Per Minute | `GET /admin/realtime` | `rpm` | 格式化显示 (如 747,000) |
| 当前 QPS | `GET /admin/realtime` | `qps` | 小数点后 1 位 |
| 限流用户数 | `GET /admin/realtime` | `rate_limited_count` | 整数 |
#### 统计指标
| 卡片名称 | 端点 | 字段 | 渲染逻辑 |
|----------|------|------|----------|
| Active Provider Keys | `GET /admin/dashboard/summary` | `provider_keys.active` | 整数 (上游凭证) |
| Total Provider Keys | `GET /admin/dashboard/summary` | `provider_keys.total` | 整数 |
| Consumed Tokens | `GET /admin/dashboard/summary` | `tokens.total` | 格式化 (如 892k) |
| Input Tokens | `GET /admin/dashboard/summary` | `tokens.input` | - |
| Output Tokens | `GET /admin/dashboard/summary` | `tokens.output` | - |
| Error Rate | `GET /admin/dashboard/summary` | `requests.error_rate` | 百分比 (如 0.02%) |
| Total Requests | `GET /admin/dashboard/summary` | `requests.total` | 整数 |
| Failed Requests | `GET /admin/dashboard/summary` | `requests.failed` | 整数 |
> **字段说明**:
> - `provider_keys`: 上游 Provider API 密钥 (model.APIKey)
> - `keys`: Master 生成的子 Token (model.Key),用于内部监控
> - "Active Keys" UI 卡片应使用 `provider_keys.active`
**请求参数**:
| 参数 | 类型 | 说明 |
|------|------|------|
| `period` | string | 预设周期: `today`, `week`, `month`, `last7d`, `last30d`, `all` |
| `since` | int | 自定义起始时间 (Unix 秒) |
| `until` | int | 自定义结束时间 (Unix 秒) |
| `include_trends` | bool | 是否包含趋势数据 (与前一周期对比) |
**周期类型说明**:
| 周期值 | 时间窗口 | 趋势对比周期 |
|--------|----------|--------------|
| `today` | 今日 00:00 UTC → 当前 | 昨日 |
| `week` | 本周一 00:00 UTC → 当前 | 上一自然周 |
| `month` | 本月 1 日 00:00 UTC → 当前 | 上一自然月 |
| `last7d` | 当前 - 7 天 → 当前 | 14 天前 → 7 天前 |
| `last30d` | 当前 - 30 天 → 当前 | 60 天前 → 30 天前 |
| `all` | 无时间过滤 | 不支持趋势 |
**趋势数据** (当 `include_trends=true` 时返回):
```json
{
"trends": {
"requests": { "delta": 12.5, "direction": "up" },
"tokens": { "delta": -1.1, "direction": "down" },
"error_rate": { "delta": 0.0, "direction": "stable" },
"latency": { "delta": -5.2, "direction": "down" }
}
}
```
| 字段 | 说明 |
|------|------|
| `delta` | 与前一周期相比的百分比变化 (四舍五入到 1 位小数) |
| `direction` | `up` (增长 >0.5%), `down` (下降 <-0.5%), `stable`, `new` (无基准数据) |
**交互说明**:
| 功能点 | 用户操作 | 前端行为 | 后端请求 | 成功反馈 | 异常处理 | 风险/边界 |
|--------|----------|----------|----------|----------|----------|-----------|
| 实时卡片刷新 | 页面加载/定时轮询 | 显示加载态或保留上次数据 | `GET /admin/realtime` | 数值更新 | 请求失败 → 保留上次数据并标记“已过期” | `updated_at` 可能为空 |
| 时间范围切换 | 选择 24H/7D/30D | 触发汇总与图表更新 | `GET /admin/dashboard/summary` + `GET /admin/logs/stats/traffic-chart` | 刷新 KPI 与趋势 | 400/500 → 展示错误提示 | 7D/30D 为滚动窗口 |
| 趋势展示 | 默认展示 | 若 `include_trends=true` 显示趋势 | `GET /admin/dashboard/summary` | 显示 ↑↓ 或 Stable | 无趋势字段 → 隐藏趋势区域 | `direction=new` 仅显示 “New” |
### 3.4 告警摘要
**端点**: `GET /admin/alerts?status=active&limit=2&offset=0`
| UI 元素 | 字段 | 渲染逻辑 |
|---------|------|----------|
| 状态文本 | `GET /admin/alerts/stats``active` | "X active events require attention" |
| 事件内容 | `items[i].message` | 告警详情文本 |
| 事件时间 | `items[i].created_at` | 转换为相对时间 "42m ago" |
| 指示器颜色 | `items[i].severity` | `critical`-> 红色 ;`warning` → 橙色; `info` → 绿色 |
**告警严重级别**:
| Severity | 颜色 | 说明 |
|----------|------|------|
| `critical` | 红色 | 严重告警 |
| `warning` | 橙色 | 警告 |
| `info` | 绿色 | 信息 |
**交互说明**:
| 功能点 | 用户操作 | 前端行为 | 后端请求 | 成功反馈 | 异常处理 | 风险/边界 |
|--------|----------|----------|----------|----------|----------|-----------|
| 告警摘要展示 | 页面加载/轮询 | 拉取统计与最新告警 | `GET /admin/alerts/stats` + `GET /admin/alerts` | 更新列表与计数 | 请求失败 → 显示占位文案 | 列表为空 → 显示 "All Clear" |
| 进入告警页 | 点击卡片/箭头 | 路由跳转 | 无 | 进入详情页 | 无 | 路由未实现时隐藏入口 |
### 3.5 流量分析图表 (Traffic Analysis)
#### 推荐端点: `GET /admin/logs/stats/traffic-chart`
此端点专为堆叠流量图表设计,返回"时间×模型"二维聚合数据。
**请求参数**:
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `granularity` | string | `hour` | 时间粒度: `hour``minute` |
| `since` | int | 24h 前 | 起始时间 (Unix 秒) |
| `until` | int | 当前 | 结束时间 (Unix 秒) |
| `top_n` | int | 5 | Top 模型数量 (1-20),其余归入 "other" |
**时间粒度约束**:
| 粒度 | 约束 | 典型场景 |
|------|------|----------|
| `hour` | 无限制 | 24H / 7D / 30D 视图 |
| `minute` | 必须提供 `since` + `until`,范围 ≤ 6 小时 | 近 1-6 小时高精度视图 |
**响应结构**:
```json
{
"granularity": "hour",
"since": 1735689600,
"until": 1735776000,
"models": ["gpt-4-turbo", "claude-3.5-sonnet", "llama-3-70b", "other"],
"buckets": [
{
"time": "2025-01-01T00:00:00Z",
"timestamp": 1735689600,
"breakdown": {
"gpt-4-turbo": { "count": 1200, "tokens_in": 50000, "tokens_out": 80000 },
"claude-3.5-sonnet": { "count": 800, "tokens_in": 30000, "tokens_out": 45000 },
"other": { "count": 50, "tokens_in": 2000, "tokens_out": 3000 }
},
"total": { "count": 2050, "tokens_in": 82000, "tokens_out": 128000 }
}
]
}
```
**前端渲染建议**:
| 图表类型 | 数据映射 |
|----------|----------|
| X 轴 | `buckets[i].time` (格式化为 00:00, 04:00 等) |
| Y 轴 | 请求数量 |
| 堆叠系列 | `models` 数组,每个模型一个系列 |
| 系列数据 | `buckets[i].breakdown[model].count` |
| 总量标签 | `buckets[i].total.count` |
**时间范围选择器映射**:
| 选择器选项 | 请求参数 |
|------------|----------|
| 24H (默认) | `granularity=hour&since={now-24h}&until={now}` |
| 7D | `granularity=hour&since={now-7d}&until={now}` |
| 30D | `granularity=hour&since={now-30d}&until={now}` |
| 最近 1H | `granularity=minute&since={now-1h}&until={now}` |
| 最近 6H | `granularity=minute&since={now-6h}&until={now}` |
#### 备选端点: `GET /admin/logs/stats`
单维度聚合,适用于简单场景。
| 参数 | 返回数据 |
|------|----------|
| `group_by=hour` | 按小时聚合的时间序列 |
| `group_by=minute` | 按分钟聚合的时间序列 (需 since/until≤6h) |
| `group_by=model` | 按模型聚合的分布数据 |
**交互说明**:
| 功能点 | 用户操作 | 前端行为 | 后端请求 | 成功反馈 | 异常处理 | 风险/边界 |
|--------|----------|----------|----------|----------|----------|-----------|
| 图表加载 | 页面加载 | 请求默认 24H 图表 | `GET /admin/logs/stats/traffic-chart` | 渲染堆叠柱状图 | 请求失败 → 显示错误提示 | `buckets` 为空 → 显示 No Data |
| 时间粒度切换 | 选择分钟级范围 | 自动切换 granularity=minute | `GET /admin/logs/stats/traffic-chart` | 图表更细粒度 | 400 → 回退到 hour 并提示 | minute 范围 ≤ 6h |
| Top 模型限制 | 修改显示数量 | 约束 top_n ≤ 20 | `GET /admin/logs/stats/traffic-chart` | 图表更新 | 400 → 自动回退到 20 | 模型过多时显示 "other" |
### 3.6 模型使用排行 (Top Models)
**端点**: `GET /admin/dashboard/summary`
**字段**: `top_models` 数组
| UI 元素 | 字段 | 渲染逻辑 |
|---------|------|----------|
| 模型名称 | `top_models[i].model` | 显示模型 ID |
| 请求数量 | `top_models[i].requests` | 整数 |
| Token 数量 | `top_models[i].tokens` | 整数 |
| 占比百分比 | 前端计算 | `requests / sum(requests)` → 百分比 |
| 进度条颜色 | 索引 | 固定色板: 蓝→靛→紫→青→蓝绿 |
**展开视图端点**: `GET /admin/logs/stats?group_by=model&since={unix}&until={unix}`
**展开视图字段**: `items[i].model`, `items[i].count`, `items[i].tokens_in`, `items[i].tokens_out`, `items[i].avg_latency_ms`
**交互说明**:
| 功能点 | 用户操作 | 前端行为 | 后端请求 | 成功反馈 | 异常处理 | 风险/边界 |
|--------|----------|----------|----------|----------|----------|-----------|
| 排行加载 | 页面加载/时间范围变更 | 拉取 Top Models | `GET /admin/dashboard/summary` | 渲染列表与进度条 | 请求失败 → 显示占位 | 为空 → 隐藏卡片 |
| 展开查看更多 | 点击“查看更多/展开” | 打开弹窗/抽屉,展示完整模型列表 | `GET /admin/logs/stats?group_by=model` | 列表显示全量模型 | 请求失败 → 显示错误提示 | 模型过多时需滚动或搜索 |
---
## 4. 错误处理
| HTTP 状态码 | 处理方式 |
|-------------|----------|
| 200 | 正常渲染数据 |
| 400 | 显示参数错误提示 |
| 401 | Token 过期,触发自动登出 |
| 403 | 权限不足提示 |
| 500 | 显示系统错误,建议重试 |
**空数据处理**:
| 场景 | UI 表现 |
|------|---------|
| 图表无数据 | 显示 "No Data" 占位符 |
| 告警列表为空 | 显示 "All Clear" 状态 |
| Top Models 为空 | 隐藏该卡片或显示提示 |
---
## 5. 数据刷新策略
| 数据类型 | 刷新机制 | 频率 |
|----------|----------|------|
| 系统状态 | 定时轮询 | 30 秒 |
| 实时指标 (RPM/QPS) | 定时轮询 | 5-10 秒 |
| 告警统计 | 定时轮询 | 60 秒 |
| 概览指标 | 时间范围变更触发 | 按需 |
| 流量图表 | 时间范围变更触发 | 按需 |
---
## 6. 开发与测试建议
| 建议项 | 说明 |
|--------|------|
| Mock 数据 | 前端可利用 Mock 数据先行开发 UI |
| Swagger 验证 | 复杂聚合接口建议先在 Swagger 中验证 |
| 性能监控 | 关注 traffic-chart 接口在大数据量下的响应时间 |
---
*注:本对接文档基于现有端点整理*

View File

@@ -0,0 +1,466 @@
<!DOCTYPE html>
<html lang="en"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>EZ-API Control Plane Dashboard</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,typography"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"/>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
primary: "#3b82f6", // Bright blue for primary actions
"primary-dark": "#1d4ed8",
"accent": "#6366f1", // Indigo accent
"background-light": "#f3f4f6",
"background-dark": "#0f172a", // Very dark slate/blue
"surface-light": "#ffffff",
"surface-dark": "#1e293b", // Lighter slate for cards
"surface-darker": "#020617", // Almost black for sidebar
},
fontFamily: {
display: ["Inter", "sans-serif"],
body: ["Inter", "sans-serif"],
},
borderRadius: {
DEFAULT: "0.5rem",
'xl': '1rem',
'2xl': '1.5rem',
},
},
},
};
</script>
<style>
body {
font-family: 'Inter', sans-serif;
}::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #0f172a;
}
::-webkit-scrollbar-thumb {
background: #334155;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #475569;
}
.glass-effect {
background: rgba(30, 41, 59, 0.7);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.05);
}
</style>
</head>
<body class="bg-background-light dark:bg-background-dark text-slate-800 dark:text-slate-200 transition-colors duration-300 dark h-screen flex overflow-hidden">
<aside class="w-64 bg-white dark:bg-surface-darker border-r border-slate-200 dark:border-slate-800 flex flex-col justify-between flex-shrink-0 z-20">
<div>
<div class="h-16 flex items-center px-6 border-b border-slate-200 dark:border-slate-800">
<div class="w-8 h-8 rounded bg-primary flex items-center justify-center mr-3 shadow-lg shadow-blue-500/30">
<span class="material-icons text-white text-sm">hub</span>
</div>
<div>
<h1 class="font-bold text-sm text-slate-900 dark:text-white leading-tight">EZ-API</h1>
<p class="text-xs text-slate-500 dark:text-slate-400">Control Plane</p>
</div>
</div>
<nav class="p-4 space-y-1">
<a class="flex items-center px-4 py-3 bg-primary/10 text-primary rounded-lg group transition-all" href="#">
<span class="material-icons mr-3 text-xl">dashboard</span>
<span class="font-medium text-sm">Dashboard</span>
</a>
<a class="flex items-center px-4 py-3 text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800/50 hover:text-slate-900 dark:hover:text-white rounded-lg group transition-all" href="#">
<span class="material-icons mr-3 text-xl group-hover:text-primary transition-colors">vpn_key</span>
<span class="font-medium text-sm">API Keys</span>
</a>
<a class="flex items-center px-4 py-3 text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800/50 hover:text-slate-900 dark:hover:text-white rounded-lg group transition-all" href="#">
<span class="material-icons mr-3 text-xl group-hover:text-primary transition-colors">bar_chart</span>
<span class="font-medium text-sm">Analytics</span>
</a>
<a class="flex items-center px-4 py-3 text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800/50 hover:text-slate-900 dark:hover:text-white rounded-lg group transition-all" href="#">
<span class="material-icons mr-3 text-xl group-hover:text-primary transition-colors">tune</span>
<span class="font-medium text-sm">Gateway Settings</span>
</a>
<a class="flex items-center px-4 py-3 text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800/50 hover:text-slate-900 dark:hover:text-white rounded-lg group transition-all" href="#">
<span class="material-icons mr-3 text-xl group-hover:text-primary transition-colors">terminal</span>
<span class="font-medium text-sm">Logs</span>
</a>
<a class="flex items-center px-4 py-3 text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800/50 hover:text-slate-900 dark:hover:text-white rounded-lg group transition-all" href="#">
<span class="material-icons mr-3 text-xl group-hover:text-primary transition-colors">credit_card</span>
<span class="font-medium text-sm">Billing</span>
</a>
</nav>
</div>
<div class="p-4 border-t border-slate-200 dark:border-slate-800 space-y-1">
<a class="flex items-center px-4 py-2 text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white text-sm transition-colors" href="#">
<span class="material-icons mr-3 text-lg">description</span>
Documentation
</a>
<a class="flex items-center px-4 py-2 text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white text-sm transition-colors mb-4" href="#">
<span class="material-icons mr-3 text-lg">headset_mic</span>
Support
</a>
<div class="flex items-center p-3 bg-slate-100 dark:bg-slate-800/50 rounded-xl border border-slate-200 dark:border-slate-700 mt-4">
<div class="relative">
<img alt="User Avatar" class="w-9 h-9 rounded-full object-cover border border-slate-300 dark:border-slate-600" src="https://lh3.googleusercontent.com/aida-public/AB6AXuDtjVsB0qfW3oChJxs05sru_BK-FSqC1sZwvdso3DjLfnr-iB_pgcmyOz4SwpGMOX_zCgSfdvamkjiU-5FUnUTdZqAFJQIrQClxRJ_OHrKDt33pe_ugSjHOI8_8drUr84EbXuQ7rBz_U9z-qEt9QnUOJ3D8Q4E1yBFA0LAv4RUzx0CAzTiOTxwjT00DLIPQb0VPJIofgFDdMIGxD7hc9JRWilp_rJD0xzWzMdtYH6gjv8crqZGaQvuymzNEgJa7PrUwi8yolMMNXcbk"/>
<div class="absolute bottom-0 right-0 w-2.5 h-2.5 bg-green-500 rounded-full border-2 border-white dark:border-slate-800"></div>
</div>
<div class="ml-3 flex-1 min-w-0">
<p class="text-xs font-medium text-slate-900 dark:text-white truncate">Admin User</p>
<p class="text-[10px] text-slate-500 dark:text-slate-400 truncate">admin@ez-api.io</p>
</div>
<button class="text-slate-400 hover:text-slate-600 dark:hover:text-white transition-colors">
<span class="material-icons text-sm">logout</span>
</button>
</div>
</div>
</aside>
<main class="flex-1 flex flex-col min-w-0 overflow-hidden bg-background-light dark:bg-background-dark relative">
<div class="absolute inset-0 z-0 opacity-[0.03] dark:opacity-[0.05] pointer-events-none" style="background-image: linear-gradient(#6366f1 1px, transparent 1px), linear-gradient(90deg, #6366f1 1px, transparent 1px); background-size: 40px 40px;">
</div>
<header class="h-16 flex items-center justify-between px-8 border-b border-slate-200 dark:border-slate-800 bg-white/80 dark:bg-background-dark/80 backdrop-blur-md z-10">
<div class="flex items-center">
<div class="inline-flex items-center px-3 py-1 rounded-full bg-emerald-500/10 border border-emerald-500/20 text-emerald-600 dark:text-emerald-400 text-xs font-medium">
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500 mr-2 animate-pulse"></span>
SYSTEM STATUS: NOMINAL
</div>
</div>
<div class="flex items-center space-x-6">
<div class="relative w-64">
<span class="material-icons absolute left-3 top-2.5 text-slate-400 text-sm">search</span>
<input class="w-full bg-slate-100 dark:bg-slate-800 border-none rounded-lg py-2 pl-10 pr-4 text-sm text-slate-800 dark:text-slate-200 placeholder-slate-400 focus:ring-2 focus:ring-primary" placeholder="Search resources..." type="text"/>
</div>
<div class="flex items-center space-x-4">
<button class="relative text-slate-500 dark:text-slate-400 hover:text-slate-700 dark:hover:text-white transition-colors">
<span class="material-icons">notifications</span>
<span class="absolute top-0 right-0 w-2 h-2 bg-red-500 rounded-full border-2 border-white dark:border-background-dark"></span>
</button>
<button class="text-slate-500 dark:text-slate-400 hover:text-slate-700 dark:hover:text-white transition-colors">
<span class="material-icons">help</span>
</button>
</div>
</div>
</header>
<div class="flex-1 overflow-auto p-8 z-10">
<div class="max-w-7xl mx-auto space-y-6">
<div class="flex items-end justify-between mb-8">
<div>
<h2 class="text-2xl font-bold text-slate-900 dark:text-white tracking-tight">System Performance</h2>
<p class="text-slate-500 dark:text-slate-400 mt-1 text-sm">Real-time metrics from EZ-API Gateway Cluster</p>
</div>
<div class="flex items-center bg-white dark:bg-surface-dark border border-slate-200 dark:border-slate-700 rounded-lg p-1">
<span class="text-xs font-medium text-slate-500 dark:text-slate-400 px-3">Time Range:</span>
<select class="bg-transparent border-none text-sm font-semibold text-slate-900 dark:text-white focus:ring-0 py-1 pl-0 pr-8 cursor-pointer">
<option>24H</option>
<option>7D</option>
<option>30D</option>
</select>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<div class="bg-white dark:bg-surface-dark border border-slate-200 dark:border-slate-800 rounded-2xl p-5 shadow-sm hover:shadow-md transition-shadow relative overflow-hidden group">
<div class="absolute -right-6 -top-6 w-24 h-24 bg-blue-500/10 rounded-full group-hover:scale-110 transition-transform duration-500"></div>
<div class="flex justify-between items-start mb-4">
<div class="w-10 h-10 rounded-lg bg-blue-500/10 flex items-center justify-center text-blue-500">
<span class="material-icons">bolt</span>
</div>
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-emerald-500/10 text-emerald-600 dark:text-emerald-400">
<span class="material-icons text-[10px] mr-1">trending_up</span> +12%
</span>
</div>
<h3 class="text-slate-500 dark:text-slate-400 text-sm font-medium">Requests Per Minute</h3>
<div class="mt-1 text-3xl font-bold text-slate-900 dark:text-white">747,000</div>
</div>
<div class="bg-white dark:bg-surface-dark border border-slate-200 dark:border-slate-800 rounded-2xl p-5 shadow-sm hover:shadow-md transition-shadow relative overflow-hidden group">
<div class="absolute -right-6 -top-6 w-24 h-24 bg-indigo-500/10 rounded-full group-hover:scale-110 transition-transform duration-500"></div>
<div class="flex justify-between items-start mb-4">
<div class="w-10 h-10 rounded-lg bg-indigo-500/10 flex items-center justify-center text-indigo-500">
<span class="material-icons">vpn_key</span>
</div>
</div>
<h3 class="text-slate-500 dark:text-slate-400 text-sm font-medium">Active Keys</h3>
<div class="mt-1 text-3xl font-bold text-slate-900 dark:text-white">2,350</div>
</div>
<div class="bg-white dark:bg-surface-dark border border-slate-200 dark:border-slate-800 rounded-2xl p-5 shadow-sm hover:shadow-md transition-shadow relative overflow-hidden group">
<div class="absolute -right-6 -top-6 w-24 h-24 bg-amber-500/10 rounded-full group-hover:scale-110 transition-transform duration-500"></div>
<div class="flex justify-between items-start mb-4">
<div class="w-10 h-10 rounded-lg bg-amber-500/10 flex items-center justify-center text-amber-500">
<span class="material-icons">token</span>
</div>
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-rose-500/10 text-rose-600 dark:text-rose-400">
<span class="material-icons text-[10px] mr-1">trending_down</span> -1.1%
</span>
</div>
<h3 class="text-slate-500 dark:text-slate-400 text-sm font-medium">Consumed Tokens</h3>
<div class="mt-1 text-3xl font-bold text-slate-900 dark:text-white">892k</div>
</div>
<div class="bg-white dark:bg-surface-dark border border-slate-200 dark:border-slate-800 rounded-2xl p-5 shadow-sm hover:shadow-md transition-shadow relative overflow-hidden group">
<div class="absolute -right-6 -top-6 w-24 h-24 bg-rose-500/10 rounded-full group-hover:scale-110 transition-transform duration-500"></div>
<div class="flex justify-between items-start mb-4">
<div class="w-10 h-10 rounded-lg bg-rose-500/10 flex items-center justify-center text-rose-500">
<span class="material-icons">error_outline</span>
</div>
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-emerald-500/10 text-emerald-600 dark:text-emerald-400">
<span class="material-icons text-[10px] mr-1">check</span> Stable
</span>
</div>
<h3 class="text-slate-500 dark:text-slate-400 text-sm font-medium">Error Rate</h3>
<div class="mt-1 text-3xl font-bold text-slate-900 dark:text-white">0.02%</div>
</div>
</div>
<a class="block bg-white dark:bg-surface-dark border border-slate-200 dark:border-slate-800 rounded-2xl p-5 shadow-sm hover:shadow-md hover:border-primary/40 transition-all group" href="#">
<div class="flex flex-col lg:flex-row items-center justify-between gap-4">
<div class="flex items-center space-x-4 w-full lg:w-auto">
<div class="w-10 h-10 rounded-lg bg-rose-500/10 flex items-center justify-center flex-shrink-0 text-rose-500">
<span class="material-icons">notifications_active</span>
</div>
<div>
<h3 class="text-slate-900 dark:text-white font-bold text-sm">Warnings &amp; Alerts</h3>
<p class="text-xs text-slate-500 dark:text-slate-400">2 active events require attention</p>
</div>
</div>
<div class="w-full lg:flex-1 lg:mx-8 grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="flex items-center space-x-3 bg-slate-50 dark:bg-slate-800/50 rounded-lg px-3 py-2 border border-slate-100 dark:border-slate-700/50">
<div class="w-1.5 h-1.5 rounded-full bg-rose-500 flex-shrink-0 shadow-[0_0_8px_rgba(244,63,94,0.6)] animate-pulse"></div>
<div class="min-w-0 flex-1">
<p class="text-xs font-medium text-slate-800 dark:text-slate-200 truncate">API Key quota exceeded for <span class="text-rose-500 dark:text-rose-400 font-mono">Enterprise_Client_A</span></p>
</div>
<span class="text-[10px] text-slate-400 whitespace-nowrap">42m ago</span>
</div>
<div class="flex items-center space-x-3 bg-slate-50 dark:bg-slate-800/50 rounded-lg px-3 py-2 border border-slate-100 dark:border-slate-700/50">
<div class="w-1.5 h-1.5 rounded-full bg-emerald-500 flex-shrink-0 shadow-[0_0_8px_rgba(16,185,129,0.6)]"></div>
<div class="min-w-0 flex-1">
<p class="text-xs font-medium text-slate-800 dark:text-slate-200 truncate">Database backup completed successfully</p>
</div>
<span class="text-[10px] text-slate-400 whitespace-nowrap">2h ago</span>
</div>
</div>
<div class="hidden lg:flex items-center text-slate-400 group-hover:text-primary transition-colors">
<span class="material-icons">arrow_forward</span>
</div>
</div>
</a>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div class="lg:col-span-2 bg-white dark:bg-surface-dark border border-slate-200 dark:border-slate-800 rounded-2xl p-6 shadow-sm">
<div class="flex items-center justify-between mb-6">
<div>
<h3 class="text-lg font-bold text-slate-900 dark:text-white">Traffic Volume</h3>
<p class="text-sm text-slate-500 dark:text-slate-400">Inbound requests over the last 24 hours</p>
</div>
<div class="text-right">
<div class="text-2xl font-bold text-slate-900 dark:text-white">1.2M</div>
<div class="text-xs font-medium text-emerald-500 flex items-center justify-end">
<span class="material-icons text-[14px] mr-0.5">arrow_upward</span> 8.9%
</div>
</div>
</div>
<div class="h-[350px] w-full relative">
<canvas id="trafficChart"></canvas>
</div>
<div class="mt-4 flex justify-between text-xs text-slate-400 dark:text-slate-500 px-2 font-mono">
<span>00:00</span>
<span>04:00</span>
<span>08:00</span>
<span>12:00</span>
<span>16:00</span>
<span>20:00</span>
<span>23:59</span>
</div>
</div>
<div class="space-y-6 flex flex-col h-full">
<div class="bg-white dark:bg-surface-dark border border-slate-200 dark:border-slate-800 rounded-2xl p-6 shadow-sm flex-1">
<h3 class="text-lg font-bold text-slate-900 dark:text-white mb-6">Top Models</h3>
<div class="space-y-5">
<div>
<div class="flex justify-between text-sm mb-1.5">
<span class="text-slate-700 dark:text-slate-300 font-medium">GPT-4-Turbo</span>
<span class="text-slate-500 dark:text-slate-400">45%</span>
</div>
<div class="w-full bg-slate-100 dark:bg-slate-700/50 rounded-full h-2">
<div class="bg-blue-500 h-2 rounded-full" style="width: 45%"></div>
</div>
</div>
<div>
<div class="flex justify-between text-sm mb-1.5">
<span class="text-slate-700 dark:text-slate-300 font-medium">Claude 3.5 Sonnet</span>
<span class="text-slate-500 dark:text-slate-400">32%</span>
</div>
<div class="w-full bg-slate-100 dark:bg-slate-700/50 rounded-full h-2">
<div class="bg-indigo-500 h-2 rounded-full" style="width: 32%"></div>
</div>
</div>
<div>
<div class="flex justify-between text-sm mb-1.5">
<span class="text-slate-700 dark:text-slate-300 font-medium">Llama 3 70B</span>
<span class="text-slate-500 dark:text-slate-400">18%</span>
</div>
<div class="w-full bg-slate-100 dark:bg-slate-700/50 rounded-full h-2">
<div class="bg-violet-500 h-2 rounded-full" style="width: 18%"></div>
</div>
</div>
<div>
<div class="flex justify-between text-sm mb-1.5">
<span class="text-slate-700 dark:text-slate-300 font-medium">Mistral Large</span>
<span class="text-slate-500 dark:text-slate-400">5%</span>
</div>
<div class="w-full bg-slate-100 dark:bg-slate-700/50 rounded-full h-2">
<div class="bg-cyan-500 h-2 rounded-full" style="width: 5%"></div>
</div>
</div>
<div>
<div class="flex justify-between text-sm mb-1.5">
<span class="text-slate-700 dark:text-slate-300 font-medium">Gemini Pro</span>
<span class="text-slate-500 dark:text-slate-400">3%</span>
</div>
<div class="w-full bg-slate-100 dark:bg-slate-700/50 rounded-full h-2">
<div class="bg-teal-500 h-2 rounded-full" style="width: 3%"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<script>
document.addEventListener('DOMContentLoaded', function() {
const ctx = document.getElementById('trafficChart').getContext('2d');
// Check for dark mode to adjust chart colors
const isDarkMode = document.documentElement.classList.contains('dark') || true; // Defaulting true for preview
const gridColor = isDarkMode ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.05)';
const textColor = isDarkMode ? '#94a3b8' : '#64748b';
// Data Generation
const labels = ['00:00', '02:00', '04:00', '06:00', '08:00', '10:00', '12:00', '14:00', '16:00', '18:00', '20:00', '22:00'];
// Generating somewhat realistic data patterns
const dataGPT4 = [45, 42, 35, 30, 45, 65, 85, 90, 88, 75, 60, 50];
const dataClaude = [32, 30, 25, 20, 35, 50, 60, 65, 62, 55, 45, 38];
const dataLlama = [18, 15, 12, 10, 20, 25, 30, 35, 32, 28, 22, 19];
const dataMistral = [5, 4, 3, 2, 6, 8, 10, 12, 11, 9, 7, 6];
const dataGemini = [3, 2, 2, 1, 4, 5, 7, 8, 7, 6, 5, 4];
new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [
{
label: 'GPT-4-Turbo',
data: dataGPT4,
backgroundColor: '#3b82f6', // blue-500
borderRadius: 4,
barPercentage: 0.6,
},
{
label: 'Claude 3.5',
data: dataClaude,
backgroundColor: '#6366f1', // indigo-500
borderRadius: 4,
barPercentage: 0.6,
},
{
label: 'Llama 3 70B',
data: dataLlama,
backgroundColor: '#8b5cf6', // violet-500
borderRadius: 4,
barPercentage: 0.6,
},
{
label: 'Mistral Large',
data: dataMistral,
backgroundColor: '#06b6d4', // cyan-500
borderRadius: 4,
barPercentage: 0.6,
},
{
label: 'Gemini Pro',
data: dataGemini,
backgroundColor: '#14b8a6', // teal-500
borderRadius: 4,
barPercentage: 0.6,
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: true,
position: 'bottom', // Legend at bottom
labels: {
color: textColor,
usePointStyle: true,
pointStyle: 'circle',
padding: 20,
font: {
family: "'Inter', sans-serif",
size: 11
}
}
},
tooltip: {
mode: 'index',
intersect: false,
backgroundColor: isDarkMode ? '#1e293b' : '#ffffff',
titleColor: isDarkMode ? '#f8fafc' : '#0f172a',
bodyColor: isDarkMode ? '#cbd5e1' : '#475569',
borderColor: isDarkMode ? '#334155' : '#e2e8f0',
borderWidth: 1,
padding: 10,
cornerRadius: 8,
titleFont: { family: "'Inter', sans-serif", weight: '600' },
bodyFont: { family: "'Inter', sans-serif" }
}
},
scales: {
x: {
stacked: true,
grid: {
display: false,
drawBorder: false
},
ticks: {
color: textColor,
font: {
family: "'Inter', sans-serif",
size: 11
}
}
},
y: {
stacked: true,
grid: {
color: gridColor,
borderDash: [4, 4],
drawBorder: false
},
ticks: {
color: textColor,
font: {
family: "'Inter', sans-serif",
size: 11
},
callback: function(value) {
return value + 'k';
}
}
}
},
interaction: {
mode: 'nearest',
axis: 'x',
intersect: false
}
}
});
});
</script>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB