merge: 合并 upstream/main 并解决冲突
解决了以下文件的冲突: - backend/internal/handler/admin/setting_handler.go - 采用 upstream 的字段对齐风格和 *Configured 字段名 - 添加 EnableIdentityPatch 和 IdentityPatchPrompt 字段 - backend/internal/handler/gateway_handler.go - 采用 upstream 的 billingErrorDetails 错误处理方式 - frontend/src/api/admin/settings.ts - 采用 upstream 的 *_configured 字段名 - 添加 enable_identity_patch 和 identity_patch_prompt 字段 - frontend/src/views/admin/SettingsView.vue - 合并 turnstile_secret_key_configured 字段 - 保留 enable_identity_patch 和 identity_patch_prompt 字段
This commit is contained in:
@@ -493,6 +493,7 @@ import { useI18n } from 'vue-i18n'
|
||||
import { getPublicSettings } from '@/api/auth'
|
||||
import { useAuthStore } from '@/stores'
|
||||
import LocaleSwitcher from '@/components/common/LocaleSwitcher.vue'
|
||||
import { sanitizeUrl } from '@/utils/url'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@@ -549,9 +550,9 @@ onMounted(async () => {
|
||||
try {
|
||||
const settings = await getPublicSettings()
|
||||
siteName.value = settings.site_name || 'Sub2API'
|
||||
siteLogo.value = settings.site_logo || ''
|
||||
siteLogo.value = sanitizeUrl(settings.site_logo || '', { allowRelative: true })
|
||||
siteSubtitle.value = settings.site_subtitle || 'AI API Gateway Platform'
|
||||
docUrl.value = settings.doc_url || ''
|
||||
docUrl.value = sanitizeUrl(settings.doc_url || '', { allowRelative: true })
|
||||
} catch (error) {
|
||||
console.error('Failed to load public settings:', error)
|
||||
}
|
||||
|
||||
@@ -255,7 +255,11 @@
|
||||
placeholder="0x4AAAAAAA..."
|
||||
/>
|
||||
<p class="mt-1.5 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.settings.turnstile.secretKeyHint') }}
|
||||
{{
|
||||
form.turnstile_secret_key_configured
|
||||
? t('admin.settings.turnstile.secretKeyConfiguredHint')
|
||||
: t('admin.settings.turnstile.secretKeyHint')
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -577,10 +581,18 @@
|
||||
v-model="form.smtp_password"
|
||||
type="password"
|
||||
class="input"
|
||||
:placeholder="t('admin.settings.smtp.passwordPlaceholder')"
|
||||
:placeholder="
|
||||
form.smtp_password_configured
|
||||
? t('admin.settings.smtp.passwordConfiguredPlaceholder')
|
||||
: t('admin.settings.smtp.passwordPlaceholder')
|
||||
"
|
||||
/>
|
||||
<p class="mt-1.5 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.settings.smtp.passwordHint') }}
|
||||
{{
|
||||
form.smtp_password_configured
|
||||
? t('admin.settings.smtp.passwordConfiguredHint')
|
||||
: t('admin.settings.smtp.passwordHint')
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
@@ -713,7 +725,7 @@
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { adminAPI } from '@/api'
|
||||
import type { SystemSettings } from '@/api/admin/settings'
|
||||
import type { SystemSettings, UpdateSettingsRequest } from '@/api/admin/settings'
|
||||
import AppLayout from '@/components/layout/AppLayout.vue'
|
||||
import Toggle from '@/components/common/Toggle.vue'
|
||||
import { useAppStore } from '@/stores'
|
||||
@@ -735,7 +747,12 @@ const adminApiKeyMasked = ref('')
|
||||
const adminApiKeyOperating = ref(false)
|
||||
const newAdminApiKey = ref('')
|
||||
|
||||
const form = reactive<SystemSettings>({
|
||||
type SettingsForm = SystemSettings & {
|
||||
smtp_password: string
|
||||
turnstile_secret_key: string
|
||||
}
|
||||
|
||||
const form = reactive<SettingsForm>({
|
||||
registration_enabled: true,
|
||||
email_verify_enabled: false,
|
||||
default_balance: 0,
|
||||
@@ -750,6 +767,7 @@ const form = reactive<SystemSettings>({
|
||||
smtp_port: 587,
|
||||
smtp_username: '',
|
||||
smtp_password: '',
|
||||
smtp_password_configured: false,
|
||||
smtp_from_email: '',
|
||||
smtp_from_name: '',
|
||||
smtp_use_tls: true,
|
||||
@@ -757,6 +775,7 @@ const form = reactive<SystemSettings>({
|
||||
turnstile_enabled: false,
|
||||
turnstile_site_key: '',
|
||||
turnstile_secret_key: '',
|
||||
turnstile_secret_key_configured: false,
|
||||
// Identity patch (Claude -> Gemini)
|
||||
enable_identity_patch: true,
|
||||
identity_patch_prompt: ''
|
||||
@@ -805,6 +824,8 @@ async function loadSettings() {
|
||||
try {
|
||||
const settings = await adminAPI.settings.getSettings()
|
||||
Object.assign(form, settings)
|
||||
form.smtp_password = ''
|
||||
form.turnstile_secret_key = ''
|
||||
} catch (error: any) {
|
||||
appStore.showError(
|
||||
t('admin.settings.failedToLoad') + ': ' + (error.message || t('common.unknownError'))
|
||||
@@ -817,7 +838,32 @@ async function loadSettings() {
|
||||
async function saveSettings() {
|
||||
saving.value = true
|
||||
try {
|
||||
await adminAPI.settings.updateSettings(form)
|
||||
const payload: UpdateSettingsRequest = {
|
||||
registration_enabled: form.registration_enabled,
|
||||
email_verify_enabled: form.email_verify_enabled,
|
||||
default_balance: form.default_balance,
|
||||
default_concurrency: form.default_concurrency,
|
||||
site_name: form.site_name,
|
||||
site_logo: form.site_logo,
|
||||
site_subtitle: form.site_subtitle,
|
||||
api_base_url: form.api_base_url,
|
||||
contact_info: form.contact_info,
|
||||
doc_url: form.doc_url,
|
||||
smtp_host: form.smtp_host,
|
||||
smtp_port: form.smtp_port,
|
||||
smtp_username: form.smtp_username,
|
||||
smtp_password: form.smtp_password || undefined,
|
||||
smtp_from_email: form.smtp_from_email,
|
||||
smtp_from_name: form.smtp_from_name,
|
||||
smtp_use_tls: form.smtp_use_tls,
|
||||
turnstile_enabled: form.turnstile_enabled,
|
||||
turnstile_site_key: form.turnstile_site_key,
|
||||
turnstile_secret_key: form.turnstile_secret_key || undefined
|
||||
}
|
||||
const updated = await adminAPI.settings.updateSettings(payload)
|
||||
Object.assign(form, updated)
|
||||
form.smtp_password = ''
|
||||
form.turnstile_secret_key = ''
|
||||
// Refresh cached public settings so sidebar/header update immediately
|
||||
await appStore.fetchPublicSettings(true)
|
||||
appStore.showSuccess(t('admin.settings.settingsSaved'))
|
||||
|
||||
@@ -277,6 +277,14 @@ const errors = reactive({
|
||||
// ==================== Lifecycle ====================
|
||||
|
||||
onMounted(async () => {
|
||||
const expiredFlag = sessionStorage.getItem('auth_expired')
|
||||
if (expiredFlag) {
|
||||
sessionStorage.removeItem('auth_expired')
|
||||
const message = t('auth.reloginRequired')
|
||||
errorMessage.value = message
|
||||
appStore.showWarning(message)
|
||||
}
|
||||
|
||||
try {
|
||||
const settings = await getPublicSettings()
|
||||
turnstileEnabled.value = settings.turnstile_enabled
|
||||
|
||||
@@ -335,12 +335,14 @@
|
||||
/>
|
||||
<span v-else class="text-gray-400">{{ t('keys.selectGroup') }}</span>
|
||||
</template>
|
||||
<template #option="{ option }">
|
||||
<GroupBadge
|
||||
<template #option="{ option, selected }">
|
||||
<GroupOptionItem
|
||||
:name="(option as unknown as GroupOption).label"
|
||||
:platform="(option as unknown as GroupOption).platform"
|
||||
:subscription-type="(option as unknown as GroupOption).subscriptionType"
|
||||
:rate-multiplier="(option as unknown as GroupOption).rate"
|
||||
:description="(option as unknown as GroupOption).description"
|
||||
:selected="selected"
|
||||
/>
|
||||
</template>
|
||||
</Select>
|
||||
@@ -517,26 +519,19 @@
|
||||
? 'bg-primary-50 dark:bg-primary-900/20'
|
||||
: 'hover:bg-gray-100 dark:hover:bg-dark-700'
|
||||
]"
|
||||
:title="option.description || undefined"
|
||||
>
|
||||
<GroupBadge
|
||||
<GroupOptionItem
|
||||
:name="option.label"
|
||||
:platform="option.platform"
|
||||
:subscription-type="option.subscriptionType"
|
||||
:rate-multiplier="option.rate"
|
||||
/>
|
||||
<svg
|
||||
v-if="
|
||||
:description="option.description"
|
||||
:selected="
|
||||
selectedKeyForGroup?.group_id === option.value ||
|
||||
(!selectedKeyForGroup?.group_id && option.value === null)
|
||||
"
|
||||
class="h-4 w-4 shrink-0 text-primary-600 dark:text-primary-400"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -563,6 +558,7 @@ import EmptyState from '@/components/common/EmptyState.vue'
|
||||
import Select from '@/components/common/Select.vue'
|
||||
import UseKeyModal from '@/components/keys/UseKeyModal.vue'
|
||||
import GroupBadge from '@/components/common/GroupBadge.vue'
|
||||
import GroupOptionItem from '@/components/common/GroupOptionItem.vue'
|
||||
import type { ApiKey, Group, PublicSettings, SubscriptionType, GroupPlatform } from '@/types'
|
||||
import type { Column } from '@/components/common/types'
|
||||
import type { BatchApiKeyUsageStats } from '@/api/usage'
|
||||
@@ -571,6 +567,7 @@ import { formatDateTime } from '@/utils/format'
|
||||
interface GroupOption {
|
||||
value: number
|
||||
label: string
|
||||
description: string | null
|
||||
rate: number
|
||||
subscriptionType: SubscriptionType
|
||||
platform: GroupPlatform
|
||||
@@ -666,6 +663,7 @@ const groupOptions = computed(() =>
|
||||
groups.value.map((group) => ({
|
||||
value: group.id,
|
||||
label: group.name,
|
||||
description: group.description,
|
||||
rate: group.rate_multiplier,
|
||||
subscriptionType: group.subscription_type,
|
||||
platform: group.platform
|
||||
|
||||
Reference in New Issue
Block a user