Commit aa6f2533 authored by IanShaw027's avatar IanShaw027
Browse files

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 字段
parents f60f943d 46dda583
...@@ -493,6 +493,7 @@ import { useI18n } from 'vue-i18n' ...@@ -493,6 +493,7 @@ import { useI18n } from 'vue-i18n'
import { getPublicSettings } from '@/api/auth' import { getPublicSettings } from '@/api/auth'
import { useAuthStore } from '@/stores' import { useAuthStore } from '@/stores'
import LocaleSwitcher from '@/components/common/LocaleSwitcher.vue' import LocaleSwitcher from '@/components/common/LocaleSwitcher.vue'
import { sanitizeUrl } from '@/utils/url'
const { t } = useI18n() const { t } = useI18n()
...@@ -549,9 +550,9 @@ onMounted(async () => { ...@@ -549,9 +550,9 @@ onMounted(async () => {
try { try {
const settings = await getPublicSettings() const settings = await getPublicSettings()
siteName.value = settings.site_name || 'Sub2API' 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' siteSubtitle.value = settings.site_subtitle || 'AI API Gateway Platform'
docUrl.value = settings.doc_url || '' docUrl.value = sanitizeUrl(settings.doc_url || '', { allowRelative: true })
} catch (error) { } catch (error) {
console.error('Failed to load public settings:', error) console.error('Failed to load public settings:', error)
} }
......
...@@ -255,7 +255,11 @@ ...@@ -255,7 +255,11 @@
placeholder="0x4AAAAAAA..." placeholder="0x4AAAAAAA..."
/> />
<p class="mt-1.5 text-xs text-gray-500 dark:text-gray-400"> <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> </p>
</div> </div>
</div> </div>
...@@ -577,10 +581,18 @@ ...@@ -577,10 +581,18 @@
v-model="form.smtp_password" v-model="form.smtp_password"
type="password" type="password"
class="input" 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"> <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> </p>
</div> </div>
<div> <div>
...@@ -713,7 +725,7 @@ ...@@ -713,7 +725,7 @@
import { ref, reactive, onMounted } from 'vue' import { ref, reactive, onMounted } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { adminAPI } from '@/api' 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 AppLayout from '@/components/layout/AppLayout.vue'
import Toggle from '@/components/common/Toggle.vue' import Toggle from '@/components/common/Toggle.vue'
import { useAppStore } from '@/stores' import { useAppStore } from '@/stores'
...@@ -735,7 +747,12 @@ const adminApiKeyMasked = ref('') ...@@ -735,7 +747,12 @@ const adminApiKeyMasked = ref('')
const adminApiKeyOperating = ref(false) const adminApiKeyOperating = ref(false)
const newAdminApiKey = ref('') const newAdminApiKey = ref('')
const form = reactive<SystemSettings>({ type SettingsForm = SystemSettings & {
smtp_password: string
turnstile_secret_key: string
}
const form = reactive<SettingsForm>({
registration_enabled: true, registration_enabled: true,
email_verify_enabled: false, email_verify_enabled: false,
default_balance: 0, default_balance: 0,
...@@ -750,6 +767,7 @@ const form = reactive<SystemSettings>({ ...@@ -750,6 +767,7 @@ const form = reactive<SystemSettings>({
smtp_port: 587, smtp_port: 587,
smtp_username: '', smtp_username: '',
smtp_password: '', smtp_password: '',
smtp_password_configured: false,
smtp_from_email: '', smtp_from_email: '',
smtp_from_name: '', smtp_from_name: '',
smtp_use_tls: true, smtp_use_tls: true,
...@@ -757,6 +775,7 @@ const form = reactive<SystemSettings>({ ...@@ -757,6 +775,7 @@ const form = reactive<SystemSettings>({
turnstile_enabled: false, turnstile_enabled: false,
turnstile_site_key: '', turnstile_site_key: '',
turnstile_secret_key: '', turnstile_secret_key: '',
turnstile_secret_key_configured: false,
// Identity patch (Claude -> Gemini) // Identity patch (Claude -> Gemini)
enable_identity_patch: true, enable_identity_patch: true,
identity_patch_prompt: '' identity_patch_prompt: ''
...@@ -805,6 +824,8 @@ async function loadSettings() { ...@@ -805,6 +824,8 @@ async function loadSettings() {
try { try {
const settings = await adminAPI.settings.getSettings() const settings = await adminAPI.settings.getSettings()
Object.assign(form, settings) Object.assign(form, settings)
form.smtp_password = ''
form.turnstile_secret_key = ''
} catch (error: any) { } catch (error: any) {
appStore.showError( appStore.showError(
t('admin.settings.failedToLoad') + ': ' + (error.message || t('common.unknownError')) t('admin.settings.failedToLoad') + ': ' + (error.message || t('common.unknownError'))
...@@ -817,7 +838,32 @@ async function loadSettings() { ...@@ -817,7 +838,32 @@ async function loadSettings() {
async function saveSettings() { async function saveSettings() {
saving.value = true saving.value = true
try { 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 // Refresh cached public settings so sidebar/header update immediately
await appStore.fetchPublicSettings(true) await appStore.fetchPublicSettings(true)
appStore.showSuccess(t('admin.settings.settingsSaved')) appStore.showSuccess(t('admin.settings.settingsSaved'))
......
...@@ -277,6 +277,14 @@ const errors = reactive({ ...@@ -277,6 +277,14 @@ const errors = reactive({
// ==================== Lifecycle ==================== // ==================== Lifecycle ====================
onMounted(async () => { 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 { try {
const settings = await getPublicSettings() const settings = await getPublicSettings()
turnstileEnabled.value = settings.turnstile_enabled turnstileEnabled.value = settings.turnstile_enabled
......
...@@ -335,12 +335,14 @@ ...@@ -335,12 +335,14 @@
/> />
<span v-else class="text-gray-400">{{ t('keys.selectGroup') }}</span> <span v-else class="text-gray-400">{{ t('keys.selectGroup') }}</span>
</template> </template>
<template #option="{ option }"> <template #option="{ option, selected }">
<GroupBadge <GroupOptionItem
:name="(option as unknown as GroupOption).label" :name="(option as unknown as GroupOption).label"
:platform="(option as unknown as GroupOption).platform" :platform="(option as unknown as GroupOption).platform"
:subscription-type="(option as unknown as GroupOption).subscriptionType" :subscription-type="(option as unknown as GroupOption).subscriptionType"
:rate-multiplier="(option as unknown as GroupOption).rate" :rate-multiplier="(option as unknown as GroupOption).rate"
:description="(option as unknown as GroupOption).description"
:selected="selected"
/> />
</template> </template>
</Select> </Select>
...@@ -517,26 +519,19 @@ ...@@ -517,26 +519,19 @@
? 'bg-primary-50 dark:bg-primary-900/20' ? 'bg-primary-50 dark:bg-primary-900/20'
: 'hover:bg-gray-100 dark:hover:bg-dark-700' : 'hover:bg-gray-100 dark:hover:bg-dark-700'
]" ]"
:title="option.description || undefined"
> >
<GroupBadge <GroupOptionItem
:name="option.label" :name="option.label"
:platform="option.platform" :platform="option.platform"
:subscription-type="option.subscriptionType" :subscription-type="option.subscriptionType"
:rate-multiplier="option.rate" :rate-multiplier="option.rate"
/> :description="option.description"
<svg :selected="
v-if="
selectedKeyForGroup?.group_id === option.value || selectedKeyForGroup?.group_id === option.value ||
(!selectedKeyForGroup?.group_id && option.value === null) (!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> </button>
</div> </div>
</div> </div>
...@@ -563,6 +558,7 @@ import EmptyState from '@/components/common/EmptyState.vue' ...@@ -563,6 +558,7 @@ import EmptyState from '@/components/common/EmptyState.vue'
import Select from '@/components/common/Select.vue' import Select from '@/components/common/Select.vue'
import UseKeyModal from '@/components/keys/UseKeyModal.vue' import UseKeyModal from '@/components/keys/UseKeyModal.vue'
import GroupBadge from '@/components/common/GroupBadge.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 { ApiKey, Group, PublicSettings, SubscriptionType, GroupPlatform } from '@/types'
import type { Column } from '@/components/common/types' import type { Column } from '@/components/common/types'
import type { BatchApiKeyUsageStats } from '@/api/usage' import type { BatchApiKeyUsageStats } from '@/api/usage'
...@@ -571,6 +567,7 @@ import { formatDateTime } from '@/utils/format' ...@@ -571,6 +567,7 @@ import { formatDateTime } from '@/utils/format'
interface GroupOption { interface GroupOption {
value: number value: number
label: string label: string
description: string | null
rate: number rate: number
subscriptionType: SubscriptionType subscriptionType: SubscriptionType
platform: GroupPlatform platform: GroupPlatform
...@@ -666,6 +663,7 @@ const groupOptions = computed(() => ...@@ -666,6 +663,7 @@ const groupOptions = computed(() =>
groups.value.map((group) => ({ groups.value.map((group) => ({
value: group.id, value: group.id,
label: group.name, label: group.name,
description: group.description,
rate: group.rate_multiplier, rate: group.rate_multiplier,
subscriptionType: group.subscription_type, subscriptionType: group.subscription_type,
platform: group.platform platform: group.platform
......
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import checker from 'vite-plugin-checker';
import { resolve } from 'path';
export default defineConfig({
plugins: [
vue(),
checker({
typescript: true,
vueTsc: true
})
],
resolve: {
alias: {
'@': resolve(__dirname, 'src')
}
},
build: {
outDir: '../backend/internal/web/dist',
emptyOutDir: true
},
server: {
host: '0.0.0.0',
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
},
'/setup': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
});
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment