feat: add bot restart functionality via API and Discord command

This commit is contained in:
mmacierzanka committed 2026-09-11 01:21:26 +02:00
1 parent 82746e4c83
commit 708e3591ed
7 files changed
+172 -5

No files matched your search

+1
View File
@@ -40,6 +40,7 @@ function post<T>(path: string, body?: unknown): Promise<T> {
export const api = {
status: () => request<BotStatus>('/api/status'),
restart: () => post<{ status: string }>('/api/restart'),
connect: (channelId: string) => post('/api/connect', { channel_id: channelId }),
disconnect: () => post('/api/disconnect'),
+88 -3
View File
@@ -1,6 +1,9 @@
<script setup lang="ts">
import { computed } from 'vue'
import { computed, ref, watch } from 'vue'
import { RotateCw } from 'lucide-vue-next'
import type { BotStatus, GuildSettings } from '@/types/status'
import { api } from '@/api/client'
import { useToasts } from '@/composables/useToasts'
const props = defineProps<{
status: BotStatus | null
@@ -9,6 +12,11 @@ const props = defineProps<{
}>()
defineEmits<{ selectGuild: [guildId: string] }>()
const { notify } = useToasts()
const isRestarting = ref(false)
const restartInitiatedAt = ref<number | null>(null)
let restartTimer: number | null = null
const activeChannel = computed(() => props.status?.active_voice_channel ?? null)
const label = computed(() =>
@@ -16,6 +24,43 @@ const label = computed(() =>
? `Połączony: ${activeChannel.value.channel_name}`
: 'Rozłączony',
)
async function handleRestart() {
if (isRestarting.value) return
if (!window.confirm('Czy na pewno chcesz zrestartować bota?')) return
isRestarting.value = true
restartInitiatedAt.value = Date.now()
notify('Wysyłanie polecenia restartu...', 'info')
try {
await api.restart()
notify('Wysłano polecenie. Restartowanie bota...', 'info')
} catch {
notify('Wysłano polecenie restartu.', 'info')
}
if (restartTimer !== null) clearTimeout(restartTimer)
restartTimer = window.setTimeout(() => {
isRestarting.value = false
restartInitiatedAt.value = null
}, 15000)
}
watch(
() => props.status,
() => {
if (isRestarting.value && restartInitiatedAt.value && Date.now() - restartInitiatedAt.value > 3000) {
isRestarting.value = false
restartInitiatedAt.value = null
if (restartTimer !== null) {
clearTimeout(restartTimer)
restartTimer = null
}
notify('Bot został uruchomiony ponownie!', 'success')
}
},
)
</script>
<template>
@@ -56,6 +101,19 @@ const label = computed(() =>
/>
<span>{{ label }}</span>
</div>
<button
type="button"
class="btn-secondary btn-restart"
:disabled="isRestarting"
:title="isRestarting ? 'Restartowanie bota...' : 'Zrestartuj bota'"
@click="handleRestart"
>
<RotateCw
:size="16"
:class="{ 'spin-icon': isRestarting }"
/>
<span>{{ isRestarting ? 'Restart...' : 'Restart' }}</span>
</button>
</div>
</header>
</template>
@@ -67,10 +125,32 @@ const label = computed(() =>
gap: 16px;
}
.btn-secondary {
display: flex;
.btn-restart {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
border-radius: 30px;
font-size: 0.85rem;
font-weight: 600;
cursor: pointer;
transition: var(--transition-smooth);
}
.btn-restart:hover:not(:disabled) {
border-color: rgba(239, 68, 68, 0.4);
color: #fca5a5;
background: rgba(239, 68, 68, 0.12);
box-shadow: 0 0 12px rgba(239, 68, 68, 0.2);
}
.btn-restart:disabled {
opacity: 0.65;
cursor: not-allowed;
}
.spin-icon {
animation: spin 1s linear infinite;
}
@media (max-width: 768px) {
@@ -85,5 +165,10 @@ const label = computed(() =>
flex-direction: column;
align-items: flex-start;
}
.btn-restart {
width: 100%;
justify-content: center;
}
}
</style>