fix(adminrpc): expose HTTP port 9091 in k8s deployment and service
The adminrpc binary runs HTTP endpoints on port grpc_port+1 (9091), but k8s deployment only exposed port 9090 (gRPC). The /api/admin/* HTTP routes were unreachable from the admin dashboard frontend. - Add http container port 9091 to Deployment - Add http named port to k8s Service - Point liveness/readiness probes to HTTP port 9091 - Add http_port: 9091 to Helm values.yaml
This commit is contained in:
parent
f125fb0c02
commit
38da729860
@ -8,123 +8,123 @@
|
|||||||
* import { listWorkspaceSessions, kickUser } from "@/lib/admin-rpc";
|
* import { listWorkspaceSessions, kickUser } from "@/lib/admin-rpc";
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ADMIN_RPC_URL } from "./env";
|
import {ADMIN_RPC_URL} from "./env";
|
||||||
|
|
||||||
// Default to k8s internal service address; override via ADMIN_RPC_URL env var
|
// 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";
|
const BASE_URL = ADMIN_RPC_URL || "http://adminrpc.gitdataai.svc.cluster.local:9091";
|
||||||
|
|
||||||
async function rpc<T>(path: string, options?: RequestInit): Promise<T> {
|
async function rpc<T>(path: string, options?: RequestInit): Promise<T> {
|
||||||
const url = `${BASE_URL}${path}`;
|
const url = `${BASE_URL}${path}`;
|
||||||
const res = await fetch(url, {
|
const res = await fetch(url, {
|
||||||
...options,
|
...options,
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
...options?.headers,
|
...options?.headers,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const body = await res.text();
|
const body = await res.text();
|
||||||
throw new Error(`adminrpc ${options?.method ?? "GET"} ${path} failed (${res.status}): ${body}`);
|
throw new Error(`adminrpc ${options?.method ?? "GET"} ${path} failed (${res.status}): ${body}`);
|
||||||
}
|
}
|
||||||
return res.json() as Promise<T>;
|
return res.json() as Promise<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
export interface UserSession {
|
export interface UserSession {
|
||||||
session_id: string;
|
session_id: string;
|
||||||
user_id: string;
|
user_id: string;
|
||||||
workspace_id: string;
|
workspace_id: string;
|
||||||
ip_address: string;
|
ip_address: string;
|
||||||
user_agent: string;
|
user_agent: string;
|
||||||
connected_at: string;
|
connected_at: string;
|
||||||
last_heartbeat: string;
|
last_heartbeat: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SessionInfo {
|
export interface SessionInfo {
|
||||||
user_id: string;
|
user_id: string;
|
||||||
session_count: number;
|
session_count: number;
|
||||||
workspaces: string[];
|
workspaces: string[];
|
||||||
latest_session: UserSession | null;
|
latest_session: UserSession | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Sessions API ─────────────────────────────────────────────────────────────
|
// ─── Sessions API ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/** List all active sessions for a workspace. */
|
/** List all active sessions for a workspace. */
|
||||||
export async function listWorkspaceSessions(workspaceId: string): Promise<UserSession[]> {
|
export async function listWorkspaceSessions(workspaceId: string): Promise<UserSession[]> {
|
||||||
return rpc<UserSession[]>(`/api/admin/sessions/workspace/${encodeURIComponent(workspaceId)}`);
|
return rpc<UserSession[]>(`/api/admin/sessions/workspace/${encodeURIComponent(workspaceId)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** List all active sessions for a specific user. */
|
/** List all active sessions for a specific user. */
|
||||||
export async function listUserSessions(userId: string): Promise<UserSession[]> {
|
export async function listUserSessions(userId: string): Promise<UserSession[]> {
|
||||||
return rpc<UserSession[]>(`/api/admin/sessions/user/${encodeURIComponent(userId)}`);
|
return rpc<UserSession[]>(`/api/admin/sessions/user/${encodeURIComponent(userId)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get the online status of a specific user. */
|
/** Get the online status of a specific user. */
|
||||||
export async function getUserStatus(userId: string): Promise<{ status: string }> {
|
export async function getUserStatus(userId: string): Promise<{ status: string }> {
|
||||||
return rpc<{ status: string }>(`/api/admin/sessions/user/${encodeURIComponent(userId)}/status`);
|
return rpc<{ status: string }>(`/api/admin/sessions/user/${encodeURIComponent(userId)}/status`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get detailed info for a user (session count, workspaces, latest session). */
|
/** Get detailed info for a user (session count, workspaces, latest session). */
|
||||||
export async function getUserInfo(userId: string): Promise<SessionInfo | null> {
|
export async function getUserInfo(userId: string): Promise<SessionInfo | null> {
|
||||||
return rpc<SessionInfo | null>(`/api/admin/sessions/user/${encodeURIComponent(userId)}/info`);
|
return rpc<SessionInfo | null>(`/api/admin/sessions/user/${encodeURIComponent(userId)}/info`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** List all online user IDs in a workspace. */
|
/** List all online user IDs in a workspace. */
|
||||||
export async function getWorkspaceOnlineUsers(workspaceId: string): Promise<{ user_ids: string[] }> {
|
export async function getWorkspaceOnlineUsers(workspaceId: string): Promise<{ user_ids: string[] }> {
|
||||||
return rpc<{ user_ids: string[] }>(`/api/admin/sessions/workspace/${encodeURIComponent(workspaceId)}/online-users`);
|
return rpc<{ user_ids: string[] }>(`/api/admin/sessions/workspace/${encodeURIComponent(workspaceId)}/online-users`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Check if a user is currently online. */
|
/** Check if a user is currently online. */
|
||||||
export async function isUserOnline(userId: string): Promise<{ online: boolean }> {
|
export async function isUserOnline(userId: string): Promise<{ online: boolean }> {
|
||||||
return rpc<{ online: boolean }>(`/api/admin/sessions/user/${encodeURIComponent(userId)}/online`);
|
return rpc<{ online: boolean }>(`/api/admin/sessions/user/${encodeURIComponent(userId)}/online`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Kick all sessions for a user. Returns the number of sessions kicked. */
|
/** Kick all sessions for a user. Returns the number of sessions kicked. */
|
||||||
export async function kickUser(userId: string): Promise<{ kicked_count: number }> {
|
export async function kickUser(userId: string): Promise<{ kicked_count: number }> {
|
||||||
return rpc<{ kicked_count: number }>("/api/admin/sessions/kick", {
|
return rpc<{ kicked_count: number }>("/api/admin/sessions/kick", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({ user_id: userId }),
|
body: JSON.stringify({user_id: userId}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Kick all sessions for a user in a specific workspace. */
|
/** Kick all sessions for a user in a specific workspace. */
|
||||||
export async function kickUserFromWorkspace(
|
export async function kickUserFromWorkspace(
|
||||||
userId: string,
|
userId: string,
|
||||||
workspaceId: string
|
workspaceId: string
|
||||||
): Promise<{ kicked_count: number }> {
|
): Promise<{ kicked_count: number }> {
|
||||||
return rpc<{ kicked_count: number }>("/api/admin/sessions/kick-workspace", {
|
return rpc<{ kicked_count: number }>("/api/admin/sessions/kick-workspace", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({ user_id: userId, workspace_id: workspaceId }),
|
body: JSON.stringify({user_id: userId, workspace_id: workspaceId}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Metrics API ─────────────────────────────────────────────────────────────
|
// ─── Metrics API ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
export interface InstanceMetrics {
|
export interface InstanceMetrics {
|
||||||
instance_id: string;
|
instance_id: string;
|
||||||
timestamp_secs: number;
|
timestamp_secs: number;
|
||||||
http: Record<string, string>;
|
http: Record<string, string>;
|
||||||
room: Record<string, string>;
|
room: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get metrics across all app instances. */
|
/** Get metrics across all app instances. */
|
||||||
export async function getMetrics(instanceFilter = ""): Promise<InstanceMetrics[]> {
|
export async function getMetrics(instanceFilter = ""): Promise<InstanceMetrics[]> {
|
||||||
const qs = instanceFilter ? `?instance_filter=${encodeURIComponent(instanceFilter)}` : "";
|
const qs = instanceFilter ? `?instance_filter=${encodeURIComponent(instanceFilter)}` : "";
|
||||||
return rpc<InstanceMetrics[]>(`/api/admin/metrics${qs}`);
|
return rpc<InstanceMetrics[]>(`/api/admin/metrics${qs}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Export all metrics as CSV string. */
|
/** Export all metrics as CSV string. */
|
||||||
export async function exportMetricsCsv(instanceFilter = ""): Promise<string> {
|
export async function exportMetricsCsv(instanceFilter = ""): Promise<string> {
|
||||||
const qs = instanceFilter ? `?instance_filter=${encodeURIComponent(instanceFilter)}` : "";
|
const qs = instanceFilter ? `?instance_filter=${encodeURIComponent(instanceFilter)}` : "";
|
||||||
const res = await fetch(`${BASE_URL}/api/admin/metrics/export${qs}`);
|
const res = await fetch(`${BASE_URL}/api/admin/metrics/export${qs}`);
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw new Error(`adminrpc GET /metrics/export failed (${res.status})`);
|
throw new Error(`adminrpc GET /metrics/export failed (${res.status})`);
|
||||||
}
|
}
|
||||||
return res.text();
|
return res.text();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get adminrpc health status. */
|
/** Get adminrpc health status. */
|
||||||
export async function adminRpcHealth(): Promise<{ ok: boolean }> {
|
export async function adminRpcHealth(): Promise<{ ok: boolean }> {
|
||||||
return rpc<{ ok: boolean }>("/health");
|
return rpc<{ ok: boolean }>("/health");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,6 +35,9 @@ spec:
|
|||||||
- name: grpc
|
- name: grpc
|
||||||
containerPort: {{ .Values.adminrpc.service.port }}
|
containerPort: {{ .Values.adminrpc.service.port }}
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
|
- name: http
|
||||||
|
containerPort: {{ .Values.adminrpc.service.http_port }}
|
||||||
|
protocol: TCP
|
||||||
args:
|
args:
|
||||||
- --bind
|
- --bind
|
||||||
- "0.0.0.0:{{ .Values.adminrpc.service.port }}"
|
- "0.0.0.0:{{ .Values.adminrpc.service.port }}"
|
||||||
@ -43,12 +46,12 @@ spec:
|
|||||||
name: {{ include "gitdata.fullname" . }}-config
|
name: {{ include "gitdata.fullname" . }}-config
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
tcpSocket:
|
tcpSocket:
|
||||||
port: {{ .Values.adminrpc.service.port }}
|
port: {{ .Values.adminrpc.service.http_port }}
|
||||||
initialDelaySeconds: 5
|
initialDelaySeconds: 5
|
||||||
periodSeconds: 10
|
periodSeconds: 10
|
||||||
readinessProbe:
|
readinessProbe:
|
||||||
tcpSocket:
|
tcpSocket:
|
||||||
port: {{ .Values.adminrpc.service.port }}
|
port: {{ .Values.adminrpc.service.http_port }}
|
||||||
initialDelaySeconds: 5
|
initialDelaySeconds: 5
|
||||||
periodSeconds: 5
|
periodSeconds: 5
|
||||||
{{- if .Values.adminrpc.resources }}
|
{{- if .Values.adminrpc.resources }}
|
||||||
@ -83,6 +86,10 @@ spec:
|
|||||||
targetPort: grpc
|
targetPort: grpc
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
name: grpc
|
name: grpc
|
||||||
|
- port: {{ .Values.adminrpc.service.http_port }}
|
||||||
|
targetPort: http
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
selector:
|
selector:
|
||||||
app.kubernetes.io/name: {{ include "gitdata.fullname" . }}-adminrpc
|
app.kubernetes.io/name: {{ include "gitdata.fullname" . }}-adminrpc
|
||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
|||||||
@ -401,6 +401,7 @@ adminrpc:
|
|||||||
|
|
||||||
service:
|
service:
|
||||||
port: 9090
|
port: 9090
|
||||||
|
http_port: 9091
|
||||||
|
|
||||||
readinessProbe:
|
readinessProbe:
|
||||||
tcpSocket:
|
tcpSocket:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user