"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; import { AlertTriangle, Database, RefreshCcw, ShieldCheck, Wallet } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; type HealthPayload = { status?: string; db?: { ok?: boolean }; }; type ProbabilityRollout = { decision?: string; ready_for_primary?: boolean; blocking_reasons?: string[]; }; type SystemStatusPayload = { state_storage_mode?: string; db?: { ok?: boolean; db_path?: string }; features?: Record; metrics?: Record; probability?: { engine_mode?: string; rollout?: { decision?: ProbabilityRollout; evaluation_report_exists?: boolean; shadow_report_exists?: boolean; }; }; integrations?: Record; }; type PaymentRuntimePayload = { checkout?: { enabled?: boolean; configured?: boolean; chain_id?: number; receiver_contract?: string; confirmations?: number; }; rpc?: { configured_rpc_count?: number; active_rpc_url?: string; }; event_loop_state?: { last_scanned_block?: number; updated_at?: string; }; recent_audit_events?: Array<{ id: number; event_type: string; created_at: string; payload?: { reason?: string; detail?: string; tx_hash?: string; receiver_actual?: string; receiver_expected?: string; }; }>; }; type PaymentIncident = { id: number; event_type: string; created_at: string; payload?: { reason?: string; detail?: string; tx_hash?: string; receiver_actual?: string; receiver_expected?: string; plan_code?: string; resolved_at?: string; resolved_by?: string; }; }; type AuthMePayload = { authenticated?: boolean; email?: string | null; entitlement_mode?: string; subscription_active?: boolean | null; weekly_rank?: number | null; points?: number; }; type OpsUser = { telegram_id: number; username?: string | null; points?: number; daily_points?: number; daily_points_date?: string | null; weekly_points?: number; weekly_points_week?: string | null; message_count?: number; supabase_email?: string | null; last_message_at?: string | null; }; type WeeklyLeaderboardEntry = { telegram_id: number; username?: string | null; points?: number; message_count?: number; weekly_points?: number; }; type MembershipEntry = { user_id: string; email?: string | null; telegram_id?: number | null; username?: string | null; registered_at?: string | null; plan_code?: string | null; starts_at?: string | null; expires_at?: string | null; }; function formatDateTime(value?: string | null) { if (!value) return "-"; const date = new Date(value); if (Number.isNaN(date.getTime())) return value; return date.toLocaleString("zh-CN", { hour12: false }); } function maskUrl(value?: string | null) { if (!value) return "-"; if (value.length <= 40) return value; return `${value.slice(0, 28)}...${value.slice(-8)}`; } async function readJson(url: string): Promise { const response = await fetch(url, { cache: "no-store" }); if (!response.ok) { const raw = await response.text(); throw new Error(`${url} -> HTTP ${response.status} ${raw.slice(0, 180)}`); } return response.json() as Promise; } export function OpsDashboard() { const [health, setHealth] = useState(null); const [status, setStatus] = useState(null); const [payments, setPayments] = useState(null); const [auth, setAuth] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [refreshedAt, setRefreshedAt] = useState(null); const [searchQuery, setSearchQuery] = useState(""); const [users, setUsers] = useState([]); const [memberships, setMemberships] = useState([]); const [paymentIncidents, setPaymentIncidents] = useState([]); const [incidentReasonFilter, setIncidentReasonFilter] = useState("all"); const [usersLoading, setUsersLoading] = useState(false); const [usersError, setUsersError] = useState(null); const [leaderboard, setLeaderboard] = useState([]); const [membershipsLoading, setMembershipsLoading] = useState(false); const [incidentsLoading, setIncidentsLoading] = useState(false); const [resolvingIncidentId, setResolvingIncidentId] = useState(null); const [grantEmail, setGrantEmail] = useState(""); const [grantPoints, setGrantPoints] = useState("300"); const [grantStatus, setGrantStatus] = useState(null); const [grantError, setGrantError] = useState(null); const [grantLoading, setGrantLoading] = useState(false); const load = useCallback(async () => { setLoading(true); setError(null); try { const [healthData, statusData, paymentData, authData] = await Promise.all([ readJson("/api/healthz"), readJson("/api/system/status"), readJson("/api/payments/runtime"), readJson("/api/auth/me"), ]); setHealth(healthData); setStatus(statusData); setPayments(paymentData); setAuth(authData); setRefreshedAt(new Date().toISOString()); } catch (loadError) { setError(String(loadError)); } finally { setLoading(false); } }, []); useEffect(() => { void load(); }, [load]); const loadUsers = useCallback(async (query: string) => { setUsersLoading(true); setUsersError(null); try { const url = new URL("/api/ops/users", window.location.origin); if (query.trim()) { url.searchParams.set("q", query.trim()); } url.searchParams.set("limit", "20"); const data = await readJson<{ users?: OpsUser[] }>(url.toString()); setUsers(data.users || []); } catch (loadError) { setUsersError(String(loadError)); } finally { setUsersLoading(false); } }, []); const loadLeaderboard = useCallback(async () => { try { const data = await readJson<{ leaderboard?: WeeklyLeaderboardEntry[] }>( "/api/ops/leaderboard/weekly?limit=10", ); setLeaderboard(data.leaderboard || []); } catch { setLeaderboard([]); } }, []); const loadMemberships = useCallback(async () => { setMembershipsLoading(true); try { const data = await readJson<{ memberships?: MembershipEntry[] }>( "/api/ops/memberships?limit=200", ); setMemberships(data.memberships || []); } catch { setMemberships([]); } finally { setMembershipsLoading(false); } }, []); const loadPaymentIncidents = useCallback(async (reasonFilter?: string) => { setIncidentsLoading(true); try { const url = new URL("/api/ops/payments/incidents", window.location.origin); url.searchParams.set("limit", "20"); const selectedReason = String(reasonFilter || incidentReasonFilter || "all"); if (selectedReason && selectedReason !== "all") { url.searchParams.set("reason", selectedReason); } const data = await readJson<{ incidents?: PaymentIncident[] }>(url.toString()); setPaymentIncidents(data.incidents || []); } catch { setPaymentIncidents([]); } finally { setIncidentsLoading(false); } }, [incidentReasonFilter]); useEffect(() => { void loadUsers(""); void loadLeaderboard(); void loadMemberships(); void loadPaymentIncidents(incidentReasonFilter); }, [loadLeaderboard, loadMemberships, loadPaymentIncidents, loadUsers]); const resolveIncident = useCallback(async (eventId: number) => { setResolvingIncidentId(eventId); try { const response = await fetch(`/api/ops/payments/incidents/${eventId}/resolve`, { method: "POST", }); if (!response.ok) { const raw = await response.text(); throw new Error(raw || `HTTP ${response.status}`); } await loadPaymentIncidents(incidentReasonFilter); await load(); } finally { setResolvingIncidentId(null); } }, [incidentReasonFilter, load, loadPaymentIncidents]); const rolloutDecision = status?.probability?.rollout?.decision; const rolloutVariant = useMemo(() => { const decision = rolloutDecision?.decision; if (decision === "promote") return "success" as const; if (decision === "observe") return "warning" as const; return "danger" as const; }, [rolloutDecision?.decision]); const submitGrant = useCallback(async () => { setGrantLoading(true); setGrantError(null); setGrantStatus(null); try { const response = await fetch("/api/ops/users/grant-points", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: grantEmail.trim(), points: Number(grantPoints), }), }); const payload = (await response.json()) as { ok?: boolean; detail?: { reason?: string }; points_after?: number; points_added?: number; supabase_email?: string; }; if (!response.ok) { throw new Error( payload?.detail?.reason || `HTTP ${response.status}`, ); } setGrantStatus( `${payload.supabase_email || grantEmail.trim()} 已补 ${payload.points_added || grantPoints} 分,当前 ${payload.points_after ?? "-"}`, ); await loadUsers(searchQuery); await loadLeaderboard(); await loadMemberships(); await loadPaymentIncidents(incidentReasonFilter); } catch (submitError) { setGrantError(String(submitError)); } finally { setGrantLoading(false); } }, [grantEmail, grantPoints, incidentReasonFilter, loadLeaderboard, loadMemberships, loadPaymentIncidents, loadUsers, searchQuery]); return (
Ops Health {health?.status || "unknown"} SQLite {status?.db?.ok ? "ok" : "error"} EMOS {rolloutDecision?.decision || "hold"}

PolyWeather Ops

直接挂在现有域名下的轻量运营页。先做只读运维视图,把系统状态、支付运行态和当前登录态聚合起来。

刷新时间: {formatDateTime(refreshedAt)}
{error ? ( 加载失败 {error} ) : null}
系统健康 后端健康、鉴权策略、状态存储模式。
healthz{health?.status || "-"}
鉴权模式{auth?.entitlement_mode || "-"}
状态存储{status?.state_storage_mode || "-"}
DB{health?.db?.ok ? "ok" : "-"}
运行态存储 SQLite 是否正常,以及 rollout 当前状态。
SQLite{status?.db?.ok ? "ok" : "error"}
EMOS 模式{status?.probability?.engine_mode || "-"}
上线门禁{rolloutDecision?.decision || "-"}
ready_for_primary{rolloutDecision?.ready_for_primary ? "true" : "false"}
支付运行态 当前 RPC、事件循环区块、合约配置。
支付启用{payments?.checkout?.enabled ? "true" : "false"}
RPC 数量{payments?.rpc?.configured_rpc_count ?? 0}
{payments?.checkout?.chain_id ?? "-"}
最后区块{payments?.event_loop_state?.last_scanned_block ?? "-"}
当前登录态 先确认管理员自己当前有没有会话。
authenticated{auth?.authenticated ? "true" : "false"}
email{auth?.email || "-"}
points{auth?.points ?? 0}
weekly_rank{auth?.weekly_rank ?? "-"}
EMOS 上线门禁 当前 shadow 到 primary 的发布判断。
{rolloutDecision?.decision || "hold"} ready={rolloutDecision?.ready_for_primary ? "true" : "false"}
阻塞原因
    {(rolloutDecision?.blocking_reasons || []).length ? ( (rolloutDecision?.blocking_reasons || []).map((reason) => (
  • {reason}
  • )) ) : (
  • 当前无阻塞项
  • )}
支付审计摘要 先展示最新几条事件,日常巡检够用。
RPC
{maskUrl(payments?.rpc?.active_rpc_url)}
Receiver Contract
{payments?.checkout?.receiver_contract || "-"}
{(payments?.recent_audit_events || []).slice(0, 6).map((item) => (
{item.event_type} #{item.id}
{formatDateTime(item.created_at)}
{item.payload?.reason ? (
原因: {item.payload.reason}
{item.payload.tx_hash ?
Tx: {maskUrl(item.payload.tx_hash)}
: null} {item.payload.receiver_actual ? (
实际收款: {maskUrl(item.payload.receiver_actual)}
) : null} {item.payload.receiver_expected ? (
期望收款: {maskUrl(item.payload.receiver_expected)}
) : null}
) : null}
))} {!payments?.recent_audit_events?.length ? (
暂无审计事件
) : null}
支付异常单 只显示已明确标记失败的支付确认事故。
{paymentIncidents.map((item) => ( ))} {!paymentIncidents.length ? ( ) : null}
时间 原因 套餐 Tx 实际收款 期望收款 操作
{formatDateTime(item.created_at)} {item.payload?.reason || "-"} {item.payload?.plan_code || "-"} {maskUrl(item.payload?.tx_hash)} {maskUrl(item.payload?.receiver_actual)} {maskUrl(item.payload?.receiver_expected)}
暂无支付异常单
当前会员 当前有效订阅用户、注册时间和到期时间。
当前有效会员数:{memberships.length}
{memberships.map((item) => ( ))} {!memberships.length ? ( ) : null}
邮箱 用户名 注册时间 到期时间
{item.email || "-"} {item.username || "-"} {formatDateTime(item.registered_at)} {formatDateTime(item.expires_at)}
暂无有效会员
用户查询 按 Telegram ID、用户名或 Supabase 邮箱搜用户。
setSearchQuery(event.target.value)} placeholder="telegram id / username / email" className="h-10 flex-1 rounded-xl border border-slate-800 bg-slate-950 px-3 text-sm text-slate-100 outline-none ring-0" />
{usersError ?
{usersError}
: null}
{users.map((user) => (
{user.username || "(未命名用户)"}
TG {user.telegram_id} · {user.supabase_email || "未绑定邮箱"}
总分 {user.points ?? 0} 周分 {user.weekly_points ?? 0} 发言 {user.message_count ?? 0}
今日积分 {user.daily_points ?? 0} · 最近发言 {formatDateTime(user.last_message_at)}
))} {!users.length && !usersLoading ? (
没有匹配用户
) : null}
积分运营 先做最小版:按 Supabase 邮箱手动补分。
setGrantEmail(event.target.value)} placeholder="user@example.com" className="h-10 w-full rounded-xl border border-slate-800 bg-slate-950 px-3 text-sm text-slate-100 outline-none ring-0" /> setGrantPoints(event.target.value)} placeholder="300" className="h-10 w-full rounded-xl border border-slate-800 bg-slate-950 px-3 text-sm text-slate-100 outline-none ring-0" />
{grantStatus ?
{grantStatus}
: null} {grantError ?
{grantError}
: null}
本周榜前 10
{(leaderboard || []).map((item, index) => (
#{index + 1} {item.username || "(未命名用户)"}
TG {item.telegram_id}
周分 {item.weekly_points ?? 0}
总分 {item.points ?? 0}
))} {!leaderboard.length ? (
当前没有周榜数据
) : null}
); }