323 lines
10 KiB
Vue
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> |