First pass on bot management page
This commit is contained in:
@@ -166,6 +166,56 @@ async fn play(path: web::Path<(String, String)>, play_request: web::Json<PlayReq
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[post("/{guild_id}/voice/stop")]
|
||||||
|
async fn stop(path: web::Path<String>, data: web::Data<Arc<AppState>>) -> HttpResponse {
|
||||||
|
let guild_id = path.into_inner();
|
||||||
|
let guild_id = match guild_id.parse::<u64>() {
|
||||||
|
Ok(id) => id,
|
||||||
|
Err(err) => {
|
||||||
|
warn!("Could not parse guild id: {:?}", err);
|
||||||
|
return ResponseError::error_response(&ServiceError {
|
||||||
|
status: 422,
|
||||||
|
message: err.to_string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(handler_lock) = data.songbird.get(guild_id) {
|
||||||
|
let handler = handler_lock.lock().await;
|
||||||
|
handler.queue().stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpResponse::Ok().finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/{guild_id}/voice/resume")]
|
||||||
|
async fn resume(path: web::Path<String>, data: web::Data<Arc<AppState>>) -> HttpResponse {
|
||||||
|
let guild_id = path.into_inner();
|
||||||
|
let guild_id = match guild_id.parse::<u64>() {
|
||||||
|
Ok(id) => id,
|
||||||
|
Err(err) => {
|
||||||
|
warn!("Could not parse guild id: {:?}", err);
|
||||||
|
return ResponseError::error_response(&ServiceError {
|
||||||
|
status: 422,
|
||||||
|
message: err.to_string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(handler_lock) = data.songbird.get(guild_id) {
|
||||||
|
let handler = handler_lock.lock().await;
|
||||||
|
if let Err(err) = handler.queue().resume() {
|
||||||
|
warn!("Could not resume track: {:?}", err);
|
||||||
|
return ResponseError::error_response(&ServiceError {
|
||||||
|
status: 422,
|
||||||
|
message: err.to_string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpResponse::Ok().finish()
|
||||||
|
}
|
||||||
|
|
||||||
#[post("/{guild_id}/voice/pause")]
|
#[post("/{guild_id}/voice/pause")]
|
||||||
async fn pause(path: web::Path<String>, data: web::Data<Arc<AppState>>) -> HttpResponse {
|
async fn pause(path: web::Path<String>, data: web::Data<Arc<AppState>>) -> HttpResponse {
|
||||||
let guild_id = path.into_inner();
|
let guild_id = path.into_inner();
|
||||||
@@ -201,7 +251,9 @@ pub fn init_routes(config: &mut web::ServiceConfig) {
|
|||||||
.service(get_text_channels)
|
.service(get_text_channels)
|
||||||
.service(get_voice_channels)
|
.service(get_voice_channels)
|
||||||
.service(send_message)
|
.service(send_message)
|
||||||
.service(pause)
|
|
||||||
.service(play)
|
.service(play)
|
||||||
|
.service(stop)
|
||||||
|
.service(resume)
|
||||||
|
.service(pause)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
24
ui/package-lock.json
generated
24
ui/package-lock.json
generated
@@ -9,6 +9,7 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mantine/core": "^7.1.2",
|
"@mantine/core": "^7.1.2",
|
||||||
|
"@mantine/form": "^7.1.2",
|
||||||
"@mantine/hooks": "^7.1.2",
|
"@mantine/hooks": "^7.1.2",
|
||||||
"@mantine/modals": "^7.1.2",
|
"@mantine/modals": "^7.1.2",
|
||||||
"@mantine/notifications": "^7.1.2",
|
"@mantine/notifications": "^7.1.2",
|
||||||
@@ -225,6 +226,18 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@mantine/form": {
|
||||||
|
"version": "7.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mantine/form/-/form-7.1.2.tgz",
|
||||||
|
"integrity": "sha512-FnUu5XNmRM265G0wy19qSRiItG/2eQ0GQCctnokw6ws9ZnCU1NqvsmpuDE/UiV4YCAOhAVHfqnjG/8tsrlw7ug==",
|
||||||
|
"dependencies": {
|
||||||
|
"fast-deep-equal": "^3.1.3",
|
||||||
|
"klona": "^2.0.5"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@mantine/hooks": {
|
"node_modules/@mantine/hooks": {
|
||||||
"version": "7.1.2",
|
"version": "7.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-7.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-7.1.2.tgz",
|
||||||
@@ -2305,8 +2318,7 @@
|
|||||||
"node_modules/fast-deep-equal": {
|
"node_modules/fast-deep-equal": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/fast-diff": {
|
"node_modules/fast-diff": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
@@ -3353,6 +3365,14 @@
|
|||||||
"json-buffer": "3.0.1"
|
"json-buffer": "3.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/klona": {
|
||||||
|
"version": "2.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz",
|
||||||
|
"integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/language-subtag-registry": {
|
"node_modules/language-subtag-registry": {
|
||||||
"version": "0.3.22",
|
"version": "0.3.22",
|
||||||
"resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz",
|
"resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mantine/core": "^7.1.2",
|
"@mantine/core": "^7.1.2",
|
||||||
|
"@mantine/form": "^7.1.2",
|
||||||
"@mantine/hooks": "^7.1.2",
|
"@mantine/hooks": "^7.1.2",
|
||||||
"@mantine/modals": "^7.1.2",
|
"@mantine/modals": "^7.1.2",
|
||||||
"@mantine/notifications": "^7.1.2",
|
"@mantine/notifications": "^7.1.2",
|
||||||
|
|||||||
33
ui/src/api/guilds.ts
Normal file
33
ui/src/api/guilds.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { getRequest, postRequest } from '.';
|
||||||
|
import { GuildChannel, GuildInfo } from './guilds.types';
|
||||||
|
|
||||||
|
export async function getGuilds(): Promise<GuildInfo[]> {
|
||||||
|
const response = await getRequest('guilds', {});
|
||||||
|
return response?.data || { data: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTextChannels(guildId: number): Promise<GuildChannel[]> {
|
||||||
|
const response = await getRequest(`guilds/${guildId}/text`, {});
|
||||||
|
return response?.data || { data: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getVoiceChannels(guildId: number): Promise<GuildChannel[]> {
|
||||||
|
const response = await getRequest(`guilds/${guildId}/voice`, {});
|
||||||
|
return response?.data || { data: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function playTrack(guildId: number, channelId: number, track: string): Promise<void> {
|
||||||
|
await postRequest(`guilds/${guildId}/voice/${channelId}/play`, { track_url: track });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function stopTrack(guildId: number, channelId: number): Promise<void> {
|
||||||
|
await postRequest(`guilds/${guildId}/voice/${channelId}/stop`, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function pauseTrack(guildId: number, channelId: number): Promise<void> {
|
||||||
|
await postRequest(`guilds/${guildId}/voice/${channelId}/pause`, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function resumeTrack(guildId: number, channelId: number): Promise<void> {
|
||||||
|
await postRequest(`guilds/${guildId}/voice/${channelId}/resume`, {});
|
||||||
|
}
|
||||||
13
ui/src/api/guilds.types.ts
Normal file
13
ui/src/api/guilds.types.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
export interface GuildInfo {
|
||||||
|
id: number;
|
||||||
|
icon?: string;
|
||||||
|
name: string;
|
||||||
|
owner: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GuildChannel {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
guild_id: number;
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ export async function getRequest(endpoint: string, params: any): Promise<AxiosRe
|
|||||||
|
|
||||||
export async function postRequest(endpoint: string, body: any): Promise<AxiosResponse<any, any> | undefined> {
|
export async function postRequest(endpoint: string, body: any): Promise<AxiosResponse<any, any> | undefined> {
|
||||||
const response = await axios
|
const response = await axios
|
||||||
.post(`${serviceHost}:${servicePort}/${endpoint}`, { body })
|
.post(`${serviceHost}:${servicePort}/${endpoint}`, body || {})
|
||||||
.catch((error) => console.error(error));
|
.catch((error) => console.error(error));
|
||||||
return response || undefined;
|
return response || undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
106
ui/src/app/management/page.tsx
Normal file
106
ui/src/app/management/page.tsx
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getGuilds,
|
||||||
|
getTextChannels,
|
||||||
|
getVoiceChannels,
|
||||||
|
pauseTrack,
|
||||||
|
playTrack,
|
||||||
|
resumeTrack,
|
||||||
|
stopTrack
|
||||||
|
} from '@/api/guilds';
|
||||||
|
import { GuildChannel, GuildInfo } from '@/api/guilds.types';
|
||||||
|
import { Button, Slider, Tabs, TextInput } from '@mantine/core';
|
||||||
|
import { useForm } from '@mantine/form';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
const [guilds, setGuilds] = useState<GuildInfo[]>([]);
|
||||||
|
const [activeGuild, setActiveGuild] = useState<GuildInfo | null>(null);
|
||||||
|
const [textChannels, setTextChannels] = useState<GuildChannel[]>([]);
|
||||||
|
const [voiceChannels, setVoiceChannels] = useState<GuildChannel[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getGuilds().then((g) => {
|
||||||
|
setGuilds(g);
|
||||||
|
if (g.length > 0) {
|
||||||
|
setActiveGuild(g[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (activeGuild) {
|
||||||
|
getTextChannels(activeGuild.id).then((c) => setTextChannels(c));
|
||||||
|
getVoiceChannels(activeGuild.id).then((c) => setVoiceChannels(c));
|
||||||
|
}
|
||||||
|
}, [activeGuild]);
|
||||||
|
|
||||||
|
const playForm = useForm({
|
||||||
|
initialValues: {
|
||||||
|
trackUrl: ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tabs orientation='vertical' defaultValue={activeGuild?.name}>
|
||||||
|
<Tabs.List>
|
||||||
|
{guilds.map((guild) => (
|
||||||
|
<Tabs.Tab key={guild.id} value={guild.name} onClick={() => setActiveGuild(guild)}>
|
||||||
|
{guild.name}
|
||||||
|
</Tabs.Tab>
|
||||||
|
))}
|
||||||
|
</Tabs.List>
|
||||||
|
{guilds.map((guild) => (
|
||||||
|
<Tabs.Panel key={guild.id} value={guild.name}>
|
||||||
|
<h1>{guild.name}</h1>
|
||||||
|
<h2>Text Channels</h2>
|
||||||
|
<Tabs orientation='horizontal' defaultValue={textChannels[0]?.name}>
|
||||||
|
<Tabs.List>
|
||||||
|
{textChannels.map((channel) => (
|
||||||
|
<Tabs.Tab key={channel.id} value={channel.name}>
|
||||||
|
{channel.name}
|
||||||
|
</Tabs.Tab>
|
||||||
|
))}
|
||||||
|
</Tabs.List>
|
||||||
|
{textChannels.map((channel) => (
|
||||||
|
<Tabs.Panel key={channel.id} value={channel.name}>
|
||||||
|
{channel.name}
|
||||||
|
</Tabs.Panel>
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
<h2>Voice Channels</h2>
|
||||||
|
<Tabs orientation='horizontal' defaultValue={voiceChannels[0]?.name}>
|
||||||
|
<Tabs.List>
|
||||||
|
{voiceChannels.map((channel) => (
|
||||||
|
<Tabs.Tab key={channel.id} value={channel.name}>
|
||||||
|
{channel.name}
|
||||||
|
</Tabs.Tab>
|
||||||
|
))}
|
||||||
|
</Tabs.List>
|
||||||
|
{voiceChannels.map((channel) => (
|
||||||
|
<Tabs.Panel key={channel.id} value={channel.name}>
|
||||||
|
{channel.name}
|
||||||
|
<form
|
||||||
|
style={{ display: 'flex' }}
|
||||||
|
onSubmit={playForm.onSubmit((values) => {
|
||||||
|
playTrack(activeGuild!.id, channel.id, values.trackUrl);
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<TextInput placeholder='Youtube URL...' {...playForm.getInputProps('trackUrl')} />
|
||||||
|
<Button type='submit'>Play Track</Button>
|
||||||
|
</form>
|
||||||
|
<Button onClick={() => stopTrack(activeGuild!.id, channel.id)}>Stop</Button>
|
||||||
|
<Button onClick={() => pauseTrack(activeGuild!.id, channel.id)}>Pause</Button>
|
||||||
|
<Button onClick={() => resumeTrack(activeGuild!.id, channel.id)}>Resume</Button>
|
||||||
|
<div>
|
||||||
|
<Slider label='Volume' style={{ width: '20%' }} defaultValue={50} onChange={(v) => {}} />
|
||||||
|
</div>
|
||||||
|
</Tabs.Panel>
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
</Tabs.Panel>
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -32,6 +32,10 @@ const headerItems = [
|
|||||||
{
|
{
|
||||||
name: 'Spells',
|
name: 'Spells',
|
||||||
link: '/spells'
|
link: '/spells'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Management',
|
||||||
|
link: '/management'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user