dev #3
@ -4,238 +4,73 @@
|
||||
<h1 class="page-title">{{ t('Settings') }}</h1>
|
||||
</div>
|
||||
|
||||
<n-grid :cols="2" :x-gap="24" :y-gap="24">
|
||||
<!-- 左栏:系统设置 -->
|
||||
<n-grid-item>
|
||||
<n-card :title="t('System Settings')">
|
||||
<n-form :model="systemSettings" label-placement="left" label-width="120px">
|
||||
<n-form-item :label="t('App Name')">
|
||||
<n-input v-model:value="systemSettings.appName" :placeholder="t('Enter app name')" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="t('Interface Language')">
|
||||
<n-select
|
||||
v-model:value="systemSettings.language"
|
||||
:options="languageOptions"
|
||||
style="width: 200px"
|
||||
@update:value="changeLanguage"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item :label="t('Items Per Page')">
|
||||
<n-select
|
||||
v-model:value="systemSettings.itemsPerPage"
|
||||
:options="pageSizeOptions"
|
||||
style="width: 120px"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item :label="t('Timezone')">
|
||||
<n-alert v-if="timezoneError" type="error" style="margin-bottom: 8px">
|
||||
{{ timezoneError }}
|
||||
</n-alert>
|
||||
<n-select
|
||||
v-model:value="systemSettings.timezone"
|
||||
:options="timezoneOptions"
|
||||
style="width: 250px"
|
||||
filterable
|
||||
:placeholder="t('Select timezone')"
|
||||
:disabled="timezoneOptions.length === 0"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<template #footer>
|
||||
<n-space justify="start">
|
||||
<n-button type="primary" class="save-btn-brand" @click="saveSystemSettings">
|
||||
<template #icon>
|
||||
<n-icon><Icon icon="tabler:check" /></n-icon>
|
||||
</template>
|
||||
{{ t('Save') }}
|
||||
</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-card>
|
||||
</n-grid-item>
|
||||
|
||||
<!-- 右栏:环境配置(仅系统管理员可见) -->
|
||||
<n-grid-item v-if="isAdmin">
|
||||
<n-card :title="t('Environment Configuration')">
|
||||
<n-alert type="warning" style="margin-bottom: 16px">
|
||||
{{ t('Only system administrators can view and edit environment configuration') }}
|
||||
<n-card :title="t('System Settings')">
|
||||
<n-form :model="systemSettings" label-placement="left" label-width="120px">
|
||||
<n-form-item :label="t('App Name')">
|
||||
<n-input v-model:value="systemSettings.appName" :placeholder="t('Enter app name')" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="t('Interface Language')">
|
||||
<n-select
|
||||
v-model:value="systemSettings.language"
|
||||
:options="languageOptions"
|
||||
style="width: 200px"
|
||||
@update:value="changeLanguage"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item :label="t('Items Per Page')">
|
||||
<n-select
|
||||
v-model:value="systemSettings.itemsPerPage"
|
||||
:options="pageSizeOptions"
|
||||
style="width: 120px"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item :label="t('Timezone')">
|
||||
<n-alert v-if="timezoneError" type="error" style="margin-bottom: 8px">
|
||||
{{ timezoneError }}
|
||||
</n-alert>
|
||||
<n-form
|
||||
:model="envConfig"
|
||||
label-placement="left"
|
||||
label-width="180px"
|
||||
:loading="envConfigLoading"
|
||||
>
|
||||
<n-collapse>
|
||||
<n-collapse-item name="jingrow" :title="t('Jingrow API Configuration')">
|
||||
<n-form-item :label="t('Jingrow Server URL')">
|
||||
<n-input v-model:value="envConfig.jingrow_server_url" placeholder="https://example.jingrow.com" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="t('Jingrow API Key')">
|
||||
<n-input v-model:value="envConfig.jingrow_api_key" type="password" show-password-on="click" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="t('Jingrow API Secret')">
|
||||
<n-input v-model:value="envConfig.jingrow_api_secret" type="password" show-password-on="click" />
|
||||
</n-form-item>
|
||||
</n-collapse-item>
|
||||
|
||||
<n-collapse-item name="cloud" :title="t('Jingrow Cloud Configuration')">
|
||||
<n-form-item :label="t('Cloud URL')">
|
||||
<n-input v-model:value="envConfig.jingrow_cloud_url" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="t('Cloud API URL')">
|
||||
<n-input v-model:value="envConfig.jingrow_cloud_api_url" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="t('Cloud API Key')">
|
||||
<n-input v-model:value="envConfig.jingrow_cloud_api_key" type="password" show-password-on="click" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="t('Cloud API Secret')">
|
||||
<n-input v-model:value="envConfig.jingrow_cloud_api_secret" type="password" show-password-on="click" />
|
||||
</n-form-item>
|
||||
</n-collapse-item>
|
||||
|
||||
<n-collapse-item v-if="isLocalMode" name="database" :title="t('Database Configuration')">
|
||||
<n-form-item :label="t('DB Host')">
|
||||
<n-input v-model:value="envConfig.jingrow_db_host" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="t('DB Port')">
|
||||
<n-input v-model:value="envConfig.jingrow_db_port" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="t('DB Name')">
|
||||
<n-input v-model:value="envConfig.jingrow_db_name" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="t('DB User')">
|
||||
<n-input v-model:value="envConfig.jingrow_db_user" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="t('DB Password')">
|
||||
<n-input v-model:value="envConfig.jingrow_db_password" type="password" show-password-on="click" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="t('DB Type')">
|
||||
<n-select v-model:value="envConfig.jingrow_db_type" :options="dbTypeOptions" style="width: 200px" />
|
||||
</n-form-item>
|
||||
</n-collapse-item>
|
||||
|
||||
<n-collapse-item name="backend" :title="t('Backend Configuration')">
|
||||
<n-form-item :label="t('Backend Host')">
|
||||
<n-input v-model:value="envConfig.backend_host" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="t('Backend Port')">
|
||||
<n-input-number v-model:value="envConfig.backend_port" :min="1" :max="65535" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="t('Backend Reload')">
|
||||
<n-switch v-model:value="envConfig.backend_reload" />
|
||||
</n-form-item>
|
||||
</n-collapse-item>
|
||||
|
||||
<n-collapse-item name="dramatiq" :title="t('Dramatiq')">
|
||||
<n-form-item :label="t('Worker Processes')">
|
||||
<n-input-number v-model:value="envConfig.worker_processes" :min="1" :max="32" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="t('Worker Threads')">
|
||||
<n-input-number v-model:value="envConfig.worker_threads" :min="1" :max="32" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="t('Watch')">
|
||||
<n-switch v-model:value="envConfig.watch" />
|
||||
</n-form-item>
|
||||
</n-collapse-item>
|
||||
|
||||
<n-collapse-item name="qdrant" :title="t('Qdrant Configuration')">
|
||||
<n-form-item :label="t('Qdrant Host')">
|
||||
<n-input v-model:value="envConfig.qdrant_host" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="t('Qdrant Port')">
|
||||
<n-input-number v-model:value="envConfig.qdrant_port" :min="1" :max="65535" />
|
||||
</n-form-item>
|
||||
</n-collapse-item>
|
||||
|
||||
<n-collapse-item name="runtime" :title="t('Other')">
|
||||
<n-form-item :label="t('Run Mode')">
|
||||
<n-select v-model:value="envConfig.run_mode" :options="runModeOptions" style="width: 200px" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="t('Environment')">
|
||||
<n-select v-model:value="envConfig.environment" :options="environmentOptions" style="width: 200px" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="t('Log Level')">
|
||||
<n-select v-model:value="envConfig.log_level" :options="logLevelOptions" style="width: 200px" />
|
||||
</n-form-item>
|
||||
</n-collapse-item>
|
||||
</n-collapse>
|
||||
</n-form>
|
||||
<template #footer>
|
||||
<n-space justify="start">
|
||||
<n-button type="default" @click="() => loadEnvironmentConfig()" :loading="envConfigLoading">
|
||||
<template #icon>
|
||||
<n-icon><Icon icon="tabler:refresh" /></n-icon>
|
||||
</template>
|
||||
{{ t('Refresh') }}
|
||||
</n-button>
|
||||
<n-button type="warning" :loading="envConfigRestarting" @click="handleRestartEnvironment">
|
||||
<template #icon>
|
||||
<n-icon><Icon icon="ix:restart" /></n-icon>
|
||||
</template>
|
||||
{{ t('Restart Environment') }}
|
||||
</n-button>
|
||||
<n-button type="primary" class="save-btn-brand" :loading="envConfigSaving" @click="saveEnvironmentConfig">
|
||||
<template #icon>
|
||||
<n-icon><Icon icon="tabler:check" /></n-icon>
|
||||
</template>
|
||||
{{ t('Save') }}
|
||||
</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-card>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
<n-select
|
||||
v-model:value="systemSettings.timezone"
|
||||
:options="timezoneOptions"
|
||||
style="width: 250px"
|
||||
filterable
|
||||
:placeholder="t('Select timezone')"
|
||||
:disabled="timezoneOptions.length === 0"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<template #footer>
|
||||
<n-space justify="start">
|
||||
<n-button type="primary" class="save-btn-brand" @click="saveSystemSettings">
|
||||
<template #icon>
|
||||
<n-icon><Icon icon="tabler:check" /></n-icon>
|
||||
</template>
|
||||
{{ t('Save') }}
|
||||
</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, computed, ref } from 'vue'
|
||||
import { reactive, onMounted, ref } from 'vue'
|
||||
import {
|
||||
NGrid,
|
||||
NGridItem,
|
||||
NCard,
|
||||
NForm,
|
||||
NFormItem,
|
||||
NInput,
|
||||
NButton,
|
||||
NInputNumber,
|
||||
NSelect,
|
||||
NSwitch,
|
||||
NAlert,
|
||||
NCollapse,
|
||||
NCollapseItem,
|
||||
NSpace,
|
||||
NIcon,
|
||||
useMessage,
|
||||
useDialog
|
||||
useMessage
|
||||
} from 'naive-ui'
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { getCurrentLocale, setLocale, locales, initLocale, t } from '../../shared/i18n'
|
||||
import { useAuthStore } from '../../shared/stores/auth'
|
||||
import { getEnvironmentConfig, updateEnvironmentConfig, restartEnvironment, type EnvironmentConfig } from '../../shared/api/system'
|
||||
import { getCurrentTimezone, getGroupedTimezoneOptions } from '../../shared/utils/timezone'
|
||||
|
||||
const message = useMessage()
|
||||
const dialog = useDialog()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
// 检查是否为系统管理员
|
||||
const isAdmin = computed(() => {
|
||||
const user = authStore.user
|
||||
return user?.username === 'Administrator' || user?.id === 'Administrator'
|
||||
})
|
||||
|
||||
// 检查是否为 local 运行模式
|
||||
const isLocalMode = computed(() => {
|
||||
return envConfig.run_mode === 'local'
|
||||
})
|
||||
|
||||
// 环境配置
|
||||
const envConfig = reactive<Partial<EnvironmentConfig>>({})
|
||||
const envConfigLoading = ref(false)
|
||||
const envConfigSaving = ref(false)
|
||||
const envConfigRestarting = ref(false)
|
||||
|
||||
const systemSettings = reactive({
|
||||
appName: localStorage.getItem('appName') || 'Jingrow',
|
||||
@ -262,33 +97,6 @@ const pageSizeOptions = [
|
||||
const timezoneOptions = ref<Array<{ type: string; label: string; key: string; children: Array<{ label: string; value: string }> }>>([])
|
||||
const timezoneError = ref<string | null>(null)
|
||||
|
||||
// 数据库类型选项
|
||||
const dbTypeOptions = [
|
||||
{ label: 'MariaDB', value: 'mariadb' },
|
||||
{ label: 'MySQL', value: 'mysql' },
|
||||
{ label: 'PostgreSQL', value: 'postgresql' }
|
||||
]
|
||||
|
||||
// 运行模式选项
|
||||
const runModeOptions = [
|
||||
{ label: 'API', value: 'api' },
|
||||
{ label: 'Local', value: 'local' }
|
||||
]
|
||||
|
||||
// 环境选项
|
||||
const environmentOptions = [
|
||||
{ label: 'Development', value: 'development' },
|
||||
{ label: 'Production', value: 'production' }
|
||||
]
|
||||
|
||||
// 日志级别选项
|
||||
const logLevelOptions = [
|
||||
{ label: 'DEBUG', value: 'DEBUG' },
|
||||
{ label: 'INFO', value: 'INFO' },
|
||||
{ label: 'WARNING', value: 'WARNING' },
|
||||
{ label: 'ERROR', value: 'ERROR' },
|
||||
{ label: 'CRITICAL', value: 'CRITICAL' }
|
||||
]
|
||||
|
||||
const changeLanguage = (locale: string) => {
|
||||
setLocale(locale)
|
||||
@ -310,88 +118,6 @@ const saveSystemSettings = () => {
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
// 加载环境配置
|
||||
const loadEnvironmentConfig = async (showMessage = true) => {
|
||||
if (!isAdmin.value) {
|
||||
return
|
||||
}
|
||||
|
||||
envConfigLoading.value = true
|
||||
try {
|
||||
const result = await getEnvironmentConfig()
|
||||
if (result.success && result.data) {
|
||||
Object.assign(envConfig, result.data)
|
||||
if (showMessage) {
|
||||
message.success(t('Environment configuration loaded'))
|
||||
}
|
||||
} else {
|
||||
if (showMessage) {
|
||||
message.error(result.message || t('Failed to load environment configuration'))
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (showMessage) {
|
||||
message.error(error.message || t('Failed to load environment configuration'))
|
||||
}
|
||||
} finally {
|
||||
envConfigLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 保存环境配置
|
||||
const saveEnvironmentConfig = async () => {
|
||||
if (!isAdmin.value) {
|
||||
message.error(t('Only system administrators can edit environment configuration'))
|
||||
return
|
||||
}
|
||||
|
||||
envConfigSaving.value = true
|
||||
try {
|
||||
const result = await updateEnvironmentConfig(envConfig)
|
||||
if (result.success) {
|
||||
message.success(result.message || t('Environment configuration saved'))
|
||||
// 重新加载配置以获取最新值(静默加载,不显示消息)
|
||||
await loadEnvironmentConfig(false)
|
||||
} else {
|
||||
message.error(result.message || t('Failed to save environment configuration'))
|
||||
}
|
||||
} catch (error: any) {
|
||||
message.error(error.message || t('Failed to save environment configuration'))
|
||||
} finally {
|
||||
envConfigSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 重启环境
|
||||
const handleRestartEnvironment = () => {
|
||||
if (!isAdmin.value) {
|
||||
message.error(t('Only system administrators can restart environment'))
|
||||
return
|
||||
}
|
||||
|
||||
// 确认对话框
|
||||
dialog.warning({
|
||||
title: t('Restart Environment'),
|
||||
content: t('Are you sure you want to restart the environment? This operation may cause service interruption.'),
|
||||
positiveText: t('Restart'),
|
||||
negativeText: t('Cancel'),
|
||||
onPositiveClick: async () => {
|
||||
envConfigRestarting.value = true
|
||||
try {
|
||||
const result = await restartEnvironment()
|
||||
if (result.success) {
|
||||
message.success(result.message || t('Environment restart request submitted. The system will restart shortly.'))
|
||||
} else {
|
||||
message.error(result.message || t('Failed to restart environment'))
|
||||
}
|
||||
} catch (error: any) {
|
||||
message.error(error.message || t('Failed to restart environment'))
|
||||
} finally {
|
||||
envConfigRestarting.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
initLocale()
|
||||
@ -406,10 +132,6 @@ onMounted(async () => {
|
||||
console.error('Failed to load timezone options:', error)
|
||||
}
|
||||
|
||||
// 如果是系统管理员,加载环境配置(静默加载,不显示消息)
|
||||
if (isAdmin.value) {
|
||||
await loadEnvironmentConfig(false)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user