Compare commits
8 Commits
refactorin
...
e64984b7b8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e64984b7b8 | ||
|
|
20187a06f0 | ||
|
|
464f37adda | ||
|
|
a1fda6fe87 | ||
|
|
aaa362ec0e | ||
|
|
3b62590df3 | ||
|
|
8373fdd685 | ||
| 6a74cd55a0 |
@@ -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({
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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)}>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user