mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-09-24 16:05:44 +08:00
Merge pull request #3864 from fengshao1227/fix/frontend-i18n-and-site-logo-sanitize
fix(frontend): 补齐 en 语言包缺失 key 并对 site_logo 统一应用 sanitizeUrl
This commit is contained in:
@@ -194,6 +194,7 @@ import { useI18n } from 'vue-i18n'
|
||||
import { useAdminSettingsStore, useAppStore, useAuthStore, useOnboardingStore } from '@/stores'
|
||||
import VersionBadge from '@/components/common/VersionBadge.vue'
|
||||
import { sanitizeSvg } from '@/utils/sanitize'
|
||||
import { sanitizeUrl } from '@/utils/url'
|
||||
import { FeatureFlags, makeSidebarFlag } from '@/utils/featureFlags'
|
||||
import { useBatchImageAccess } from '@/composables/useBatchImageAccess'
|
||||
|
||||
@@ -256,7 +257,7 @@ const expandedGroups = ref<Set<string>>(new Set())
|
||||
|
||||
// Site settings from appStore (cached, no flicker)
|
||||
const siteName = computed(() => appStore.siteName)
|
||||
const siteLogo = computed(() => appStore.siteLogo)
|
||||
const siteLogo = computed(() => sanitizeUrl(appStore.siteLogo || '', { allowRelative: true, allowDataUrl: true }))
|
||||
const siteVersion = computed(() => appStore.siteVersion)
|
||||
const settingsLoaded = computed(() => appStore.publicSettingsLoaded)
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { dirname, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
const dir = dirname(fileURLToPath(import.meta.url))
|
||||
const sidebarSource = readFileSync(resolve(dir, '../AppSidebar.vue'), 'utf8')
|
||||
const homeViewSource = readFileSync(resolve(dir, '../../../views/HomeView.vue'), 'utf8')
|
||||
const keyUsageViewSource = readFileSync(resolve(dir, '../../../views/KeyUsageView.vue'), 'utf8')
|
||||
|
||||
describe('site_logo sanitization', () => {
|
||||
it('AppSidebar imports sanitizeUrl and applies it to siteLogo', () => {
|
||||
expect(sidebarSource).toContain("import { sanitizeUrl } from '@/utils/url'")
|
||||
expect(sidebarSource).toContain('sanitizeUrl(appStore.siteLogo')
|
||||
})
|
||||
|
||||
it('HomeView applies sanitizeUrl to siteLogo', () => {
|
||||
expect(homeViewSource).toContain('sanitizeUrl(appStore.cachedPublicSettings?.site_logo || appStore.siteLogo')
|
||||
})
|
||||
|
||||
it('KeyUsageView applies sanitizeUrl to siteLogo', () => {
|
||||
expect(keyUsageViewSource).toContain('sanitizeUrl(appStore.cachedPublicSettings?.site_logo || appStore.siteLogo')
|
||||
})
|
||||
|
||||
it('all three pass allowRelative and allowDataUrl options', () => {
|
||||
for (const src of [sidebarSource, homeViewSource, keyUsageViewSource]) {
|
||||
expect(src).toContain('allowRelative: true')
|
||||
expect(src).toContain('allowDataUrl: true')
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,38 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import en from '@/i18n/locales/en'
|
||||
|
||||
function flattenKeys(obj: Record<string, any>, prefix = ''): string[] {
|
||||
const keys: string[] = []
|
||||
for (const [k, v] of Object.entries(obj)) {
|
||||
const fullKey = prefix ? `${prefix}.${k}` : k
|
||||
if (typeof v === 'object' && v !== null && !Array.isArray(v)) {
|
||||
keys.push(...flattenKeys(v, fullKey))
|
||||
} else {
|
||||
keys.push(fullKey)
|
||||
}
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
describe('ops locale key completeness', () => {
|
||||
const requiredKeys = [
|
||||
'admin.ops.result',
|
||||
'admin.ops.timeRange.custom',
|
||||
'admin.ops.customTimeRange.startTime',
|
||||
'admin.ops.customTimeRange.endTime',
|
||||
]
|
||||
|
||||
for (const key of requiredKeys) {
|
||||
it(`en locale has ${key}`, () => {
|
||||
const enKeys = flattenKeys(en)
|
||||
expect(enKeys).toContain(key)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
describe('groups locale key completeness', () => {
|
||||
it('en locale has admin.groups.failedToSave', () => {
|
||||
const enKeys = flattenKeys(en)
|
||||
expect(enKeys).toContain('admin.groups.failedToSave')
|
||||
})
|
||||
})
|
||||
@@ -23,6 +23,7 @@ export default {
|
||||
lastRun: 'last_run:',
|
||||
lastSuccess: 'last_success:',
|
||||
lastError: 'last_error:',
|
||||
result: 'Result',
|
||||
noData: 'No data.',
|
||||
loadingText: 'loading',
|
||||
ready: 'ready',
|
||||
@@ -143,7 +144,12 @@ export default {
|
||||
'6h': 'Last 6 hours',
|
||||
'24h': 'Last 24 hours',
|
||||
'7d': 'Last 7 days',
|
||||
'30d': 'Last 30 days'
|
||||
'30d': 'Last 30 days',
|
||||
custom: 'Custom Range'
|
||||
},
|
||||
customTimeRange: {
|
||||
startTime: 'Start Time',
|
||||
endTime: 'End Time'
|
||||
},
|
||||
openaiTokenStats: {
|
||||
title: 'OpenAI Token Request Stats',
|
||||
|
||||
@@ -770,6 +770,7 @@ export default {
|
||||
failedToLoad: 'Failed to load groups',
|
||||
failedToCreate: 'Failed to create group',
|
||||
failedToUpdate: 'Failed to update group',
|
||||
failedToSave: 'Failed to save group',
|
||||
failedToDelete: 'Failed to delete group',
|
||||
nameRequired: 'Please enter group name',
|
||||
rateMultipliers: 'Rate Multipliers',
|
||||
|
||||
@@ -419,7 +419,7 @@ const appStore = useAppStore()
|
||||
|
||||
// Site settings - directly from appStore (already initialized from injected config)
|
||||
const siteName = computed(() => appStore.cachedPublicSettings?.site_name || appStore.siteName || 'Sub2API')
|
||||
const siteLogo = computed(() => appStore.cachedPublicSettings?.site_logo || appStore.siteLogo || '')
|
||||
const siteLogo = computed(() => sanitizeUrl(appStore.cachedPublicSettings?.site_logo || appStore.siteLogo || '', { allowRelative: true, allowDataUrl: true }))
|
||||
const siteSubtitle = computed(() => appStore.cachedPublicSettings?.site_subtitle || 'AI API Gateway Platform')
|
||||
const docUrl = computed(() => sanitizeUrl(appStore.cachedPublicSettings?.doc_url || appStore.docUrl || ''))
|
||||
const homeContent = computed(() => appStore.cachedPublicSettings?.home_content || '')
|
||||
|
||||
@@ -431,7 +431,7 @@ const appStore = useAppStore()
|
||||
// ==================== Site Settings (same as HomeView) ====================
|
||||
|
||||
const siteName = computed(() => appStore.cachedPublicSettings?.site_name || appStore.siteName || 'Sub2API')
|
||||
const siteLogo = computed(() => appStore.cachedPublicSettings?.site_logo || appStore.siteLogo || '')
|
||||
const siteLogo = computed(() => sanitizeUrl(appStore.cachedPublicSettings?.site_logo || appStore.siteLogo || '', { allowRelative: true, allowDataUrl: true }))
|
||||
const docUrl = computed(() => sanitizeUrl(appStore.cachedPublicSettings?.doc_url || appStore.docUrl || ''))
|
||||
const githubUrl = 'https://github.com/Wei-Shaw/sub2api'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user