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:
erio
2026-04-21 00:21:29 +08:00
parent a1425b457d
commit 7da5124067
18 changed files with 283 additions and 14 deletions
+55
View File
@@ -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);