feat: add scheduler config

This commit is contained in:
engigu
2025-12-22 16:05:07 +08:00
parent 03f4fa7b5c
commit be3527cb61
12 changed files with 387 additions and 52 deletions
+9
View File
@@ -114,6 +114,9 @@ export const api = {
getPublicSite: () => request<{ title: string; subtitle: string; icon: string }>('/settings/public'),
updateSite: (data: SiteSettings) =>
request('/settings/site', { method: 'PUT', body: JSON.stringify(data) }),
getScheduler: () => request<SchedulerSettings>('/settings/scheduler'),
updateScheduler: (data: SchedulerSettings) =>
request('/settings/scheduler', { method: 'PUT', body: JSON.stringify(data) }),
getAbout: () => request<AboutInfo>('/settings/about'),
getLoginLogs: (params?: { page?: number; page_size?: number; username?: string }) => {
const query = new URLSearchParams()
@@ -307,6 +310,12 @@ export interface SiteSettings {
cookie_days: string
}
export interface SchedulerSettings {
worker_count: string
queue_size: string
rate_interval: string
}
export interface LoginLog {
id: number
+21
View File
@@ -143,3 +143,24 @@
line-height: 1.5;
}
}
/* Number input spinner buttons - dark mode */
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
opacity: 0.6;
}
input[type="number"]:hover::-webkit-inner-spin-button,
input[type="number"]:hover::-webkit-outer-spin-button {
opacity: 1;
}
.dark input[type="number"]::-webkit-inner-spin-button,
.dark input[type="number"]::-webkit-outer-spin-button {
filter: invert(0.7);
}
.dark input[type="number"]:hover::-webkit-inner-spin-button,
.dark input[type="number"]:hover::-webkit-outer-spin-button {
filter: invert(0.85);
}
+14 -20
View File
@@ -21,7 +21,6 @@ const backupLoading = ref(false)
const restoreLoading = ref(false)
const fileInput = ref<HTMLInputElement>()
const showConfirm = ref(false)
const pendingFile = ref<File | null>(null)
async function checkBackupStatus() {
try {
@@ -49,11 +48,20 @@ function downloadBackup() {
setTimeout(checkBackupStatus, 6000)
}
function triggerFileSelect() {
function showRestoreConfirm() {
showConfirm.value = true
}
function confirmRestore() {
showConfirm.value = false
fileInput.value?.click()
}
function handleFileSelect(e: Event) {
function cancelRestore() {
showConfirm.value = false
}
async function handleFileSelect(e: Event) {
const target = e.target as HTMLInputElement
const file = target.files?.[0]
if (!file) return
@@ -64,33 +72,19 @@ function handleFileSelect(e: Event) {
return
}
pendingFile.value = file
showConfirm.value = true
target.value = ''
}
async function confirmRestore() {
if (!pendingFile.value) return
showConfirm.value = false
restoreLoading.value = true
try {
await api.settings.restoreBackup(pendingFile.value)
await api.settings.restoreBackup(file)
toast.success('恢复成功,页面即将刷新')
setTimeout(() => window.location.reload(), 1500)
} catch (e: any) {
toast.error(e.message || '恢复失败')
} finally {
restoreLoading.value = false
pendingFile.value = null
target.value = ''
}
}
function cancelRestore() {
showConfirm.value = false
pendingFile.value = null
}
onMounted(checkBackupStatus)
</script>
@@ -112,7 +106,7 @@ onMounted(checkBackupStatus)
</div>
<div class="border-t pt-4 mt-4">
<div class="flex items-center gap-4">
<Button @click="triggerFileSelect" :disabled="restoreLoading" variant="outline">
<Button @click="showRestoreConfirm" :disabled="restoreLoading" variant="outline">
<Upload class="w-4 h-4 mr-2" />
{{ restoreLoading ? '恢复中...' : '恢复备份' }}
</Button>
@@ -0,0 +1,108 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Button } from '@/components/ui/button'
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle
} from '@/components/ui/alert-dialog'
import { api } from '@/api'
import { toast } from 'vue-sonner'
interface SchedulerSettings {
worker_count: string
queue_size: string
rate_interval: string
}
const form = ref<SchedulerSettings>({
worker_count: '4',
queue_size: '100',
rate_interval: '200'
})
const loading = ref(false)
const showConfirm = ref(false)
async function loadSettings() {
try {
const res = await api.settings.getScheduler()
form.value = res
} catch {}
}
function confirmSave() {
showConfirm.value = true
}
async function saveSettings() {
showConfirm.value = false
loading.value = true
try {
await api.settings.updateScheduler({
worker_count: String(form.value.worker_count),
queue_size: String(form.value.queue_size),
rate_interval: String(form.value.rate_interval)
})
toast.success('保存成功,调度配置已重新加载')
} catch {
toast.error('保存失败')
} finally {
loading.value = false
}
}
onMounted(loadSettings)
</script>
<template>
<div class="space-y-4">
<div class="grid grid-cols-4 items-center gap-4">
<Label class="text-right">Worker 数量</Label>
<div class="col-span-3 flex items-center gap-2">
<Input v-model="form.worker_count" type="number" class="w-24" />
<span class="text-xs text-muted-foreground">并发执行任务的 worker 数量</span>
</div>
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label class="text-right">队列大小</Label>
<div class="col-span-3 flex items-center gap-2">
<Input v-model="form.queue_size" type="number" class="w-24" />
<span class="text-xs text-muted-foreground">任务队列缓冲区大小</span>
</div>
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label class="text-right">速率间隔</Label>
<div class="col-span-3 flex items-center gap-2">
<Input v-model="form.rate_interval" type="number" class="w-24" />
<span class="text-xs text-muted-foreground">ms任务启动间隔200ms = 每秒最多5个</span>
</div>
</div>
<div class="flex justify-end pt-2">
<Button @click="confirmSave" :disabled="loading">
{{ loading ? '保存中...' : '保存设置' }}
</Button>
</div>
<AlertDialog :open="showConfirm" @update:open="showConfirm = $event">
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>确认保存</AlertDialogTitle>
<AlertDialogDescription>
保存后调度配置将立即生效正在执行的任务不受影响确定要保存吗
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>取消</AlertDialogCancel>
<AlertDialogAction @click="saveSettings">确认保存</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
</template>
+14
View File
@@ -4,6 +4,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import PasswordSettings from './PasswordSettings.vue'
import SiteSettings from './SiteSettings.vue'
import SchedulerSettings from './SchedulerSettings.vue'
import BackupSettings from './BackupSettings.vue'
import AboutSettings from './AboutSettings.vue'
@@ -21,6 +22,7 @@ const activeTab = ref('password')
<TabsList>
<TabsTrigger value="password">密码修改</TabsTrigger>
<TabsTrigger value="site">站点设置</TabsTrigger>
<TabsTrigger value="scheduler">调度设置</TabsTrigger>
<TabsTrigger value="backup">备份恢复</TabsTrigger>
<TabsTrigger value="about">关于</TabsTrigger>
</TabsList>
@@ -49,6 +51,18 @@ const activeTab = ref('password')
</Card>
</TabsContent>
<TabsContent value="scheduler" class="mt-6">
<Card class="max-w-xl">
<CardHeader>
<CardTitle>调度设置</CardTitle>
<CardDescription>配置任务调度器参数</CardDescription>
</CardHeader>
<CardContent>
<SchedulerSettings />
</CardContent>
</Card>
</TabsContent>
<TabsContent value="backup" class="mt-6">
<Card class="max-w-xl">
<CardHeader>