Polish HeroUI chrome, topology toolbar, and session client IP.

Checkpoint before workbench facade redesign: list defaults, API key quota, toast portal, topology canvas editor toolbar with More menu, and trusted-proxy client IP for sessions.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-12 15:10:57 +08:00
parent 5096117ad4
commit c4526e36d8
64 changed files with 6316 additions and 4521 deletions

View file

@ -1,3 +1,4 @@
import { Button } from "@heroui/react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useI18n } from "../i18n";
import { useToast } from "../hooks/useToast";
@ -15,6 +16,37 @@ type SessionRow = {
current: boolean;
};
/** Short browser/OS label so same NAT IP can still be told apart. */
function summarizeUa(ua: string): string {
const s = String(ua || "").trim();
if (!s) return "—";
const browser = /Edg\//.test(s)
? "Edge"
: /Chrome\//.test(s)
? "Chrome"
: /Firefox\//.test(s)
? "Firefox"
: /Safari\//.test(s) && !/Chrome\//.test(s)
? "Safari"
: /curl\//i.test(s)
? "curl"
: /python/i.test(s)
? "python"
: "Client";
const os = /Windows NT/i.test(s)
? "Windows"
: /Android/i.test(s)
? "Android"
: /iPhone|iPad/i.test(s)
? "iOS"
: /Mac OS X|Macintosh/i.test(s)
? "macOS"
: /Linux/i.test(s)
? "Linux"
: "";
return os ? `${browser} · ${os}` : browser;
}
export function SessionsPage() {
const { t } = useI18n();
const { showOk, showError } = useToast();
@ -54,16 +86,22 @@ export function SessionsPage() {
<div className="panel">
<div className="filter-inline" style={{ marginBottom: 12 }}>
<button
type="button"
disabled={revokeOthersMut.isPending || items.filter((s) => !s.current).length === 0}
onClick={() => revokeOthersMut.mutate()}
<Button
size="sm"
variant="secondary"
isDisabled={revokeOthersMut.isPending || items.filter((s) => !s.current).length === 0}
onPress={() => revokeOthersMut.mutate()}
>
{t("auth.revokeOtherSessions")}
</button>
<button type="button" onClick={() => void sessionsQuery.refetch()} disabled={sessionsQuery.isFetching}>
</Button>
<Button
size="sm"
variant="secondary"
onPress={() => void sessionsQuery.refetch()}
isDisabled={sessionsQuery.isFetching}
>
{t("common.refresh")}
</button>
</Button>
</div>
{sessionsQuery.isLoading ? <p className="muted">{t("common.refreshing")}</p> : null}
@ -76,6 +114,7 @@ export function SessionsPage() {
<tr>
<th>{t("auth.colSession")}</th>
<th>{t("auth.colIp")}</th>
<th>{t("auth.colClient")}</th>
<th>{t("auth.colLastSeen")}</th>
<th>{t("auth.colCreated")}</th>
<th>{t("auth.actions")}</th>
@ -85,27 +124,31 @@ export function SessionsPage() {
{items.map((row) => (
<tr key={row.id}>
<td>
<code title={row.user_agent}>{row.id.slice(0, 10)}…</code>
<code title={row.id}>{row.id.slice(0, 10)}…</code>
{row.current ? (
<span className="pt-list-status pt-list-status--ok" style={{ marginLeft: 8 }}>
{t("auth.sessionCurrent")}
</span>
) : null}
</td>
<td>{row.client_ip || "—"}</td>
<td>
<code>{row.client_ip || "—"}</code>
</td>
<td title={row.user_agent || undefined}>{summarizeUa(row.user_agent)}</td>
<td>{row.last_seen_at ? formatSystemTime(row.last_seen_at) : "—"}</td>
<td>{row.created_at ? formatSystemTime(row.created_at) : "—"}</td>
<td>
<button
type="button"
disabled={revokeMut.isPending}
onClick={() => {
<Button
size="sm"
variant="secondary"
isDisabled={revokeMut.isPending}
onPress={() => {
if (row.current && !window.confirm(t("auth.revokeCurrentConfirm"))) return;
revokeMut.mutate(row.id);
}}
>
{t("auth.revokeSession")}
</button>
</Button>
</td>
</tr>
))}