Files
LinkDesk/frontend/src/components/settings/GlobalSettingsPanel.vue
T
2026-02-28 03:22:04 +08:00

323 lines
10 KiB
Vue

<template>
<div class="space-y-6">
<!-- Upload Limit Section -->
<GlobalUploadLimitEditor />
<!-- All Settings Management -->
<Card>
<CardHeader>
<CardTitle class="flex items-center gap-2">
<Settings class="h-5 w-5" />
All Global Settings
</CardTitle>
<CardDescription>
Manage all system-wide configuration settings
</CardDescription>
</CardHeader>
<CardContent>
<div class="space-y-4">
<!-- Add New Setting -->
<div class="border rounded-lg p-4 space-y-3">
<h4 class="font-medium">Add New Setting</h4>
<div class="grid grid-cols-1 md:grid-cols-3 gap-3">
<div>
<Label for="new-key">Setting Key</Label>
<Input
id="new-key"
v-model="newSetting.setting_key"
placeholder="e.g., max_concurrent_uploads"
:disabled="loading"
/>
</div>
<div>
<Label for="new-value">Value</Label>
<Input
id="new-value"
v-model="newSetting.setting_value"
placeholder="Setting value"
:disabled="loading"
/>
</div>
<div>
<Label for="new-description">Description</Label>
<Input
id="new-description"
v-model="newSetting.description"
placeholder="Optional description"
:disabled="loading"
/>
</div>
</div>
<Button
@click="createNewSetting"
:disabled="loading || !canCreateSetting"
size="sm"
>
<Loader2 v-if="loading" class="h-4 w-4 animate-spin mr-2" />
<Plus class="h-4 w-4 mr-2" />
Add Setting
</Button>
</div>
<!-- Settings List -->
<div class="space-y-2">
<div class="flex items-center justify-between">
<h4 class="font-medium">Current Settings</h4>
<Button
@click="refreshSettings"
variant="outline"
size="sm"
:disabled="loading"
>
<RefreshCw class="h-4 w-4 mr-2" />
Refresh
</Button>
</div>
<div v-if="loading && allSettings.length === 0" class="text-center py-8">
<Loader2 class="h-6 w-6 animate-spin mx-auto mb-2" />
<p class="text-sm text-muted-foreground">Loading settings...</p>
</div>
<div v-else-if="allSettings.length === 0" class="text-center py-8">
<Settings class="h-12 w-12 mx-auto mb-2 text-muted-foreground" />
<p class="text-sm text-muted-foreground">No settings found</p>
</div>
<div v-else class="space-y-2">
<div
v-for="setting in allSettings"
:key="setting.id"
class="border rounded-lg p-3"
>
<div class="flex items-start justify-between">
<div class="flex-1 space-y-1">
<div class="flex items-center gap-2">
<code class="text-sm font-mono bg-muted px-2 py-1 rounded">
{{ setting.setting_key }}
</code>
<Badge v-if="setting.setting_key === 'global_upload_limit_mb'" variant="secondary">
System
</Badge>
</div>
<p class="text-sm">{{ setting.setting_value }}</p>
<p v-if="setting.description" class="text-xs text-muted-foreground">
{{ setting.description }}
</p>
<p class="text-xs text-muted-foreground">
Updated: {{ formatDate(setting.updated_at) }}
</p>
</div>
<div class="flex items-center gap-2">
<Button
@click="editSetting(setting)"
variant="outline"
size="sm"
:disabled="loading"
>
<Edit class="h-4 w-4" />
</Button>
<Button
@click="deleteSetting(setting.setting_key)"
variant="outline"
size="sm"
:disabled="loading || setting.setting_key === 'global_upload_limit_mb'"
class="text-destructive hover:text-destructive"
>
<Trash2 class="h-4 w-4" />
</Button>
</div>
</div>
</div>
</div>
</div>
</div>
<div v-if="error" class="mt-4 text-sm text-destructive">
{{ error }}
</div>
</CardContent>
</Card>
<!-- Edit Setting Dialog -->
<Dialog v-model:open="editDialogOpen">
<DialogContent>
<DialogHeader>
<DialogTitle>Edit Setting</DialogTitle>
<DialogDescription>
Update the value and description for this setting
</DialogDescription>
</DialogHeader>
<div class="space-y-4">
<div>
<Label>Setting Key</Label>
<Input :value="editingSetting?.setting_key" disabled />
</div>
<div>
<Label for="edit-value">Value</Label>
<Input
id="edit-value"
v-model="editForm.setting_value"
:disabled="loading"
/>
</div>
<div>
<Label for="edit-description">Description</Label>
<Input
id="edit-description"
v-model="editForm.description"
:disabled="loading"
/>
</div>
</div>
<DialogFooter>
<Button variant="outline" @click="editDialogOpen = false" :disabled="loading">
Cancel
</Button>
<Button @click="updateSetting" :disabled="loading">
<Loader2 v-if="loading" class="h-4 w-4 animate-spin mr-2" />
Update
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Badge } from '@/components/ui/badge'
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { Settings, Plus, Edit, Trash2, RefreshCw, Loader2 } from 'lucide-vue-next'
import { useSettingsStore } from '@/stores/settings'
import { useToast } from '@/components/ui/toast/use-toast'
import type { GlobalSetting } from '@/services/settings'
import GlobalUploadLimitEditor from './GlobalUploadLimitEditor.vue'
const settingsStore = useSettingsStore()
const { toast } = useToast()
const newSetting = ref({
setting_key: '',
setting_value: '',
description: ''
})
const editDialogOpen = ref(false)
const editingSetting = ref<GlobalSetting | null>(null)
const editForm = ref({
setting_value: '',
description: ''
})
const loading = computed(() => settingsStore.loading)
const allSettings = computed(() => settingsStore.allSettings)
const error = computed(() => settingsStore.error)
const canCreateSetting = computed(() =>
newSetting.value.setting_key.trim() && newSetting.value.setting_value.trim()
)
function formatDate(dateString: string): string {
return new Date(dateString).toLocaleString()
}
async function createNewSetting() {
if (!canCreateSetting.value) return
try {
await settingsStore.createSetting(newSetting.value)
toast({
title: 'Setting created',
description: `Setting "${newSetting.value.setting_key}" has been created`
})
// Reset form
newSetting.value = {
setting_key: '',
setting_value: '',
description: ''
}
} catch (error) {
toast({
title: 'Error',
description: 'Failed to create setting',
variant: 'destructive'
})
}
}
function editSetting(setting: GlobalSetting) {
editingSetting.value = setting
editForm.value = {
setting_value: setting.setting_value,
description: setting.description || ''
}
editDialogOpen.value = true
}
async function updateSetting() {
if (!editingSetting.value) return
try {
await settingsStore.updateSetting(editingSetting.value.setting_key, editForm.value)
toast({
title: 'Setting updated',
description: `Setting "${editingSetting.value.setting_key}" has been updated`
})
editDialogOpen.value = false
editingSetting.value = null
} catch (error) {
toast({
title: 'Error',
description: 'Failed to update setting',
variant: 'destructive'
})
}
}
async function deleteSetting(settingKey: string) {
if (settingKey === 'global_upload_limit_mb') {
toast({
title: 'Cannot delete',
description: 'The global upload limit setting cannot be deleted',
variant: 'destructive'
})
return
}
if (!confirm(`Are you sure you want to delete the setting "${settingKey}"?`)) {
return
}
try {
await settingsStore.deleteSetting(settingKey)
toast({
title: 'Setting deleted',
description: `Setting "${settingKey}" has been deleted`
})
} catch (error) {
toast({
title: 'Error',
description: 'Failed to delete setting',
variant: 'destructive'
})
}
}
async function refreshSettings() {
await settingsStore.fetchAllSettings()
}
onMounted(() => {
settingsStore.fetchAllSettings()
})
</script>