feat(channels): gate available channels behind feature switch (frontend)

Wire the new available_channels_enabled setting end-to-end on the
frontend — hide the user sidebar entry by default and surface the
toggle under Admin Settings > Features.

- types/PublicSettings + api/admin/settings SystemSettings +
  UpdateSettingsRequest: add available_channels_enabled
- stores/app cached fallback: available_channels_enabled: false
- AppSidebar: add flagAvailableChannels getter (strict === true:
  default hide), apply to /available-channels nav entry
- SettingsView: Features tab — new Toggle card, form default/payload
- i18n (zh/en): admin.settings.features.availableChannels.*
This commit is contained in:
erio
2026-04-21 17:29:06 +08:00
parent 5e30563d40
commit e9a763ce24
7 changed files with 53 additions and 2 deletions
+6
View File
@@ -148,6 +148,9 @@ export interface SystemSettings {
// Channel Monitor feature switch
channel_monitor_enabled: boolean
channel_monitor_default_interval_seconds: number
// Available Channels feature switch
available_channels_enabled: boolean
}
export interface UpdateSettingsRequest {
@@ -259,6 +262,9 @@ export interface UpdateSettingsRequest {
// Channel Monitor feature switch
channel_monitor_enabled?: boolean
channel_monitor_default_interval_seconds?: number
// Available Channels feature switch
available_channels_enabled?: boolean
}
/**
@@ -631,6 +631,9 @@ const ChevronDownIcon = {
// getter 返回 false = 隐藏;undefined/true = 显示(宽容策略,避免 public settings 未加载闪烁)。
const flagChannelMonitor = () => appStore.cachedPublicSettings?.channel_monitor_enabled
const flagPayment = () => appStore.cachedPublicSettings?.payment_enabled
// Opt-in feature:默认关闭,未加载 settings 时也视为关闭(严格 === true 判定)
const flagAvailableChannels = () =>
appStore.cachedPublicSettings?.available_channels_enabled === true
const flagOpsMonitoring = () => adminSettingsStore.opsMonitoringEnabled
const flagAdminPayment = () => adminSettingsStore.paymentEnabled
@@ -648,7 +651,7 @@ function buildSelfNavItems(withDashboard: boolean): NavItem[] {
{ path: '/subscriptions', label: t('nav.mySubscriptions'), icon: CreditCardIcon, hideInSimpleMode: true },
{ path: '/purchase', label: t('nav.buySubscription'), icon: RechargeSubscriptionIcon, hideInSimpleMode: true, featureFlag: flagPayment },
{ path: '/orders', label: t('nav.myOrders'), icon: OrderListIcon, hideInSimpleMode: true, featureFlag: flagPayment },
{ path: '/available-channels', label: t('nav.availableChannels'), icon: ChannelIcon, hideInSimpleMode: true },
{ path: '/available-channels', label: t('nav.availableChannels'), icon: ChannelIcon, hideInSimpleMode: true, featureFlag: flagAvailableChannels },
{ path: '/redeem', label: t('nav.redeem'), icon: GiftIcon, hideInSimpleMode: true },
{ path: '/profile', label: t('nav.profile'), icon: UserIcon },
...customMenuItemsForUser.value.map((item): NavItem => ({
+6
View File
@@ -4541,6 +4541,12 @@ export default {
defaultInterval: 'Default check interval (seconds)',
defaultIntervalHint: 'Pre-fills the interval when creating a new monitor; each monitor can override it. Range 15 – 3600.',
},
availableChannels: {
title: 'Available Channels',
description: 'Show logged-in users an aggregate view of the channels, models and pricing they can access. Disabled by default.',
enabled: 'Enable Available Channels',
enabledHint: 'When off, the sidebar entry is hidden and the endpoint returns an empty list.',
},
},
emailTabDisabledTitle: 'Email Verification Not Enabled',
emailTabDisabledHint: 'Enable email verification in the Security tab to configure SMTP settings.',
+6
View File
@@ -4714,6 +4714,12 @@ export default {
defaultInterval: '默认检测间隔(秒)',
defaultIntervalHint: '新建渠道监控时表单的默认值,可被单个渠道覆盖。范围 15 – 3600 秒。',
},
availableChannels: {
title: '可用渠道',
description: '向已登录用户展示他们能访问的渠道、模型和定价聚合视图。默认关闭。',
enabled: '启用可用渠道',
enabledHint: '关闭后用户端侧边栏入口隐藏,接口返回空数组。',
},
},
emailTabDisabledTitle: '邮箱验证未启用',
emailTabDisabledHint: '请在「安全与认证」选项卡中启用邮箱验证后,再配置 SMTP 设置。',
+1
View File
@@ -345,6 +345,7 @@ export const useAppStore = defineStore('app', () => {
balance_low_notify_threshold: 0,
channel_monitor_enabled: true,
channel_monitor_default_interval_seconds: 60,
available_channels_enabled: false,
}
}
+1
View File
@@ -132,6 +132,7 @@ export interface PublicSettings {
balance_low_notify_threshold: number
channel_monitor_enabled: boolean
channel_monitor_default_interval_seconds: number
available_channels_enabled: boolean
}
export interface AuthResponse {
+29 -1
View File
@@ -2416,6 +2416,30 @@
</div>
</div>
<div class="card">
<div class="border-b border-gray-100 px-6 py-4 dark:border-dark-700">
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
{{ t('admin.settings.features.availableChannels.title') }}
</h2>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
{{ t('admin.settings.features.availableChannels.description') }}
</p>
</div>
<div class="space-y-5 p-6">
<div class="flex items-center justify-between">
<div>
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">
{{ t('admin.settings.features.availableChannels.enabled') }}
</label>
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">
{{ t('admin.settings.features.availableChannels.enabledHint') }}
</p>
</div>
<Toggle v-model="form.available_channels_enabled" />
</div>
</div>
</div>
</div><!-- /Tab: Features -->
<!-- Tab: Email -->
@@ -3145,7 +3169,9 @@ const form = reactive<SettingsForm>({
account_quota_notify_emails: [] as NotifyEmailEntry[],
// Channel Monitor feature switch
channel_monitor_enabled: true,
channel_monitor_default_interval_seconds: 60
channel_monitor_default_interval_seconds: 60,
// Available Channels feature switch
available_channels_enabled: false
})
// Proxies for web search emulation ProxySelector
@@ -3749,6 +3775,8 @@ async function saveSettings() {
// Channel Monitor feature switch
channel_monitor_enabled: form.channel_monitor_enabled,
channel_monitor_default_interval_seconds: Number(form.channel_monitor_default_interval_seconds) || 60,
// Available Channels feature switch
available_channels_enabled: form.available_channels_enabled,
}
const updated = await adminAPI.settings.updateSettings(payload)