Compare commits
No commits in common. "d54841fdb42d623d7d70fe4bad316cb92dcc23ce" and "af0b0e450b831e1b45cd712fbac179415bb69ac4" have entirely different histories.
d54841fdb4
...
af0b0e450b
10
src/App.tsx
10
src/App.tsx
@ -1,8 +1,6 @@
|
|||||||
import { useReducer, useState } from "react";
|
import { useReducer, useState } from "react";
|
||||||
import { SessionQueryIntent, SessionTable } from "./SessionTable";
|
import { SessionQueryIntent, SessionTable } from "./SessionTable";
|
||||||
import { demoSessions } from "./protocol/fixtures";
|
|
||||||
import { initializeSessionTableStates, updateSessionTableState } from "./sessionTableState";
|
import { initializeSessionTableStates, updateSessionTableState } from "./sessionTableState";
|
||||||
import { toSessionRow } from "./sessionPresentation";
|
|
||||||
import {
|
import {
|
||||||
createInitialWorkspaceState,
|
createInitialWorkspaceState,
|
||||||
selectActiveTab,
|
selectActiveTab,
|
||||||
@ -10,8 +8,12 @@ import {
|
|||||||
workspaceReducer,
|
workspaceReducer,
|
||||||
} from "./workspace";
|
} from "./workspace";
|
||||||
|
|
||||||
const fixtureNow = new Date("2026-08-03T19:12:02Z");
|
const sessions = [
|
||||||
const sessions = demoSessions.map((session) => toSessionRow(session, fixtureNow));
|
{ id: "NW-042", status: "ONLINE", host: "ws-fin-042", user: "nora.chen", os: "Windows 11", address: "10.42.8.17", seen: "14s ago", tags: ["finance", "priority"] },
|
||||||
|
{ id: "NW-038", status: "ONLINE", host: "srv-app-03", user: "svc.deploy", os: "Ubuntu 24.04", address: "10.42.3.8", seen: "51s ago", tags: ["server"] },
|
||||||
|
{ id: "NW-031", status: "IDLE", host: "mac-design-07", user: "alex.k", os: "macOS 15.6", address: "10.42.12.27", seen: "8m ago", tags: ["design"] },
|
||||||
|
{ id: "NW-019", status: "OFFLINE", host: "ws-ops-019", user: "operator", os: "Windows 10", address: "10.42.6.91", seen: "2h ago", tags: ["legacy"] },
|
||||||
|
];
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
const [workspace, dispatch] = useReducer(workspaceReducer, undefined, createInitialWorkspaceState);
|
const [workspace, dispatch] = useReducer(workspaceReducer, undefined, createInitialWorkspaceState);
|
||||||
|
|||||||
@ -1,43 +1,18 @@
|
|||||||
import type { CapabilityId, ProjectId, ServerId, Session, SessionId, Timestamp } from "../model";
|
import type { Session } from "../model";
|
||||||
|
|
||||||
const serverId = "atlas" as ServerId;
|
/** UI 迁移期可直接使用的最小 Session fixture。 */
|
||||||
const projectId = "northwind" as ProjectId;
|
export const demoSession = {
|
||||||
const overview = "session.overview" as CapabilityId;
|
server_id: "demo-server",
|
||||||
|
project_id: "demo-project",
|
||||||
interface DemoSessionInput {
|
session_id: "demo-session",
|
||||||
readonly session_id: string;
|
name: "演示会话",
|
||||||
readonly name: string;
|
state: "ONLINE",
|
||||||
readonly state: Session["state"];
|
hostname: "demo-host",
|
||||||
readonly hostname?: string;
|
username: "operator",
|
||||||
readonly username?: string;
|
os: "linux",
|
||||||
readonly os?: string;
|
architecture: "amd64",
|
||||||
readonly architecture?: string;
|
first_seen_at: "2026-08-03T00:00:00Z",
|
||||||
readonly source_address?: string;
|
last_active_at: "2026-08-03T00:00:05Z",
|
||||||
readonly first_seen_at: string;
|
tags: ["fixture"],
|
||||||
readonly last_active_at: string;
|
capabilities: ["session.overview"],
|
||||||
readonly tags: readonly string[];
|
} as unknown as Session;
|
||||||
}
|
|
||||||
|
|
||||||
function session(
|
|
||||||
value: DemoSessionInput,
|
|
||||||
): Session {
|
|
||||||
return {
|
|
||||||
...value,
|
|
||||||
server_id: serverId,
|
|
||||||
project_id: projectId,
|
|
||||||
session_id: value.session_id as SessionId,
|
|
||||||
first_seen_at: value.first_seen_at as Timestamp,
|
|
||||||
last_active_at: value.last_active_at as Timestamp,
|
|
||||||
capabilities: [overview],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 界面只通过展示适配器消费的标准协议 Session fixture。 */
|
|
||||||
export const demoSessions: readonly Session[] = [
|
|
||||||
session({ session_id: "NW-042", name: "NW-042", state: "ONLINE", hostname: "ws-fin-042", username: "nora.chen", os: "Windows 11", architecture: "amd64", source_address: "10.42.8.17", first_seen_at: "2026-08-01T08:00:00Z", last_active_at: "2026-08-03T19:11:48Z", tags: ["finance", "priority"] }),
|
|
||||||
session({ session_id: "NW-038", name: "NW-038", state: "ONLINE", hostname: "srv-app-03", username: "svc.deploy", os: "Ubuntu 24.04", architecture: "amd64", source_address: "10.42.3.8", first_seen_at: "2026-07-28T10:30:00Z", last_active_at: "2026-08-03T19:11:11Z", tags: ["server"] }),
|
|
||||||
session({ session_id: "NW-031", name: "NW-031", state: "DORMANT", hostname: "mac-design-07", username: "alex.k", os: "macOS 15.6", architecture: "arm64", source_address: "10.42.12.27", first_seen_at: "2026-07-22T09:15:00Z", last_active_at: "2026-08-03T19:04:00Z", tags: ["design"] }),
|
|
||||||
session({ session_id: "NW-019", name: "NW-019", state: "OFFLINE", hostname: "ws-ops-019", username: "operator", os: "Windows 10", architecture: "amd64", source_address: "10.42.6.91", first_seen_at: "2026-07-10T14:00:00Z", last_active_at: "2026-08-03T17:12:00Z", tags: ["legacy"] }),
|
|
||||||
];
|
|
||||||
|
|
||||||
export const demoSession = demoSessions[0];
|
|
||||||
|
|||||||
@ -86,7 +86,6 @@ export interface Session extends SessionContext, Extensible {
|
|||||||
readonly username?: string;
|
readonly username?: string;
|
||||||
readonly os?: string;
|
readonly os?: string;
|
||||||
readonly architecture?: string;
|
readonly architecture?: string;
|
||||||
readonly source_address?: string;
|
|
||||||
readonly first_seen_at: Timestamp;
|
readonly first_seen_at: Timestamp;
|
||||||
readonly last_active_at: Timestamp;
|
readonly last_active_at: Timestamp;
|
||||||
readonly tags: readonly string[];
|
readonly tags: readonly string[];
|
||||||
|
|||||||
@ -1,18 +0,0 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
|
||||||
import { demoSessions } from "./protocol/fixtures";
|
|
||||||
import { toSessionRow } from "./sessionPresentation";
|
|
||||||
|
|
||||||
describe("toSessionRow", () => {
|
|
||||||
it("将协议 Session 转换为稳定的表格展示值", () => {
|
|
||||||
expect(toSessionRow(demoSessions[0], new Date("2026-08-03T19:12:02Z"))).toEqual({
|
|
||||||
id: "NW-042",
|
|
||||||
status: "ONLINE",
|
|
||||||
host: "ws-fin-042",
|
|
||||||
user: "nora.chen",
|
|
||||||
os: "Windows 11 / amd64",
|
|
||||||
address: "10.42.8.17",
|
|
||||||
seen: "14s ago",
|
|
||||||
tags: ["finance", "priority"],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
import type { Session } from "./protocol/model";
|
|
||||||
import type { SessionRow } from "./SessionTable";
|
|
||||||
|
|
||||||
/** 将服务端权威模型转换为表格展示值,不在 UI 复制业务模型。 */
|
|
||||||
export function toSessionRow(session: Session, now: Date = new Date()): SessionRow {
|
|
||||||
return {
|
|
||||||
id: session.session_id,
|
|
||||||
status: session.state,
|
|
||||||
host: session.hostname ?? session.name,
|
|
||||||
user: session.username ?? "—",
|
|
||||||
os: [session.os, session.architecture].filter(Boolean).join(" / ") || "未知",
|
|
||||||
address: session.source_address ?? "—",
|
|
||||||
seen: formatRelativeTime(session.last_active_at, now),
|
|
||||||
tags: [...session.tags],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatRelativeTime(timestamp: string, now: Date): string {
|
|
||||||
const elapsedSeconds = Math.max(0, Math.floor((now.getTime() - Date.parse(timestamp)) / 1000));
|
|
||||||
if (elapsedSeconds < 60) return `${elapsedSeconds}s ago`;
|
|
||||||
if (elapsedSeconds < 3600) return `${Math.floor(elapsedSeconds / 60)}m ago`;
|
|
||||||
if (elapsedSeconds < 86400) return `${Math.floor(elapsedSeconds / 3600)}h ago`;
|
|
||||||
return `${Math.floor(elapsedSeconds / 86400)}d ago`;
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user