feat(admin): 管理端接登录 + Bearer 鉴权 + 修雪花ID/探活(适配硬化后的网关)

/admin 加 RequireAdmin 后管理端原来无鉴权会 401。本次打通:
- api.ts:JWT token 存 localStorage + 所有 /admin 调用带 Bearer + 401 清令牌广播登出;
  login/me;gatewayOnline 改用公开 /healthz(原 /billing 已转受保护会误判离线)。
- 修类型:Model.id number → string(模型 id 早已迁雪花字符串)。
- Login 登录门 + App 鉴权门(启动校验 me,无则登录页)+ AppShell 显示用户/登出。

实测(硬化网关):无 token /admin/models → 401;登录拿 token → 200 返回模型(string id)。
dev 未配 ADMIN_USER_IDS 时任意登录账号放行;生产须在白名单。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-06-19 11:09:02 +08:00
parent 8f619c2a62
commit 030dcda9b4
4 changed files with 185 additions and 28 deletions
+11 -5
View File
@@ -2,10 +2,10 @@ import { Suspense, useEffect, useState } from "react";
import { NavLink, Routes, Route, Navigate, useLocation } from "react-router-dom";
import { routes, navGroups, defaultPath } from "../routes";
import { gatewayOnline } from "../api";
import { gatewayOnline, type AuthUser } from "../api";
// 控制台外壳:导航与内容均由路由注册表派生(动态路由)。
export function AppShell() {
export function AppShell({ user, onLogout }: { user: AuthUser; onLogout: () => void }) {
const [online, setOnline] = useState(false);
const loc = useLocation();
const current = routes.find((r) => r.path === loc.pathname);
@@ -45,9 +45,15 @@ export function AppShell() {
</div>
))}
</nav>
<div className="mt-auto flex items-center gap-2 border-t p-4 text-[11px] text-gray-500">
<span className={`h-2 w-2 rounded-full ${online ? "bg-emerald-500" : "bg-rose-500"}`} />
Gateway {online ? "在线" : "离线"}
<div className="mt-auto border-t p-4 text-[11px] text-gray-500">
<div className="mb-2 flex items-center justify-between">
<span className="truncate text-gray-600" title={user.email}>{user.name || user.email}</span>
<button onClick={onLogout} className="text-gray-400 hover:text-rose-600"></button>
</div>
<div className="flex items-center gap-2">
<span className={`h-2 w-2 rounded-full ${online ? "bg-emerald-500" : "bg-rose-500"}`} />
Gateway {online ? "在线" : "离线"}
</div>
</div>
</aside>