feat(channel-monitor): add feature switch settings + fix extra_models save
Settings:
- New "功能开关" tab between 通用设置 and 安全与认证
- ChannelMonitorEnabled toggle: runner skips scheduling when false,
user-facing list returns empty
- ChannelMonitorDefaultIntervalSeconds (15-3600): pre-fills interval
when creating a new monitor; each monitor can still override
Bug fix:
- ModelTagInput now commits pending input on blur, not just Enter/Tab.
Previously clicking "save" with an un-Enter'd extra model would drop
the value (DB stored extra_models=[] even when user typed entries).
Backend:
- domain_constants: SettingKeyChannelMonitor{Enabled,DefaultIntervalSeconds}
- SettingService.GetChannelMonitorRuntime: lightweight getter used by
runner tick + user handler per-request (fail-open on DB error)
- Runner tickDueChecks: bails early when feature disabled
- ChannelMonitorUserHandler: checks feature flag before serving
- Comment on runner doc: scheduler state is implicit (every tick re-reads
ListEnabled from DB), so CRUD ops on monitors self-maintain the schedule
Bump VERSION to 0.1.114.25
This commit is contained in:
@@ -469,6 +469,10 @@ export interface SystemSettings {
|
||||
balance_low_notify_recharge_url: string;
|
||||
account_quota_notify_enabled: boolean;
|
||||
account_quota_notify_emails: NotifyEmailEntry[];
|
||||
|
||||
// Channel Monitor feature switch
|
||||
channel_monitor_enabled: boolean;
|
||||
channel_monitor_default_interval_seconds: number;
|
||||
}
|
||||
|
||||
export interface UpdateSettingsRequest {
|
||||
@@ -618,6 +622,10 @@ export interface UpdateSettingsRequest {
|
||||
balance_low_notify_recharge_url?: string;
|
||||
account_quota_notify_enabled?: boolean;
|
||||
account_quota_notify_emails?: NotifyEmailEntry[];
|
||||
|
||||
// Channel Monitor feature switch
|
||||
channel_monitor_enabled?: boolean;
|
||||
channel_monitor_default_interval_seconds?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
@keydown.tab.prevent="addModel"
|
||||
@keydown.delete="handleBackspace"
|
||||
@paste="handlePaste"
|
||||
@blur="addModel"
|
||||
/>
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-gray-400">
|
||||
|
||||
@@ -143,6 +143,13 @@ const emit = defineEmits<{
|
||||
const { t } = useI18n()
|
||||
const appStore = useAppStore()
|
||||
|
||||
// System-configured default interval for new monitors. Falls back to the static
|
||||
// constant when public settings haven't loaded yet or store the legacy 0 value.
|
||||
const systemDefaultInterval = computed<number>(() => {
|
||||
const configured = appStore.cachedPublicSettings?.channel_monitor_default_interval_seconds
|
||||
return configured && configured > 0 ? configured : DEFAULT_INTERVAL_SECONDS
|
||||
})
|
||||
|
||||
// editing is true when we have an existing monitor
|
||||
const editing = computed<ChannelMonitor | null>(() => props.monitor)
|
||||
|
||||
@@ -173,7 +180,7 @@ const form = reactive<MonitorForm>({
|
||||
primary_model: '',
|
||||
extra_models: [],
|
||||
group_name: '',
|
||||
interval_seconds: DEFAULT_INTERVAL_SECONDS,
|
||||
interval_seconds: systemDefaultInterval.value,
|
||||
enabled: true,
|
||||
})
|
||||
|
||||
@@ -191,7 +198,7 @@ function resetForm() {
|
||||
form.primary_model = ''
|
||||
form.extra_models = []
|
||||
form.group_name = ''
|
||||
form.interval_seconds = DEFAULT_INTERVAL_SECONDS
|
||||
form.interval_seconds = systemDefaultInterval.value
|
||||
form.enabled = true
|
||||
}
|
||||
|
||||
@@ -203,7 +210,7 @@ function loadFromMonitor(m: ChannelMonitor) {
|
||||
form.primary_model = m.primary_model
|
||||
form.extra_models = [...(m.extra_models || [])]
|
||||
form.group_name = m.group_name || ''
|
||||
form.interval_seconds = m.interval_seconds || DEFAULT_INTERVAL_SECONDS
|
||||
form.interval_seconds = m.interval_seconds || systemDefaultInterval.value
|
||||
form.enabled = m.enabled
|
||||
}
|
||||
|
||||
|
||||
@@ -4530,6 +4530,7 @@ export default {
|
||||
description: 'Manage registration, email verification, default values, and SMTP settings',
|
||||
tabs: {
|
||||
general: 'General',
|
||||
features: 'Feature Switches',
|
||||
security: 'Security',
|
||||
users: 'Users',
|
||||
gateway: 'Gateway',
|
||||
@@ -4537,6 +4538,16 @@ export default {
|
||||
backup: 'Backup',
|
||||
payment: 'Payment',
|
||||
},
|
||||
features: {
|
||||
channelMonitor: {
|
||||
title: 'Channel Monitor',
|
||||
description: 'Periodically probe configured channels and surface availability / latency to users. Turning it off stops the scheduler and returns an empty list on the user page.',
|
||||
enabled: 'Enable Channel Monitor',
|
||||
enabledHint: 'Disabling stops background checks; existing history is preserved.',
|
||||
defaultInterval: 'Default check interval (seconds)',
|
||||
defaultIntervalHint: 'Pre-fills the interval when creating a new monitor; each monitor can override it. Range 15 – 3600.',
|
||||
},
|
||||
},
|
||||
emailTabDisabledTitle: 'Email Verification Not Enabled',
|
||||
emailTabDisabledHint: 'Enable email verification in the Security tab to configure SMTP settings.',
|
||||
registration: {
|
||||
|
||||
@@ -4695,6 +4695,7 @@ export default {
|
||||
description: '管理注册、邮箱验证、默认值和 SMTP 设置',
|
||||
tabs: {
|
||||
general: '通用设置',
|
||||
features: '功能开关',
|
||||
security: '安全与认证',
|
||||
users: '用户默认值',
|
||||
gateway: '网关服务',
|
||||
@@ -4702,6 +4703,16 @@ export default {
|
||||
backup: '数据备份',
|
||||
payment: '支付设置',
|
||||
},
|
||||
features: {
|
||||
channelMonitor: {
|
||||
title: '渠道监控',
|
||||
description: '定期对配置的渠道发起健康检查,向用户展示可用性与延迟。关闭后调度器停止扫描,用户端列表为空。',
|
||||
enabled: '启用渠道监控',
|
||||
enabledHint: '关闭后后台不再执行定时检测,已有数据保留。',
|
||||
defaultInterval: '默认检测间隔(秒)',
|
||||
defaultIntervalHint: '新建渠道监控时表单的默认值,可被单个渠道覆盖。范围 15 – 3600 秒。',
|
||||
},
|
||||
},
|
||||
emailTabDisabledTitle: '邮箱验证未启用',
|
||||
emailTabDisabledHint: '请在「安全与认证」选项卡中启用邮箱验证后,再配置 SMTP 设置。',
|
||||
registration: {
|
||||
|
||||
@@ -352,6 +352,8 @@ export const useAppStore = defineStore('app', () => {
|
||||
balance_low_notify_enabled: false,
|
||||
account_quota_notify_enabled: false,
|
||||
balance_low_notify_threshold: 0,
|
||||
channel_monitor_enabled: true,
|
||||
channel_monitor_default_interval_seconds: 60,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -185,6 +185,8 @@ export interface PublicSettings {
|
||||
balance_low_notify_enabled: boolean
|
||||
account_quota_notify_enabled: boolean
|
||||
balance_low_notify_threshold: number
|
||||
channel_monitor_enabled: boolean
|
||||
channel_monitor_default_interval_seconds: number
|
||||
}
|
||||
|
||||
export interface AuthResponse {
|
||||
|
||||
@@ -3749,6 +3749,52 @@
|
||||
</div>
|
||||
<!-- /Tab: General -->
|
||||
|
||||
<!-- Tab: Features (功能开关) -->
|
||||
<div v-show="activeTab === 'features'" class="space-y-6">
|
||||
|
||||
<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.channelMonitor.title') }}
|
||||
</h2>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.settings.features.channelMonitor.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.channelMonitor.enabled') }}
|
||||
</label>
|
||||
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.settings.features.channelMonitor.enabledHint') }}
|
||||
</p>
|
||||
</div>
|
||||
<Toggle v-model="form.channel_monitor_enabled" />
|
||||
</div>
|
||||
|
||||
<div v-if="form.channel_monitor_enabled">
|
||||
<label class="input-label">
|
||||
{{ t('admin.settings.features.channelMonitor.defaultInterval') }}
|
||||
<span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
v-model.number="form.channel_monitor_default_interval_seconds"
|
||||
type="number"
|
||||
min="15"
|
||||
max="3600"
|
||||
class="input"
|
||||
/>
|
||||
<p class="mt-1 text-xs text-gray-400">
|
||||
{{ t('admin.settings.features.channelMonitor.defaultIntervalHint') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /Tab: Features -->
|
||||
|
||||
<!-- Tab: Email -->
|
||||
<!-- Tab: Payment -->
|
||||
<div v-show="activeTab === 'payment'" class="space-y-6">
|
||||
@@ -4737,6 +4783,7 @@ const paymentMethodsHref = computed(() =>
|
||||
|
||||
type SettingsTab =
|
||||
| "general"
|
||||
| "features"
|
||||
| "security"
|
||||
| "users"
|
||||
| "gateway"
|
||||
@@ -4746,6 +4793,7 @@ type SettingsTab =
|
||||
const activeTab = ref<SettingsTab>("general");
|
||||
const settingsTabs = [
|
||||
{ key: "general" as SettingsTab, icon: "home" as const },
|
||||
{ key: "features" as SettingsTab, icon: "bolt" as const },
|
||||
{ key: "security" as SettingsTab, icon: "shield" as const },
|
||||
{ key: "users" as SettingsTab, icon: "user" as const },
|
||||
{ key: "gateway" as SettingsTab, icon: "server" as const },
|
||||
@@ -5005,6 +5053,9 @@ const form = reactive<SettingsForm>({
|
||||
balance_low_notify_recharge_url: "",
|
||||
account_quota_notify_enabled: false,
|
||||
account_quota_notify_emails: [] as NotifyEmailEntry[],
|
||||
// Channel Monitor feature switch
|
||||
channel_monitor_enabled: true,
|
||||
channel_monitor_default_interval_seconds: 60,
|
||||
});
|
||||
|
||||
const authSourceDefaults = reactive<AuthSourceDefaultsState>(
|
||||
@@ -5912,6 +5963,10 @@ async function saveSettings() {
|
||||
account_quota_notify_emails: (
|
||||
form.account_quota_notify_emails || []
|
||||
).filter((e) => e.email.trim() !== ""),
|
||||
// Channel Monitor feature switch
|
||||
channel_monitor_enabled: form.channel_monitor_enabled,
|
||||
channel_monitor_default_interval_seconds:
|
||||
Number(form.channel_monitor_default_interval_seconds) || 60,
|
||||
};
|
||||
|
||||
appendAuthSourceDefaultsToUpdateRequest(payload, authSourceDefaults);
|
||||
|
||||
Reference in New Issue
Block a user