feat(channel): support automatically test disabled channels - #2327
feat(channel): support automatically test disabled channels#2327faithleysath wants to merge 4723 commits into
Conversation
refactor: Openai video model 移动到 dto
feat: jimeng use openai sdk input_reference i2v
将 `ClaudeMediaMessage.Thinking` 的类型从 `string` 修改为 `*string`,以解决 `omitempty` 导致 `"thinking": ""` 字段在 JSON 序列化时被忽略的问题。 同时更新了 `service/convert.go` 和 `relay/channel/claude/relay-claude.go` 中的相关逻辑,以兼容新的指针类型,确保生成的 Claude 事件流符合官方规范。
1. 将对SiliconFlow渠道的RelayModeImagesGenerations请求,转发至v1/images/generations端点。 2. SiliconFlow图像生成接口额外参数适配。
1. 解析ImageRequest的Extra时,处理err 2. DoResponse方法添加RelayModeImagesGenerations(fallthrough)
fix(convert): 修复 OpenAI 转 Claude 流时 thinking 块的格式问题
feat: 添加SiliconFlow图像生成接口自动转换支持
feat: endpoint type log
… improve error handling
fix: volcengine && baidu claude adapter
fix: volcengine claude DoResponse
fix: volcengine claude DoResponse
…review-oai OAI生图接口支持gemini 3 pro image preview
fix: gemini image correct generationConfig
Gemini Veo3.1[AI Studio]增加图生视频支持
Ensure image file is closed using defer after opening.
Gemini Image系列支持图像编辑
- Add independent scheduling for testing disabled channels - Add AutoTestDisabledChannelMinutes configuration - Refactor channel testing logic to reuse testOneChannel - Update monitoring settings UI
WalkthroughAdds per-channel test extraction, periodic testing of disabled channels via a new configurable interval and dedicated goroutine, a DB helper to fetch channels by status, and a UI field to control disabled-channel test frequency. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Scheduler as Scheduler (AutomaticallyTestChannels)
participant WorkerE as Enabled-Channel Worker (goroutine)
participant WorkerD as Disabled-Channel Worker (goroutine)
participant DB as Database (model)
participant Tester as Channel Tester (testOneChannel)
participant Notifier as Notifier
Scheduler->>WorkerE: spawn enabled-channel worker (periodic)
Scheduler->>WorkerD: spawn disabled-channel worker (periodic, interval from AutoTestDisabledChannelMinutes)
WorkerE->>DB: GetChannelsByStatus(enabled)
DB-->>WorkerE: channels list
WorkerE->>Tester: testOneChannel(channel, threshold)
Tester-->>WorkerE: result (ok/error/response time)
alt state change required
WorkerE->>DB: update channel status
WorkerE->>Notifier: optional notify
end
WorkerD->>DB: GetChannelsByStatus(disabled)
DB-->>WorkerD: disabled channels list
WorkerD->>Tester: testOneChannel(channel, threshold)
Tester-->>WorkerD: result (ok/error/response time)
alt re-enable condition met
WorkerD->>DB: update channel status (enable)
WorkerD->>Notifier: optional notify
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
controller/channel-test.go (1)
536-570: testOneChannel refactor looks correct; consider guarding context usageThe extracted
testOneChannelkeeps the previous behavior:
- Disables enabled channels only when
ShouldDisableChannelor the response-time threshold say so and auto-ban is on.- Re-enables only auto-disabled channels when there is no error and automatic enable is on.
- Always updates
response_timebased on the measured duration.This is logically sound. One defensive improvement you might consider is checking
result.context != nilbefore passing it intoprocessChannelError/EnableChannel, to make this function robust against any future changes totestChannelthat might return anewAPIErrorwithout populatingcontext.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
controller/channel-test.go(3 hunks)model/channel.go(1 hunks)setting/operation_setting/monitor_setting.go(2 hunks)web/src/pages/Setting/Operation/SettingsMonitoring.jsx(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
model/channel.go (1)
model/main.go (1)
DB(64-64)
web/src/pages/Setting/Operation/SettingsMonitoring.jsx (1)
web/src/components/settings/OperationSetting.jsx (1)
inputs(32-74)
controller/channel-test.go (9)
model/channel.go (3)
Channel(21-58)ChannelInfo(60-68)GetChannelsByStatus(1007-1011)common/constants.go (5)
ChannelStatusEnabled(195-195)AutomaticDisableChannelEnabled(103-103)ChannelStatusAutoDisabled(197-197)ChannelDisableThreshold(102-102)RequestInterval(115-115)service/channel.go (3)
ShouldDisableChannel(47-99)ShouldEnableChannel(101-112)EnableChannel(38-45)types/error.go (2)
NewOpenAIError(226-249)ErrorCodeChannelResponseTimeExceeded(58-58)types/channel_error.go (1)
NewChannelError(12-21)common/gin.go (1)
GetContextKeyString(70-72)constant/context_key.go (1)
ContextKeyChannelKey(37-37)setting/operation_setting/monitor_setting.go (1)
GetMonitorSetting(28-43)common/sys_log.go (1)
SysLog(11-14)
🔇 Additional comments (4)
model/channel.go (1)
1006-1011: GetChannelsByStatus helper looks correctQuery is straightforward and matches existing GORM patterns in this file; returning full channel records (including keys) is appropriate for testing flows.
setting/operation_setting/monitor_setting.go (1)
10-21: Disabled-channel interval setting is wired consistentlyAdding
AutoTestDisabledChannelMinuteswith default0and populating it fromCHANNEL_TEST_DISABLED_FREQUENCY(only when>0) aligns with the controller logic where<=0disables automatic testing. This mirrors the existing pattern for enabled-channel testing and looks good.Also applies to: 36-41
web/src/pages/Setting/Operation/SettingsMonitoring.jsx (1)
35-44: UI wiring for disabled-channel interval matches backend semanticsThe new
monitor_setting.auto_test_disabled_channel_minutesfield, its default of0, and the<Form.InputNumber>control (min 0, “0 disables”) are consistent with the backend logic where<= 0disables auto tests. The implementation mirrors the existing “all channels” interval control and looks solid.Also applies to: 138-155
controller/channel-test.go (1)
575-607: Disabled-channel auto test loop is structured reasonablyThe new
testDisabledChannelsworker and its scheduler loop inAutomaticallyTestChannelsbehave as intended:
- Only channels with
ChannelStatusAutoDisabledare fetched and tested.AutoTestDisabledChannelMinutes <= 0cleanly disables the loop, matching the monitor setting semantics and UI text.- The additional goroutine mirrors the structure of the existing all-channel autoloop and keeps concerns separated.
Once the lock/flag behavior is fixed as noted in the other comment, this design should be robust.
Also applies to: 686-706
|
@coderabbitai Thanks for the review! I have fixed the potential deadlock issue by resetting the running flag in the error path. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 1042 @faithleysath Perfect! I can see the fix in lines 584-588 where you properly reset the if err != nil {
testDisabledChannelsLock.Lock()
testDisabledChannelsRunning = false
testDisabledChannelsLock.Unlock()
return err
}This ensures that if If you found this review helpful, would you consider giving us a shout-out on X? Thank you for using CodeRabbit! |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
controller/channel-test.go (2)
590-593: Consider using a named constant for the "impossible" threshold value.The magic number
10000000(10 seconds) is used to represent an effectively infinite threshold when automatic disabling is turned off. This could be more explicit.Apply this diff to improve clarity:
+const disableThresholdInfinite = int64(10000000) // 10 seconds - effectively infinite + func testDisabledChannels(notify bool) error { // ... var disableThreshold = int64(common.ChannelDisableThreshold * 1000) if disableThreshold == 0 { - disableThreshold = 10000000 // a impossible value + disableThreshold = disableThresholdInfinite } // ... }The same constant could be used in
testAllChannelsat line 630 to eliminate duplication.
672-712: Consider running the first test immediately when auto-testing is enabled.The nested loop structure is functionally correct, but the sleep at the start of each inner loop (lines 680, 705) means the first test is delayed by the full interval when auto-testing is first enabled. Users might expect tests to start immediately.
Consider restructuring to test immediately on the first iteration:
gopool.Go(func() { for { if !operation_setting.GetMonitorSetting().AutoTestChannelEnabled { time.Sleep(1 * time.Minute) continue } - for { - frequency := operation_setting.GetMonitorSetting().AutoTestChannelMinutes - time.Sleep(time.Duration(int(math.Round(frequency))) * time.Minute) - common.SysLog(fmt.Sprintf("automatically test channels with interval %f minutes", frequency)) - common.SysLog("automatically testing all channels") - _ = testAllChannels(false) - common.SysLog("automatically channel test finished") - if !operation_setting.GetMonitorSetting().AutoTestChannelEnabled { - break - } - } + // Run first test immediately + common.SysLog("automatically testing all channels") + _ = testAllChannels(false) + common.SysLog("automatically channel test finished") + + for operation_setting.GetMonitorSetting().AutoTestChannelEnabled { + frequency := operation_setting.GetMonitorSetting().AutoTestChannelMinutes + time.Sleep(time.Duration(int(math.Round(frequency))) * time.Minute) + common.SysLog(fmt.Sprintf("automatically test channels with interval %f minutes", frequency)) + common.SysLog("automatically testing all channels") + _ = testAllChannels(false) + common.SysLog("automatically channel test finished") + } } })Apply similar restructuring to the disabled channels goroutine (lines 692-712).
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
controller/channel-test.go(4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
controller/channel-test.go (9)
model/channel.go (2)
Channel(21-58)ChannelInfo(60-68)common/constants.go (4)
ChannelStatusEnabled(195-195)AutomaticDisableChannelEnabled(103-103)ChannelStatusAutoDisabled(197-197)ChannelDisableThreshold(102-102)service/channel.go (3)
ShouldDisableChannel(47-99)ShouldEnableChannel(101-112)EnableChannel(38-45)types/error.go (1)
NewOpenAIError(226-249)types/channel_error.go (1)
NewChannelError(12-21)common/gin.go (1)
GetContextKeyString(70-72)constant/context_key.go (1)
ContextKeyChannelKey(37-37)service/user_notify.go (1)
NotifyRootUser(17-23)setting/operation_setting/monitor_setting.go (1)
GetMonitorSetting(28-43)
🔇 Additional comments (4)
controller/channel-test.go (4)
536-570: LGTM! Good refactoring to extract per-channel test logic.The function correctly encapsulates the test logic for a single channel, including error handling, auto-ban/enable decisions, and response time tracking. The separation of concerns improves maintainability and enables reuse by both
testAllChannelsandtestDisabledChannels.
583-589: Good fix for the lock management issue.The lock is now properly reset when the database query fails, preventing the flag from getting stuck in the
truestate. This addresses the critical issue raised in the previous review.
623-626: Lock management fix applied correctly.The lock is now properly reset when the database query fails, matching the fix in
testDisabledChannelsand fully addressing the critical issue from the previous review.
641-641: Good refactoring to reuse testOneChannel.Delegating to
testOneChanneleliminates code duplication and ensures consistent behavior between testing all channels and testing disabled channels.
- Extract initial state to constants to reduce code duplication - Use state merging instead of overriding in useEffect to prevent data loss - Fix boolean type conversion from string options - Fix direct state mutation in SettingsLog
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (8)
web/src/pages/Setting/Operation/SettingsCreditLimit.jsx (1)
83-91: Props → state merge and boolean normalization are correctFiltering
props.optionsbyoriginInputskeys and coercing booleans viaString(value) === 'true'prevents stray options from leaking in and fixes the “boolean stored as string” issue for switches. FunctionalsetInputs/setInputsRowavoids stale closures. Only minor nit (optional): you could precomputeconst keys = Object.keys(originInputs)outside the loop, but it’s not necessary here.web/src/pages/Setting/Operation/SettingsGeneral.jsx (1)
132-162: Safe filtering ofprops.optionsand boolean coercionRestricting imported keys to
originInputsand normalizing boolean options withString(... ) === 'true'prevents invalid values from reaching switches and keeps type expectations clear. The functional merge intoinputs/inputsRowis also a good move to avoid race conditions on rapid updates.web/src/pages/Setting/Operation/SettingsSidebarModulesAdmin.jsx (1)
143-151: Merging loaded config overdefaultModulesis good; consider nested merge only if you want new sub‑modules on legacy configsParsing
SidebarModulesAdminand applying{ ...defaultModules, ...modules }ensures missing top-level sections fall back to defaults, which is valuable for forward-compat when adding new areas. Note that this is a shallow merge: if, in the future, you add new module flags under an existing section (e.g., a newconsole.*module), older stored configs will overwrite that section object and won’t inherit the new module’s default until the admin resets. That’s acceptable and matches previous behavior, but if you ever want new sub-modules to default-on even with legacy configs, a deep merge per section would be needed. Based on learnings, this still respects SidebarModulesAdmin as the single permission control point.web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx (1)
123-140: Merged load preserves defaults and maintains backward compatibilityLoading
HeaderNavModulesviaJSON.parse, converting legacy booleanpricingvalues into the new object shape, and then doingsetHeaderNavModules({ ...defaultModules, ...modules })ensures:
- Old configs still work (pricing boolean → object with
enabled+requireAuth:false).- New top-level modules can be added to
defaultModulesand will appear even if not present in the stored config.If you ever introduce more nested options under
pricing, consider a nested merge to let new fields inherit defaults on legacy configs, but that’s an optional future refinement.web/src/pages/Setting/Operation/SettingsMonitoring.jsx (2)
87-95: Filtered merge and boolean normalization are correctRestricting imported keys to
originInputsand coercing booleans usingString(props.options[key]) === 'true'keeps switch state consistent regardless of how the backend stores them. Functional merges intoinputs/inputsRoware also a nice improvement over direct replacement.
142-159: New “auto test disabled channels” interval field is wired correctly; consider guarding against NaNThe new
Form.InputNumberformonitor_setting.auto_test_disabled_channel_minutesis hooked up consistently with the existing auto-test interval: same suffix, help text, andparseInton change, withmin={0}matching the “0 disables auto testing” behavior.One small optional hardening:
parseInt(value)can produceNaNif the field is cleared. If that’s a concern, you could coerce invalid values back to 0 (or leave them unchanged), e.g.:- onChange={(value) => - setInputs({ - ...inputs, - 'monitor_setting.auto_test_disabled_channel_minutes': - parseInt(value), - }) - } + onChange={(value) => { + const minutes = parseInt(value, 10); + setInputs({ + ...inputs, + 'monitor_setting.auto_test_disabled_channel_minutes': + Number.isNaN(minutes) ? 0 : minutes, + }); + }}Not critical, but it would make the field more robust against transient empty input.
web/src/pages/Setting/Operation/SettingsLog.jsx (1)
186-195: Safe options merge with boolean normalization while preservinghistoryTimestampFiltering by
originInputskeys keeps only the intended options; boolean normalization viaString(... ) === 'true'fixes the “string vs boolean” issue forLogConsumeEnabled. Re-injectinginputs.historyTimestampintocurrentInputsbefore merging ensures the clean-history timestamp remains controlled by the user, not overwritten from options, which is the right call for this one-off action field.web/src/pages/Setting/Operation/SettingsSensitiveWords.jsx (1)
81-89: Options import and boolean coercion are correct for sensitive-word flagsLimiting imported keys to those in
originInputsand normalizing booleans viaString(props.options[key]) === 'true'ensures the two switches behave correctly even if the backend stored values as strings. Functional merges intoinputs/inputsRowalso reduce chances of subtle state bugs on refresh.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
web/src/pages/Setting/Operation/SettingsCreditLimit.jsx(2 hunks)web/src/pages/Setting/Operation/SettingsGeneral.jsx(4 hunks)web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx(2 hunks)web/src/pages/Setting/Operation/SettingsLog.jsx(2 hunks)web/src/pages/Setting/Operation/SettingsMonitoring.jsx(3 hunks)web/src/pages/Setting/Operation/SettingsSensitiveWords.jsx(2 hunks)web/src/pages/Setting/Operation/SettingsSidebarModulesAdmin.jsx(3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-02T16:17:53.708Z
Learnt from: x-Ai
Repo: QuantumNous/new-api PR: 1703
File: middleware/auth.go:264-293
Timestamp: 2025-09-02T16:17:53.708Z
Learning: The sidebar management system introduced in this codebase uses SidebarModulesAdmin configuration to control admin user permissions. Admin access to console.* modules should be governed by this configuration system, not bypassed with hardcoded allowlists. The system is designed for granular permission control where system administrators can configure which features admin users can access.
Applied to files:
web/src/pages/Setting/Operation/SettingsSidebarModulesAdmin.jsx
🧬 Code graph analysis (6)
web/src/pages/Setting/Operation/SettingsCreditLimit.jsx (5)
web/src/pages/Setting/Operation/SettingsGeneral.jsx (2)
originInputs(46-59)inputs(60-60)web/src/pages/Setting/Operation/SettingsLog.jsx (2)
originInputs(47-50)inputs(51-51)web/src/pages/Setting/Operation/SettingsMonitoring.jsx (2)
originInputs(35-44)inputs(45-45)web/src/pages/Setting/Operation/SettingsSensitiveWords.jsx (2)
originInputs(34-38)inputs(39-39)web/src/components/settings/OperationSetting.jsx (1)
inputs(32-74)
web/src/pages/Setting/Operation/SettingsMonitoring.jsx (6)
web/src/pages/Setting/Operation/SettingsCreditLimit.jsx (4)
originInputs(34-40)inputs(41-41)refForm(42-42)inputsRow(43-43)web/src/pages/Setting/Operation/SettingsGeneral.jsx (4)
originInputs(46-59)inputs(60-60)refForm(61-61)inputsRow(62-62)web/src/pages/Setting/Operation/SettingsLog.jsx (4)
originInputs(47-50)inputs(51-51)refForm(52-52)inputsRow(53-53)web/src/pages/Setting/Operation/SettingsSensitiveWords.jsx (4)
originInputs(34-38)inputs(39-39)refForm(40-40)inputsRow(41-41)web/src/components/table/channels/modals/EditChannelModal.jsx (2)
originInputs(130-166)inputs(171-171)web/src/components/settings/OperationSetting.jsx (1)
inputs(32-74)
web/src/pages/Setting/Operation/SettingsSensitiveWords.jsx (4)
web/src/pages/Setting/Operation/SettingsCreditLimit.jsx (4)
originInputs(34-40)inputs(41-41)refForm(42-42)inputsRow(43-43)web/src/pages/Setting/Operation/SettingsGeneral.jsx (4)
originInputs(46-59)inputs(60-60)refForm(61-61)inputsRow(62-62)web/src/pages/Setting/Operation/SettingsLog.jsx (4)
originInputs(47-50)inputs(51-51)refForm(52-52)inputsRow(53-53)web/src/pages/Setting/Operation/SettingsMonitoring.jsx (4)
originInputs(35-44)inputs(45-45)refForm(46-46)inputsRow(47-47)
web/src/pages/Setting/Operation/SettingsGeneral.jsx (4)
web/src/pages/Setting/Operation/SettingsCreditLimit.jsx (4)
originInputs(34-40)inputs(41-41)refForm(42-42)inputsRow(43-43)web/src/pages/Setting/Operation/SettingsLog.jsx (4)
originInputs(47-50)inputs(51-51)refForm(52-52)inputsRow(53-53)web/src/pages/Setting/Operation/SettingsMonitoring.jsx (4)
originInputs(35-44)inputs(45-45)refForm(46-46)inputsRow(47-47)web/src/pages/Setting/Operation/SettingsSensitiveWords.jsx (4)
originInputs(34-38)inputs(39-39)refForm(40-40)inputsRow(41-41)
web/src/pages/Setting/Operation/SettingsSidebarModulesAdmin.jsx (1)
web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx (1)
defaultModules(41-50)
web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx (2)
web/src/pages/Setting/Operation/SettingsSidebarModulesAdmin.jsx (1)
defaultModules(41-68)web/src/hooks/common/useHeaderBar.js (1)
headerNavModules(58-78)
🔇 Additional comments (8)
web/src/pages/Setting/Operation/SettingsCreditLimit.jsx (1)
34-43: Centralizing credit-limit defaults viaoriginInputslooks goodUsing
originInputsas the single source of truth and initializing bothinputsandinputsRowfrom it keeps defaults consistent and aligns with other settings components. No functional issues seen here.web/src/pages/Setting/Operation/SettingsGeneral.jsx (1)
46-63:originInputs-based initialization improves consistencyDefining
originInputsonce and feeding bothinputsandinputsRowfrom it unifies defaults (including custom currency fields) and matches the broader pattern in Settings*.jsx. No correctness issues spotted.web/src/pages/Setting/Operation/SettingsSidebarModulesAdmin.jsx (2)
41-72: CentralizeddefaultModulesfor admin sidebar is a solid improvementDefining
defaultModulesonce and initializingsidebarModulesAdminfrom it simplifies understanding of the admin-visible surface and aligns with how header nav modules are handled elsewhere. The state update handlers correctly create new objects for both sections and individual modules, so you’re not mutating the defaults.
102-106: Reset behavior correctly uses shared defaults
resetSidebarModulesnow reusesdefaultModulesinstead of re-declaring the structure, which reduces drift as the configuration evolves. Behavior stays intuitive: admins get a predictable, documented baseline config when resetting.web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx (1)
41-53: Header navdefaultModulesand shared state init/reset look correctCentralizing the header nav defaults (including the new
pricing.requireAuthflag) and initializingheaderNavModulesfrom that constant keeps the config surface clear and makes “reset to default” behavior predictable. No issues with the way booleans vs objects are handled forpricing.web/src/pages/Setting/Operation/SettingsMonitoring.jsx (1)
35-47: Monitoring defaults viaoriginInputsare coherent, including the new disabled‑channel intervalUsing
originInputsfor bothinputsandinputsRowkeeps all monitoring defaults (thresholds, booleans, and both auto-test intervals) in one place and matches the other settings pages’ pattern. The newmonitor_setting.auto_test_disabled_channel_minutes: 0default aligns with the “0 = don’t auto-test” semantics in the UI.web/src/pages/Setting/Operation/SettingsLog.jsx (1)
47-53: Log settings defaults are now well-encapsulated
originInputscleanly expresses the two log-related fields and their defaults, and initializing bothinputsandinputsRowfrom it matches the newer patterns in other settings components. No behavior regressions detected.web/src/pages/Setting/Operation/SettingsSensitiveWords.jsx (1)
34-41: Centralized sensitive-words defaults viaoriginInputsare appropriateGrouping
CheckSensitiveEnabled,CheckSensitiveOnPromptEnabled, andSensitiveWordsintooriginInputsand initializing bothinputsandinputsRowfrom it simplifies reasoning about defaults and aligns with the rest of the settings suite.
Summary
This PR introduces a mechanism to automatically test and reactivate channels that were previously disabled (auto-disabled status). This allows for automatic recovery of channels without manual intervention.
Key Changes
Refactored Testing Logic:
testOneChannelto be reused by both active channel testing and disabled channel testing.Independent Scheduling:
testDisabledChannelsto specifically target channels withAutoDisabledstatus.AutomaticallyTestChannelsto handle the scheduling for disabled channels independently from the main channel testing loop.Configuration & UI:
AutoTestDisabledChannelMinutessetting to control the test frequency.CHANNEL_TEST_DISABLED_FREQUENCY.Impact
Summary by CodeRabbit
New Features
Improvements
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.