feat(app-switcher): читать конфиг из auth-portal вместо локального editor
CURRENT_APP_ID cfdm; настройка ссылок только на портале. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -1,64 +1,15 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
CURRENT_APP_ID,
|
||||
DEFAULT_APP_SWITCHER_CONFIG,
|
||||
getCurrentApp,
|
||||
parseAppSwitcherConfig,
|
||||
} from '@/lib/app-switcher-config'
|
||||
|
||||
describe('parseAppSwitcherConfig', () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('возвращает дефолты при пустом значении', () => {
|
||||
expect(parseAppSwitcherConfig()).toEqual(DEFAULT_APP_SWITCHER_CONFIG)
|
||||
expect(parseAppSwitcherConfig('')).toEqual(DEFAULT_APP_SWITCHER_CONFIG)
|
||||
})
|
||||
|
||||
it('дефолты содержат оба приложения', () => {
|
||||
describe('DEFAULT_APP_SWITCHER_CONFIG', () => {
|
||||
it('использует portal app ids', () => {
|
||||
const ids = DEFAULT_APP_SWITCHER_CONFIG.apps.map((app) => app.id)
|
||||
expect(ids).toContain('vps-tracker')
|
||||
expect(ids).toContain('cfdm')
|
||||
})
|
||||
|
||||
it('парсит override из JSON', () => {
|
||||
const raw = JSON.stringify({
|
||||
menuLabel: 'Сервисы',
|
||||
apps: [
|
||||
{
|
||||
id: 'vps-tracker',
|
||||
name: 'VPS',
|
||||
url: 'http://localhost:5173',
|
||||
icon: 'server',
|
||||
},
|
||||
{
|
||||
id: 'cfdm',
|
||||
name: 'CFDM',
|
||||
url: 'http://localhost:5174',
|
||||
icon: 'cloud',
|
||||
},
|
||||
{
|
||||
id: 'grafana',
|
||||
name: 'Grafana',
|
||||
url: 'https://grafana.example.com',
|
||||
icon: 'chart',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const config = parseAppSwitcherConfig(raw)
|
||||
expect(config.menuLabel).toBe('Сервисы')
|
||||
expect(config.apps).toHaveLength(3)
|
||||
expect(config.apps[2]?.name).toBe('Grafana')
|
||||
})
|
||||
|
||||
it('при невалидном JSON возвращает дефолты и пишет предупреждение', () => {
|
||||
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
|
||||
expect(parseAppSwitcherConfig('{invalid')).toEqual(DEFAULT_APP_SWITCHER_CONFIG)
|
||||
expect(warnSpy).toHaveBeenCalled()
|
||||
expect(ids).toEqual(['vps', 'cfdm', 'bgp'])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -66,6 +17,7 @@ describe('getCurrentApp', () => {
|
||||
it('находит текущее приложение по CURRENT_APP_ID', () => {
|
||||
const current = getCurrentApp(DEFAULT_APP_SWITCHER_CONFIG)
|
||||
expect(current.id).toBe(CURRENT_APP_ID)
|
||||
expect(CURRENT_APP_ID).toBe('cfdm')
|
||||
expect(current.name).toBe('CF Domain Manager')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -6,15 +6,17 @@ import {
|
||||
ServerIcon,
|
||||
type LucideIcon,
|
||||
} from 'lucide-react'
|
||||
import { z } from 'zod'
|
||||
import type { AppSwitcherConfig, AppSwitcherEntry } from '@cfdm/shared'
|
||||
|
||||
/** JWT / portal app id for this product */
|
||||
export const CURRENT_APP_ID = 'cfdm'
|
||||
|
||||
const appSwitcherIconSchema = z.enum(['server', 'cloud', 'globe', 'dashboard', 'chart'])
|
||||
export type AppSwitcherIconName = keyof typeof APP_SWITCHER_ICONS
|
||||
|
||||
export type AppSwitcherIconName = z.infer<typeof appSwitcherIconSchema>
|
||||
|
||||
export const APP_SWITCHER_ICONS: Record<AppSwitcherIconName, LucideIcon> = {
|
||||
export const APP_SWITCHER_ICONS: Record<
|
||||
'server' | 'cloud' | 'globe' | 'dashboard' | 'chart',
|
||||
LucideIcon
|
||||
> = {
|
||||
server: ServerIcon,
|
||||
cloud: CloudIcon,
|
||||
globe: GlobeIcon,
|
||||
@@ -22,80 +24,43 @@ export const APP_SWITCHER_ICONS: Record<AppSwitcherIconName, LucideIcon> = {
|
||||
chart: ChartBarIcon,
|
||||
}
|
||||
|
||||
const appSwitcherEntrySchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
subtitle: z.string().optional(),
|
||||
url: z.string(),
|
||||
icon: appSwitcherIconSchema.default('server'),
|
||||
shortcut: z.string().optional(),
|
||||
})
|
||||
|
||||
const appSwitcherConfigSchema = z.object({
|
||||
menuLabel: z.string().default('Приложения'),
|
||||
apps: z.array(appSwitcherEntrySchema).min(1),
|
||||
})
|
||||
|
||||
export type AppSwitcherEntry = z.infer<typeof appSwitcherEntrySchema>
|
||||
export type AppSwitcherConfig = z.infer<typeof appSwitcherConfigSchema>
|
||||
|
||||
/** Offline fallback when auth-portal is unreachable */
|
||||
export const DEFAULT_APP_SWITCHER_CONFIG: AppSwitcherConfig = {
|
||||
menuLabel: 'Приложения',
|
||||
apps: [
|
||||
{
|
||||
id: 'vps-tracker',
|
||||
id: 'vps',
|
||||
name: 'VPS Tracker',
|
||||
subtitle: 'Учёт виртуальных серверов',
|
||||
url: 'http://192.168.100.67:3001',
|
||||
url: 'https://vps.shnt.top',
|
||||
icon: 'server',
|
||||
shortcut: '⌘1',
|
||||
},
|
||||
{
|
||||
id: 'cfdm',
|
||||
name: 'CF Domain Manager',
|
||||
subtitle: 'Управление доменами',
|
||||
url: 'http://192.168.100.67:6363',
|
||||
url: 'https://cfdm.shnt.top',
|
||||
icon: 'cloud',
|
||||
shortcut: '⌘2',
|
||||
},
|
||||
{
|
||||
id: 'evobgp',
|
||||
id: 'bgp',
|
||||
name: 'EvoBGP',
|
||||
subtitle: 'BGP маршрутизация',
|
||||
url: 'http://192.168.100.67:3000',
|
||||
url: 'https://bgp.shnt.top',
|
||||
icon: 'globe',
|
||||
shortcut: '⌘3',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export function parseAppSwitcherConfig(raw?: string): AppSwitcherConfig {
|
||||
if (!raw?.trim()) {
|
||||
return DEFAULT_APP_SWITCHER_CONFIG
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(raw) as unknown
|
||||
return appSwitcherConfigSchema.parse(parsed)
|
||||
} catch (error) {
|
||||
console.warn('Invalid VITE_APP_SWITCHER, using defaults:', error)
|
||||
return DEFAULT_APP_SWITCHER_CONFIG
|
||||
}
|
||||
}
|
||||
|
||||
export function getAppSwitcherConfig(): AppSwitcherConfig {
|
||||
return parseAppSwitcherConfig(import.meta.env.VITE_APP_SWITCHER)
|
||||
}
|
||||
|
||||
export function getAppUrl(
|
||||
appId: string,
|
||||
config: AppSwitcherConfig = getAppSwitcherConfig(),
|
||||
config: AppSwitcherConfig = DEFAULT_APP_SWITCHER_CONFIG,
|
||||
): string | undefined {
|
||||
return config.apps.find((app) => app.id === appId)?.url
|
||||
}
|
||||
|
||||
export function getCurrentApp(
|
||||
config: AppSwitcherConfig = getAppSwitcherConfig(),
|
||||
config: AppSwitcherConfig = DEFAULT_APP_SWITCHER_CONFIG,
|
||||
): AppSwitcherEntry {
|
||||
return config.apps.find((app) => app.id === CURRENT_APP_ID) ?? config.apps[0]!
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user