深度优化 ui
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import Link from "next/link";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { formatLocalDateTime } from "@/lib/datetime";
|
||||
import { formatBytes, getDashboardMetrics } from "@/lib/store";
|
||||
|
||||
@@ -28,18 +30,12 @@ export default async function DashboardPage() {
|
||||
<span className="eyebrow">概览</span>
|
||||
<h2>仪表盘</h2>
|
||||
<div className="badge-row">
|
||||
<span className="status-pill status-pill--ok">在线 {data.totals.total_online_connections}</span>
|
||||
<span className="status-pill status-pill--warm">节点 {data.totals.enabled_nodes}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="hero-aside">
|
||||
<div className="stat-tile">
|
||||
<span>活动连接</span>
|
||||
<strong>{data.totals.total_online_connections}</strong>
|
||||
</div>
|
||||
<div className="stat-tile">
|
||||
<span>累计流量</span>
|
||||
<strong>{formatBytes(data.totals.total_traffic)}</strong>
|
||||
<span className="status-pill status-pill--ok">
|
||||
在线 {data.totals.total_online_connections}
|
||||
</span>
|
||||
<span className="status-pill status-pill--warm">
|
||||
节点 {data.totals.enabled_nodes}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -48,7 +44,9 @@ export default async function DashboardPage() {
|
||||
<article className="metric-card metric-card--warm">
|
||||
<span>总用户</span>
|
||||
<strong>{data.totals.total_users}</strong>
|
||||
<small>启用 {data.totals.enabled_users} / 管理员 {data.totals.admin_users}</small>
|
||||
<small>
|
||||
启用 {data.totals.enabled_users} / 管理员 {data.totals.admin_users}
|
||||
</small>
|
||||
</article>
|
||||
<article className="metric-card metric-card--ink">
|
||||
<span>活跃用户</span>
|
||||
@@ -81,8 +79,17 @@ export default async function DashboardPage() {
|
||||
<article className="panel-card">
|
||||
<div className="section-title">
|
||||
<h3>节点健康</h3>
|
||||
<Link
|
||||
href="/admin/nodes"
|
||||
className={buttonVariants({ variant: "ghost", size: "xs" })}
|
||||
>
|
||||
管理节点 →
|
||||
</Link>
|
||||
</div>
|
||||
<div className="stack-md">
|
||||
{data.nodeHealth.length === 0 && (
|
||||
<p className="muted">暂无节点,请先添加节点。</p>
|
||||
)}
|
||||
{data.nodeHealth.map((node) => (
|
||||
<div key={node.id} className="list-row list-row--card">
|
||||
<div>
|
||||
@@ -113,8 +120,17 @@ export default async function DashboardPage() {
|
||||
<article className="panel-card">
|
||||
<div className="section-title">
|
||||
<h3>用量排行</h3>
|
||||
<Link
|
||||
href="/admin/users"
|
||||
className={buttonVariants({ variant: "ghost", size: "xs" })}
|
||||
>
|
||||
管理用户 →
|
||||
</Link>
|
||||
</div>
|
||||
<div className="stack-md">
|
||||
{data.topUsers.length === 0 && (
|
||||
<p className="muted">暂无用户用量。</p>
|
||||
)}
|
||||
{data.topUsers.map((user) => (
|
||||
<div key={user.auth_id} className="list-row list-row--card">
|
||||
<div>
|
||||
@@ -124,7 +140,8 @@ export default async function DashboardPage() {
|
||||
<div className="align-right">
|
||||
<p>{formatBytes(user.total_bytes)}</p>
|
||||
<p className="muted">
|
||||
TX {formatBytes(user.used_tx_bytes)} / RX {formatBytes(user.used_rx_bytes)}
|
||||
TX {formatBytes(user.used_tx_bytes)} / RX{" "}
|
||||
{formatBytes(user.used_rx_bytes)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -135,19 +152,34 @@ export default async function DashboardPage() {
|
||||
<article className="panel-card">
|
||||
<div className="section-title">
|
||||
<h3>最近鉴权失败</h3>
|
||||
<Link
|
||||
href="/admin/audits"
|
||||
className={buttonVariants({ variant: "ghost", size: "xs" })}
|
||||
>
|
||||
查看日志 →
|
||||
</Link>
|
||||
</div>
|
||||
<div className="stack-md">
|
||||
{data.recentFailures.length === 0 && (
|
||||
<p className="muted">暂无鉴权失败。</p>
|
||||
)}
|
||||
{data.recentFailures.map((item, index) => (
|
||||
<div key={`${item.created_at}-${index}`} className="list-row list-row--card">
|
||||
<div
|
||||
key={`${item.created_at}-${index}`}
|
||||
className="list-row list-row--card"
|
||||
>
|
||||
<div>
|
||||
<strong>{item.reason}</strong>
|
||||
<p className="muted">
|
||||
{item.node_name ?? "未知节点"} / {item.username ?? "未知用户"}
|
||||
{item.node_name ?? "未知节点"} /{" "}
|
||||
{item.username ?? "未知用户"}
|
||||
</p>
|
||||
</div>
|
||||
<div className="align-right">
|
||||
<p className="muted mono">{item.addr ?? "-"}</p>
|
||||
<p className="muted">{formatLocalDateTime(item.created_at)}</p>
|
||||
<p className="muted">
|
||||
{formatLocalDateTime(item.created_at)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@@ -159,17 +191,23 @@ export default async function DashboardPage() {
|
||||
<h3>最近流量入账</h3>
|
||||
</div>
|
||||
<div className="stack-md">
|
||||
{data.recentUsage.length === 0 && (
|
||||
<p className="muted">暂无流量入账记录。</p>
|
||||
)}
|
||||
{data.recentUsage.map((item, index) => (
|
||||
<div key={`${item.created_at}-${item.auth_id}-${index}`} className="list-row list-row--card">
|
||||
<div
|
||||
key={`${item.created_at}-${item.auth_id}-${index}`}
|
||||
className="list-row list-row--card"
|
||||
>
|
||||
<div>
|
||||
<strong>{item.username ?? item.auth_id}</strong>
|
||||
<p className="muted">{item.node_name ?? "未知节点"}</p>
|
||||
</div>
|
||||
<div className="align-right">
|
||||
<p>
|
||||
+{formatBytes(item.tx_bytes + item.rx_bytes)}
|
||||
<p>+{formatBytes(item.tx_bytes + item.rx_bytes)}</p>
|
||||
<p className="muted">
|
||||
{formatLocalDateTime(item.created_at)}
|
||||
</p>
|
||||
<p className="muted">{formatLocalDateTime(item.created_at)}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@@ -185,12 +223,19 @@ export default async function DashboardPage() {
|
||||
<p className="muted">暂无同步错误。</p>
|
||||
) : (
|
||||
data.syncErrors.map((item, index) => (
|
||||
<div key={`${item.created_at}-${index}`} className="list-row list-row--card">
|
||||
<div
|
||||
key={`${item.created_at}-${index}`}
|
||||
className="list-row list-row--card"
|
||||
>
|
||||
<div>
|
||||
<strong>{item.node_name}</strong>
|
||||
<p className="error-text">{item.error_message ?? "unknown error"}</p>
|
||||
<p className="error-text">
|
||||
{item.error_message ?? "unknown error"}
|
||||
</p>
|
||||
</div>
|
||||
<p className="muted">{formatLocalDateTime(item.created_at)}</p>
|
||||
<p className="muted">
|
||||
{formatLocalDateTime(item.created_at)}
|
||||
</p>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user