Compare commits

...

10 Commits

Author SHA1 Message Date
Logan Cusano e6bb4ddf7e Updated theme toggle to go through all three options and update dark mode colors
Lint Pull Request / lint (push) Successful in 40s
release-image / release-image (push) Successful in 14m30s
2025-07-06 20:53:15 -04:00
Logan Cusano 9506f6246c Fix copyright date 2025-07-06 20:52:44 -04:00
Logan Cusano e64984b7b8 Include the system ID when starting the bot
Lint Pull Request / lint (push) Successful in 1m0s
release-image / release-image (push) Has been cancelled
2025-07-06 19:35:00 -04:00
Logan Cusano 20187a06f0 Update invite link
Lint Pull Request / lint (push) Successful in 30s
release-image / release-image (push) Successful in 10m39s
2025-06-29 22:49:41 -04:00
Logan Cusano 464f37adda Implement active client on status
Lint Pull Request / lint (push) Successful in 20s
release-image / release-image (push) Has been cancelled
2025-06-29 22:42:20 -04:00
Logan Cusano a1fda6fe87 Add invite button for the bot tokens
Lint Pull Request / lint (push) Successful in 23s
release-image / release-image (push) Successful in 10m15s
2025-06-29 22:13:47 -04:00
Logan Cusano aaa362ec0e Update metadata
Lint Pull Request / lint (push) Successful in 22s
release-image / release-image (push) Successful in 10m16s
2025-06-29 22:00:34 -04:00
Logan Cusano 3b62590df3 Implement Fixed Node Management
Lint Pull Request / lint (push) Successful in 23s
release-image / release-image (push) Successful in 11m52s
2025-06-29 19:38:45 -04:00
Logan Cusano 8373fdd685 Implement nickname for table
Lint Pull Request / lint (push) Successful in 22s
release-image / release-image (push) Successful in 10m26s
2025-06-29 03:46:43 -04:00
logan 6a74cd55a0 Merge pull request 'Refactored the header' (#2) from refactoring-header into main
Lint Pull Request / lint (push) Successful in 20s
release-image / release-image (push) Successful in 10m23s
Reviewed-on: #2
2025-05-28 23:20:07 -04:00
6 changed files with 271 additions and 198 deletions
+34 -34
View File
@@ -85,42 +85,42 @@
} }
.dark { .dark {
/* Dark Mode Palette - Modernized */ /* Dracula Dark Mode Palette */
--background: oklch(0.18 0 0); /* Slightly lighter dark charcoal */ --background: oklch(0.17 0.005 270); /* Dracula background: #282a36 */
--foreground: oklch(0.88 0 0); /* Soft white/light gray */ --foreground: oklch(0.95 0.01 270); /* Dracula foreground: #f8f8f2 */
--card: oklch(0.22 0 0); /* Slightly lighter for cards than background */ --card: oklch(0.2 0.005 270); /* Slightly lighter for cards */
--card-foreground: oklch(0.88 0 0); --card-foreground: oklch(0.95 0.01 270);
--popover: oklch(0.22 0 0); --popover: oklch(0.2 0.005 270);
--popover-foreground: oklch(0.88 0 0); --popover-foreground: oklch(0.95 0.01 270);
--primary: oklch(0.65 0.18 260); /* Brighter, more vibrant indigo */ --primary: oklch(0.6 0.25 270); /* Dracula purple: #bd93f9 */
--primary-foreground: oklch(0.18 0 0); /* Darker text on primary */ --primary-foreground: oklch(0.17 0.005 270); /* Text on primary */
--secondary: oklch(0.28 0 0); /* Medium dark gray */ --secondary: oklch(0.25 0.005 270); /* Dracula current line/selection: #44475a */
--secondary-foreground: oklch(0.75 0 0); /* Lighter gray */ --secondary-foreground: oklch(0.75 0.01 270); /* Lighter gray */
--muted: oklch(0.28 0 0); --muted: oklch(0.25 0.005 270);
--muted-foreground: oklch(0.55 0 0); /* Mid-gray */ --muted-foreground: oklch(0.55 0.005 270); /* Dracula comment: #6272a4 */
--accent: oklch(0.28 0.04 260); /* Subtle dark hint of primary */ --accent: oklch(0.3 0.05 270); /* Subtle hint of primary */
--accent-foreground: oklch(0.7 0.06 260); /* Lighter desaturated primary */ --accent-foreground: oklch(0.7 0.05 270); /* Lighter desaturated primary */
--destructive: oklch(0.65 0.16 20); /* Brighter red */ --destructive: oklch(0.7 0.2 0); /* Dracula red: #ff5555 */
--border: oklch(0.32 0 0); /* Medium dark gray */ --border: oklch(0.3 0.005 270); /* Dracula border: #44475a (similar to secondary) */
--input: oklch(0.28 0 0); /* Darker than border */ --input: oklch(0.25 0.005 270); /* Darker than border */
--ring: oklch(0.5 0 0); /* Mid-dark gray */ --ring: oklch(0.5 0.005 270); /* Mid-dark gray */
/* Chart Colors (Dark Mode) - Brighter and distinct for visibility */ /* Chart Colors (Dracula Dark Mode) - Vibrant and distinct */
--chart-1: oklch(0.75 0.2 270); /* Brighter purple-blue */ --chart-1: oklch(0.7 0.2 270); /* Dracula purple: #bd93f9 */
--chart-2: oklch(0.8 0.18 180); /* Brighter teal */ --chart-2: oklch(0.7 0.2 120); /* Dracula green: #50fa7b */
--chart-3: oklch(0.85 0.15 90); /* Brighter muted green-yellow */ --chart-3: oklch(0.7 0.2 60); /* Dracula yellow: #f1fa8c */
--chart-4: oklch(0.8 0.18 30); /* Brighter orange-brown */ --chart-4: oklch(0.7 0.2 30); /* Dracula orange: #ffb86c */
--chart-5: oklch(0.9 0.12 330); /* Brighter muted pink */ --chart-5: oklch(0.7 0.2 330); /* Dracula pink: #ff79c6 */
/* Sidebar Colors (Dark Mode) */ /* Sidebar Colors (Dracula Dark Mode) */
--sidebar: oklch(0.22 0 0); /* Slightly lighter dark for sidebar */ --sidebar: oklch(0.2 0.005 270); /* Slightly lighter dark for sidebar */
--sidebar-foreground: oklch(0.88 0 0); --sidebar-foreground: oklch(0.95 0.01 270);
--sidebar-primary: oklch(0.65 0.18 260); --sidebar-primary: oklch(0.6 0.25 270);
--sidebar-primary-foreground: oklch(0.18 0 0); --sidebar-primary-foreground: oklch(0.17 0.005 270);
--sidebar-accent: oklch(0.28 0.04 260); --sidebar-accent: oklch(0.3 0.05 270);
--sidebar-accent-foreground: oklch(0.7 0.06 260); --sidebar-accent-foreground: oklch(0.7 0.05 270);
--sidebar-border: oklch(0.32 0 0); --sidebar-border: oklch(0.3 0.005 270);
--sidebar-ring: oklch(0.5 0 0); --sidebar-ring: oklch(0.5 0.005 270);
} }
@layer base { @layer base {
+3 -3
View File
@@ -19,8 +19,8 @@ const geistMono = Geist_Mono({
}); });
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Create Next App", title: "Discord Radio Bot Manager",
description: "Generated by create next app", description: "Manage the fleet of DRB nodes.",
}; };
export default function RootLayout({ export default function RootLayout({
@@ -46,7 +46,7 @@ export default function RootLayout({
<footer> <footer>
<div className="max-w-7xl mx-auto py-8 px-4 sm:px-6 lg:px-8 flex flex-col sm:flex-row justify-between items-center text-sm text-gray-500"> <div className="max-w-7xl mx-auto py-8 px-4 sm:px-6 lg:px-8 flex flex-col sm:flex-row justify-between items-center text-sm text-gray-500">
<div className="mb-4 sm:mb-0 text-center sm:text-left"> <div className="mb-4 sm:mb-0 text-center sm:text-left">
&copy; 2025 Logan Cusano. All rights reserved. &copy; 2015 - {new Date().getFullYear()} Logan Cusano. All rights reserved.
</div> </div>
<div className="flex space-x-6"> <div className="flex space-x-6">
<a href="https://git.vpn.cusano.net/logan/drb-frontend/issues" target="_blank" rel="noopener noreferrer" className="hover:text-gray-700">Submit Issues</a> <a href="https://git.vpn.cusano.net/logan/drb-frontend/issues" target="_blank" rel="noopener noreferrer" className="hover:text-gray-700">Submit Issues</a>
+29 -11
View File
@@ -11,7 +11,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Checkbox } from '@/components/ui/checkbox'; import { Checkbox } from '@/components/ui/checkbox';
import { DRB_BASE_API_URL } from '@/constants/api'; import { DRB_BASE_API_URL } from '@/constants/api';
import { DiscordId, System, ErrorResponse } from '@/types'; import { DiscordId, System, ErrorResponse, ActiveClient } from '@/types';
import { authAwareFetch } from '@/utils/AuthAwareFetch'; import { authAwareFetch } from '@/utils/AuthAwareFetch';
interface BotsManagementProps { interface BotsManagementProps {
@@ -20,7 +20,7 @@ interface BotsManagementProps {
} }
const BotsManagement: React.FC<BotsManagementProps> = ({ token, logoutUser }) => { const BotsManagement: React.FC<BotsManagementProps> = ({ token, logoutUser }) => {
const [bots, setBots] = useState<string[]>([]); const [bots, setBots] = useState<ActiveClient[]>([]);
const [discordIds, setDiscordIds] = useState<DiscordId[]>([]); const [discordIds, setDiscordIds] = useState<DiscordId[]>([]);
const [systems, setSystems] = useState<System[]>([]); const [systems, setSystems] = useState<System[]>([]);
const [loading, setLoading] = useState<boolean>(true); // Set to true initially const [loading, setLoading] = useState<boolean>(true); // Set to true initially
@@ -61,7 +61,7 @@ const BotsManagement: React.FC<BotsManagementProps> = ({ token, logoutUser }) =>
]); ]);
if (nodesRes.ok) { if (nodesRes.ok) {
const botsData: any = await nodesRes.json(); const botsData: ActiveClient[] = await nodesRes.json();
setBots(botsData); setBots(botsData);
} else { } else {
let errorMsg = `Failed to fetch bots (${nodesRes.status})`; let errorMsg = `Failed to fetch bots (${nodesRes.status})`;
@@ -155,7 +155,7 @@ const BotsManagement: React.FC<BotsManagementProps> = ({ token, logoutUser }) =>
if (response.ok) { if (response.ok) {
fetchData(); fetchData();
setIsAddIdDialogOpen(false); setIsAddIdDialogOpen(false);
setNewIdData({ discord_id: '', name: '', token: '', active: false, guild_ids: '' }); setNewIdData({ discord_id: '', name: '', token: '', active: false, guild_ids: '', fixed_node: '' });
setEditingId(null); setEditingId(null);
} else { } else {
let errorMsg = `Failed to ${editingId ? 'update' : 'add'} Discord ID (${response.status})`; let errorMsg = `Failed to ${editingId ? 'update' : 'add'} Discord ID (${response.status})`;
@@ -270,12 +270,12 @@ const BotsManagement: React.FC<BotsManagementProps> = ({ token, logoutUser }) =>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{bots.length > 0 ? ( {bots.length > 0 ? (
bots.map((botClientId) => { bots.map((active_bot) => {
return ( return (
<TableRow key={botClientId}> <TableRow key={active_bot.client_id}>
<TableCell className="font-medium"> <TableCell className="font-medium">
<Link href={`/nodes/${botClientId}`} className="text-blue-600 hover:underline"> <Link href={`/nodes/${active_bot.client_id}`} className="text-blue-600 hover:underline">
{botClientId} {active_bot.nickname} ({active_bot.client_id})
</Link> </Link>
</TableCell> </TableCell>
</TableRow> </TableRow>
@@ -292,7 +292,7 @@ const BotsManagement: React.FC<BotsManagementProps> = ({ token, logoutUser }) =>
<div> <div>
<h3 className="text-lg font-semibold mb-2">Manage Discord IDs</h3> <h3 className="text-lg font-semibold mb-2">Manage Discord IDs</h3>
<Button onClick={() => { setEditingId(null); setNewIdData({ discord_id: '', name: '', token: '', active: false, guild_ids: '' }); setIsAddIdDialogOpen(true);}} className="mb-4" disabled={!token}>Add New Discord ID</Button> <Button onClick={() => { setEditingId(null); setNewIdData({ discord_id: '', name: '', token: '', active: false, guild_ids: '', fixed_node: '' }); setIsAddIdDialogOpen(true); }} className="mb-4" disabled={!token}>Add New Discord ID</Button>
<Button onClick={() => setIsAssignDismissDialogOpen(true)} className="mb-4 ml-2" disabled={!token}>Assign/Dismiss Bot System</Button> <Button onClick={() => setIsAssignDismissDialogOpen(true)} className="mb-4 ml-2" disabled={!token}>Assign/Dismiss Bot System</Button>
<Table> <Table>
@@ -303,6 +303,7 @@ const BotsManagement: React.FC<BotsManagementProps> = ({ token, logoutUser }) =>
<TableHead>Token (Partial)</TableHead> <TableHead>Token (Partial)</TableHead>
<TableHead>Active</TableHead> <TableHead>Active</TableHead>
<TableHead>Guilds</TableHead> <TableHead>Guilds</TableHead>
<TableHead>Fixed Node ID</TableHead>
<TableHead>Actions</TableHead> <TableHead>Actions</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
@@ -315,7 +316,14 @@ const BotsManagement: React.FC<BotsManagementProps> = ({ token, logoutUser }) =>
<TableCell className="truncate max-w-xs">{dId.token ? dId.token.substring(0, 8) + '...' : 'N/A'}</TableCell> <TableCell className="truncate max-w-xs">{dId.token ? dId.token.substring(0, 8) + '...' : 'N/A'}</TableCell>
<TableCell>{dId.active ? 'Yes' : 'No'}</TableCell> <TableCell>{dId.active ? 'Yes' : 'No'}</TableCell>
<TableCell>{dId.guild_ids?.join(', ')}</TableCell> <TableCell>{dId.guild_ids?.join(', ')}</TableCell>
<TableCell>{dId?.fixed_node ? dId?.fixed_node : "None"}</TableCell>
<TableCell> <TableCell>
<a target="_blank" rel="noopener noreferrer" href={`https://discord.com/oauth2/authorize?client_id=${dId?.discord_id}&permissions=3146752&response_type=code&integration_type=0&scope=bot+rpc.voice.read+rpc.voice.write+rpc+voice+guilds.channels.read+guilds+guilds.join+gdm.join+guilds.members.read+connections`}>
<Button variant="outline" size="sm" className="mr-2"
disabled={!dId?.discord_id}>
Invite
</Button>
</a>
<Button variant="outline" size="sm" className="mr-2" <Button variant="outline" size="sm" className="mr-2"
onClick={() => { onClick={() => {
setEditingId(dId); setEditingId(dId);
@@ -325,6 +333,7 @@ const BotsManagement: React.FC<BotsManagementProps> = ({ token, logoutUser }) =>
token: dId.token, token: dId.token,
active: dId.active, active: dId.active,
guild_ids: dId.guild_ids?.join(', '), guild_ids: dId.guild_ids?.join(', '),
fixed_node: dId.fixed_node,
}); });
setIsAddIdDialogOpen(true); setIsAddIdDialogOpen(true);
}} }}
@@ -389,6 +398,15 @@ const BotsManagement: React.FC<BotsManagementProps> = ({ token, logoutUser }) =>
className="col-span-3" className="col-span-3"
/> />
</div> </div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="guild_ids" className="text-right">Fixed Node</Label>
<Input
id="fixed_node"
value={newIdData?.fixed_node}
onChange={(e) => setNewIdData({ ...newIdData, fixed_node: e.target.value })}
className="col-span-3"
/>
</div>
<div className="grid grid-cols-4 items-center gap-4"> <div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="active" className="text-right">Active</Label> <Label htmlFor="active" className="text-right">Active</Label>
<Checkbox <Checkbox
@@ -420,8 +438,8 @@ const BotsManagement: React.FC<BotsManagementProps> = ({ token, logoutUser }) =>
<SelectValue placeholder="Select a bot client ID" /> <SelectValue placeholder="Select a bot client ID" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{bots.map((botId) => ( {bots.map((active_bot) => (
<SelectItem key={botId} value={botId}>{botId}</SelectItem> <SelectItem key={active_bot.client_id} value={active_bot.client_id}>{active_bot.client_id}</SelectItem>
))} ))}
</SelectContent> </SelectContent>
</Select> </Select>
+25 -5
View File
@@ -3,7 +3,7 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'; import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
import { ErrorResponse, NodeStatusResponse, System } from '@/types'; import { ActiveClient, ErrorResponse, NodeStatusResponse, System } from '@/types';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
@@ -37,6 +37,9 @@ const IndividualClientPage: React.FC<IndividualClientPageProps> = ({ clientId, t
const [isLeaveDiscordDialogOpen, setIsLeaveDiscordDialogOpen] = useState<boolean>(false); const [isLeaveDiscordDialogOpen, setIsLeaveDiscordDialogOpen] = useState<boolean>(false);
const [leaveGuildId, setLeaveGuildId] = useState<string>(''); const [leaveGuildId, setLeaveGuildId] = useState<string>('');
// Active Client States
const [activeClient, setActiveClient] = useState<ActiveClient | null>(null);
const fetchClientStatus = async () => { const fetchClientStatus = async () => {
if (!token) { if (!token) {
@@ -51,6 +54,7 @@ const IndividualClientPage: React.FC<IndividualClientPageProps> = ({ clientId, t
if (response.ok) { if (response.ok) {
const data: NodeStatusResponse = await response.json(); const data: NodeStatusResponse = await response.json();
console.log(data) console.log(data)
setActiveClient(data.active_client);
setCurrentClientDiscordStatus(data.status.discord_status); setCurrentClientDiscordStatus(data.status.discord_status);
setCurrentClientOp25Status(data.status.op25_status) setCurrentClientOp25Status(data.status.op25_status)
} else { } else {
@@ -146,14 +150,15 @@ const IndividualClientPage: React.FC<IndividualClientPageProps> = ({ clientId, t
"system_id": selectedSystem "system_id": selectedSystem
}); });
} else if (action === "join") { } else if (action === "join") {
if (!payloadData?.server_id || !payloadData?.channel_id) { if (!payloadData?.server_id || !payloadData?.channel_id || !selectedSystem) {
setError("Please provide both Discord Server ID and Channel ID."); setError("Please provide both Discord Server ID and Channel ID.");
setLoading(false); setLoading(false);
return; return;
} }
httpOptions.body = JSON.stringify({ httpOptions.body = JSON.stringify({
"guild_id": payloadData.server_id, "guild_id": payloadData.server_id,
"channel_id": payloadData.channel_id "channel_id": payloadData.channel_id,
"system_id": selectedSystem
}); });
} else if (action === "leave") { } else if (action === "leave") {
if (!payloadData?.guild_id) { if (!payloadData?.guild_id) {
@@ -215,7 +220,7 @@ const IndividualClientPage: React.FC<IndividualClientPageProps> = ({ clientId, t
return ( return (
<Card className="w-full max-w-md mx-auto mt-8"> <Card className="w-full max-w-md mx-auto mt-8">
<CardHeader> <CardHeader>
<CardTitle>Manage Client: {clientId}</CardTitle> <CardTitle>Manage Client: {activeClient?.nickname} - {clientId}</CardTitle>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-4">
{error && <p className="text-red-500 text-sm">{error}</p>} {error && <p className="text-red-500 text-sm">{error}</p>}
@@ -331,9 +336,24 @@ const IndividualClientPage: React.FC<IndividualClientPageProps> = ({ clientId, t
placeholder="Enter Discord Channel ID" placeholder="Enter Discord Channel ID"
/> />
</div> </div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="system-select" className="text-right">Select System</Label>
<Select onValueChange={setSelectedSystem} value={selectedSystem || ''}>
<SelectTrigger className="col-span-3">
<SelectValue placeholder="Choose a system" />
</SelectTrigger>
<SelectContent>
{availableSystems.map((system) => (
<SelectItem key={system._id} value={system._id}>
{system.name} ({system._id})
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div> </div>
<DialogFooter> <DialogFooter>
<Button onClick={handleConfirmJoin} disabled={!discordServerId || !discordChannelId || loading || !token}> <Button onClick={handleConfirmJoin} disabled={!discordServerId || !discordChannelId || !selectedSystem || loading || !token}>
{loading ? 'Joining...' : 'Confirm Join'} {loading ? 'Joining...' : 'Confirm Join'}
</Button> </Button>
<Button variant="outline" onClick={() => setIsJoinDiscordDialogOpen(false)}> <Button variant="outline" onClick={() => setIsJoinDiscordDialogOpen(false)}>
+31 -4
View File
@@ -3,19 +3,46 @@
import * as React from "react"; import * as React from "react";
import { useTheme } from "@/components/ThemeProvider"; // Adjust path as needed import { useTheme } from "@/components/ThemeProvider"; // Adjust path as needed
import { Button } from "@/components/ui/button"; // Assuming you have a Shadcn UI Button component import { Button } from "@/components/ui/button"; // Assuming you have a Shadcn UI Button component
import { Sun, Moon } from "lucide-react"; // Assuming you have lucide-react for icons import { Sun, Moon, Laptop } from "lucide-react"; // Import Laptop icon
export function ThemeToggle() { export function ThemeToggle() {
const { setTheme, theme } = useTheme(); const { setTheme, theme } = useTheme();
// Function to cycle through themes: light -> dark -> system -> light
const toggleTheme = () => {
if (theme === "light") {
setTheme("dark");
} else if (theme === "dark") {
setTheme("system");
} else {
setTheme("light");
}
};
return ( return (
<Button <Button
variant="ghost" // Use your preferred button variant variant="ghost" // Use your preferred button variant
size="icon" size="icon"
onClick={() => setTheme(theme === "light" ? "dark" : "light")} onClick={toggleTheme} // Call the new toggleTheme function
> >
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" /> {/* Icon for Light theme */}
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" /> <Sun
className={`h-[1.2rem] w-[1.2rem] transition-all
${theme === "light" ? "rotate-0 scale-100" : "-rotate-90 scale-0"}
`}
/>
{/* Icon for Dark theme */}
<Moon
className={`absolute h-[1.2rem] w-[1.2rem] transition-all
${theme === "dark" ? "rotate-0 scale-100" : "rotate-90 scale-0"}
`}
/>
{/* Icon for System theme */}
<Laptop
className={`absolute h-[1.2rem] w-[1.2rem] transition-all
${theme === "system" ? "rotate-0 scale-100" : "rotate-90 scale-0"}
`}
/>
<span className="sr-only">Toggle theme</span> <span className="sr-only">Toggle theme</span>
</Button> </Button>
); );
+8
View File
@@ -16,6 +16,7 @@ export enum DemodTypes {
token: string; token: string;
active: boolean; active: boolean;
guild_ids: string[]; guild_ids: string[];
fixed_node?: string;
} }
export interface System { export interface System {
@@ -48,10 +49,17 @@ export enum DemodTypes {
} }
export interface NodeStatusResponse { export interface NodeStatusResponse {
active_client: ActiveClient;
status: { status: {
op25_status: string; op25_status: string;
discord_status: string; discord_status: string;
};
} }
export interface ActiveClient {
active_token: DiscordId;
nickname: string;
client_id: string;
} }
// Auth Context Types // Auth Context Types