Merge pull request 'Discord admin page + hide admin-only bot pool from operators (#213 items 2, 4)' (#219) from feat/213-discord-admin into main
Build & Deploy / Build & push images (push) Successful in 5m1s
Build & Deploy / Deploy Firestore rules & indexes (push) Successful in 5s
Build & Deploy / Deploy to VM (push) Successful in 7s
Build & Deploy / Report a failed deploy (push) Skipped

Reviewed-on: #219
This commit was merged in pull request #219.
This commit is contained in:
2026-10-04 22:05:36 -04:00
13 changed files with 1510 additions and 173 deletions
+31 -3
View File
@@ -1,5 +1,6 @@
import { auth } from "@/lib/firebase";
import type { AreaContext, CameraProviderAdmin, CamerasResponse, Ny511Response, TalkgroupPending } from "@/lib/types";
import type { DiscordAdminOverview, DiscordStream, RadioBot, RadioBotAddResult } from "@/lib/discord";
const BASE = process.env.NEXT_PUBLIC_C2_URL ?? "http://localhost:8000";
@@ -55,12 +56,39 @@ export const c2api = {
deleteSystem: (id: string) =>
request(`/systems/${id}`, { method: "DELETE" }),
// Tokens
// Tokens (radio bots) — every route is platform-admin only (server-26#204 item 6)
getTokens: () => request<unknown[]>("/tokens"),
addToken: (body: { name: string; token: string }) =>
request("/tokens", { method: "POST", body: JSON.stringify(body) }),
addToken: (body: { name: string; token: string; org_id?: string | null }) =>
request<RadioBotAddResult>("/tokens", { method: "POST", body: JSON.stringify(body) }),
deleteToken: (id: string) =>
request(`/tokens/${id}`, { method: "DELETE" }),
/** Re-check one radio bot with Discord: token valid? which servers is it in? */
refreshToken: (id: string) =>
request<RadioBot>(`/tokens/${id}/refresh`, { method: "POST" }),
/** Admin last resort: force-release every radio-bot lease. */
flushTokens: () =>
request<{ released: number }>("/tokens/flush", { method: "POST" }),
// Discord radio channels — web admin page (server-26#213 item 2)
getDiscordAdmin: () => request<DiscordAdminOverview>("/discord/admin/overview"),
linkDiscordGuild: (guildId: string, body: { org_id: string; guild_name?: string }) =>
request<{ ok: boolean; stopped: string[] }>(`/discord/guilds/${encodeURIComponent(guildId)}/link`, {
method: "PUT",
body: JSON.stringify(body),
}),
startDiscordStream: (body: {
guild_id: string;
channel_id: string;
system_id: string;
mode: "persistent" | "adhoc";
channel_name?: string;
preferred_token_id?: string;
}) => request<DiscordStream>("/discord/streams", { method: "POST", body: JSON.stringify(body) }),
stopDiscordStream: (guildId: string, channelId: string) =>
request<{ stopped: string[] }>(`/discord/guilds/${encodeURIComponent(guildId)}/stop`, {
method: "POST",
body: JSON.stringify({ channel_id: channelId, reason: "stopped from web admin" }),
}),
// Node approval
approveNode: (id: string) =>