gitdataai/admin/src/lib/admin-rpc.ts
ZhenYi 8cf0e22893 feat(admin): daily platform email reports with AI analysis
- Admin API routes: recipients CRUD, AI/SMTP config, report generation
- UI page: dual-tab management (recipients + AI/SMTP settings)
- Auto table creation via CREATE TABLE IF NOT EXISTS
- AI summary via OpenAI Chat Completions API (stored in DB, not env)
- Direct SMTP sending via Node.js built-in net/tls (no nodemailer)
- Report includes: new users, rooms, commits, messages, top room + AI summary
- Sidebar navigation added for /admin/daily-report
2026-04-22 10:23:00 +08:00

131 lines
4.9 KiB
TypeScript

/**
* adminrpc HTTP REST client
*
* Calls the adminrpc HTTP server (default: http://adminrpc.admin.svc.cluster.local:9091)
* which exposes the same session management and metrics APIs as the gRPC service.
*
* Usage:
* import { listWorkspaceSessions, kickUser } from "@/lib/admin-rpc";
*/
import { ADMIN_RPC_URL } from "./env";
// Default to k8s internal service address; override via ADMIN_RPC_URL env var
const BASE_URL = ADMIN_RPC_URL || "http://adminrpc.admin.svc.cluster.local:9091";
async function rpc<T>(path: string, options?: RequestInit): Promise<T> {
const url = `${BASE_URL}${path}`;
const res = await fetch(url, {
...options,
headers: {
"Content-Type": "application/json",
...options?.headers,
},
});
if (!res.ok) {
const body = await res.text();
throw new Error(`adminrpc ${options?.method ?? "GET"} ${path} failed (${res.status}): ${body}`);
}
return res.json() as Promise<T>;
}
// ─── Types ────────────────────────────────────────────────────────────────────
export interface UserSession {
session_id: string;
user_id: string;
workspace_id: string;
ip_address: string;
user_agent: string;
connected_at: string;
last_heartbeat: string;
}
export interface SessionInfo {
user_id: string;
session_count: number;
workspaces: string[];
latest_session: UserSession | null;
}
// ─── Sessions API ─────────────────────────────────────────────────────────────
/** List all active sessions for a workspace. */
export async function listWorkspaceSessions(workspaceId: string): Promise<UserSession[]> {
return rpc<UserSession[]>(`/api/admin/sessions/workspace/${encodeURIComponent(workspaceId)}`);
}
/** List all active sessions for a specific user. */
export async function listUserSessions(userId: string): Promise<UserSession[]> {
return rpc<UserSession[]>(`/api/admin/sessions/user/${encodeURIComponent(userId)}`);
}
/** Get the online status of a specific user. */
export async function getUserStatus(userId: string): Promise<{ status: string }> {
return rpc<{ status: string }>(`/api/admin/sessions/user/${encodeURIComponent(userId)}/status`);
}
/** Get detailed info for a user (session count, workspaces, latest session). */
export async function getUserInfo(userId: string): Promise<SessionInfo | null> {
return rpc<SessionInfo | null>(`/api/admin/sessions/user/${encodeURIComponent(userId)}/info`);
}
/** List all online user IDs in a workspace. */
export async function getWorkspaceOnlineUsers(workspaceId: string): Promise<{ user_ids: string[] }> {
return rpc<{ user_ids: string[] }>(`/api/admin/sessions/workspace/${encodeURIComponent(workspaceId)}/online-users`);
}
/** Check if a user is currently online. */
export async function isUserOnline(userId: string): Promise<{ online: boolean }> {
return rpc<{ online: boolean }>(`/api/admin/sessions/user/${encodeURIComponent(userId)}/online`);
}
/** Kick all sessions for a user. Returns the number of sessions kicked. */
export async function kickUser(userId: string): Promise<{ kicked_count: number }> {
return rpc<{ kicked_count: number }>("/api/admin/sessions/kick", {
method: "POST",
body: JSON.stringify({ user_id: userId }),
});
}
/** Kick all sessions for a user in a specific workspace. */
export async function kickUserFromWorkspace(
userId: string,
workspaceId: string
): Promise<{ kicked_count: number }> {
return rpc<{ kicked_count: number }>("/api/admin/sessions/kick-workspace", {
method: "POST",
body: JSON.stringify({ user_id: userId, workspace_id: workspaceId }),
});
}
// ─── Metrics API ─────────────────────────────────────────────────────────────
export interface InstanceMetrics {
instance_id: string;
timestamp_secs: number;
http: Record<string, string>;
room: Record<string, string>;
}
/** Get metrics across all app instances. */
export async function getMetrics(instanceFilter = ""): Promise<InstanceMetrics[]> {
const qs = instanceFilter ? `?instance_filter=${encodeURIComponent(instanceFilter)}` : "";
return rpc<InstanceMetrics[]>(`/api/admin/metrics${qs}`);
}
/** Export all metrics as CSV string. */
export async function exportMetricsCsv(instanceFilter = ""): Promise<string> {
const qs = instanceFilter ? `?instance_filter=${encodeURIComponent(instanceFilter)}` : "";
const res = await fetch(`${BASE_URL}/api/admin/metrics/export${qs}`);
if (!res.ok) {
throw new Error(`adminrpc GET /metrics/export failed (${res.status})`);
}
return res.text();
}
/** Get adminrpc health status. */
export async function adminRpcHealth(): Promise<{ ok: boolean }> {
return rpc<{ ok: boolean }>("/health");
}