fix: separate auto-update and dev tracking settings for clarity by jxxghp

This commit is contained in:
jxxghp
2026-09-07 13:37:32 +08:00
parent b1922f33e6
commit 1fb09274ca
8 changed files with 90 additions and 10 deletions
+4
View File
@@ -2651,6 +2651,10 @@ export interface SystemUpdateItemStatus {
/** 后端主程序与站点资源后台更新状态机快照。 */
export interface SystemUpdateStatus {
/** 当前自动检查开关;由后端实时返回,避免旧缓存继续触发提醒。 */
auto_update?: boolean
/** 站点资源单独控制自动检查和提醒。 */
auto_update_resource?: boolean
state: SystemUpdateState
current_version: string
version?: string | null
@@ -70,6 +70,9 @@ const updateItems = computed<SystemUpdateItemStatus[]>(() => {
const visibleItems = computed(() =>
updateItems.value.filter(item => {
// 关闭自动检查后隐藏版本提醒,手动发起的下载、安装和失败反馈仍可见。
const enabled = item.type === 'resources' ? status.value?.auto_update_resource : status.value?.auto_update
if (['available', 'ready'].includes(item.state) && enabled !== true) return false
if (!['available', 'downloading', 'ready', 'installing', 'failed'].includes(item.state)) return false
return !['available', 'ready'].includes(item.state) || !isCurrentVersionSuppressed(item)
}),
@@ -57,6 +57,8 @@ vi.mock('vue-i18n', async importOriginal => ({
}))
const availableStatus: SystemUpdateStatus = {
auto_update: true,
auto_update_resource: true,
state: 'available',
current_version: 'v3.0.0',
version: 'v3.1.0',
@@ -77,6 +79,43 @@ describe('SystemUpdatePrompt', () => {
mocks.footerDockHeight!.value = null
})
it.each(['available', 'ready'] as const)(
'hides cached %s reminders when automatic checks are disabled',
async state => {
mocks.updateStatus!.value = { ...availableStatus, state, auto_update: false }
await renderWithProviders(SystemUpdatePrompt, { props: { enabled: true } })
expect(document.querySelector('.system-update-prompt')).not.toBeInTheDocument()
mocks.updateStatus!.value = { ...availableStatus, state, auto_update: true }
await waitFor(() => expect(document.querySelector('.system-update-prompt')).toBeInTheDocument())
mocks.updateStatus!.value = { ...availableStatus, state, auto_update: false }
await waitFor(() => expect(document.querySelector('.system-update-prompt')).not.toBeInTheDocument())
},
)
it('keeps manually started download progress visible with automatic checks disabled', async () => {
mocks.updateStatus!.value = { ...availableStatus, state: 'downloading', auto_update: false, progress: 25 }
await renderWithProviders(SystemUpdatePrompt, { props: { enabled: true } })
expect(screen.getByText('25%')).toBeInTheDocument()
})
it('shows only resource reminders when only automatic resource updates are enabled', async () => {
mocks.updateStatus!.value = {
...availableStatus,
auto_update: false,
auto_update_resource: true,
updates: [
{ ...availableStatus, type: 'application' },
{ ...availableStatus, type: 'resources', auth_version: '3.1.0' },
],
}
await renderWithProviders(SystemUpdatePrompt, { props: { enabled: true } })
expect(screen.queryByText('systemUpdate.applicationAvailableTitle')).not.toBeInTheDocument()
expect(screen.getByText('systemUpdate.resourcesAvailableTitle')).toBeInTheDocument()
mocks.updateStatus!.value = { ...mocks.updateStatus!.value, auto_update_resource: false }
await waitFor(() => expect(document.querySelector('.system-update-prompt')).not.toBeInTheDocument())
})
it('asks an administrator to start the background download', async () => {
mocks.post.mockResolvedValue({
...availableStatus,
+3
View File
@@ -2989,6 +2989,9 @@ export default {
imageProxyAllowedPrivateRangeAdd: 'Add CIDR, e.g.: 198.18.0.0/15',
proxyHost: 'Proxy Server',
proxyHostHint: 'Set proxy server address, support: http(s), socks5, socks5h, etc.',
moviePilotAutoUpdate: 'Automatically check for updates',
moviePilotAutoUpdateHint:
'Check for application updates every 6 hours and show reminders; confirm downloads and installation manually',
moviePilotDevUpdate: 'Track Dev builds',
moviePilotDevUpdateHint: 'Update to the latest code on the current v3 development branch at every restart',
autoUpdateResource: 'Auto Update Resource',
+2
View File
@@ -2924,6 +2924,8 @@ export default {
imageProxyAllowedPrivateRangeAdd: '添加 CIDR,如:198.18.0.0/15',
proxyHost: '代理服务器',
proxyHostHint: '设置代理服务器地址,支持:http(s)、socks5、socks5h 等协议',
moviePilotAutoUpdate: '自动检查版本更新',
moviePilotAutoUpdateHint: '每 6 小时检查主程序版本更新并提示,下载和安装需手动确认',
moviePilotDevUpdate: '跟踪 Dev 开发版',
moviePilotDevUpdateHint: '每次重启时更新到当前 v3 开发分支的最新代码',
autoUpdateResource: '自动更新站点资源',
+2
View File
@@ -2897,6 +2897,8 @@ export default {
imageProxyAllowedPrivateRangeAdd: '添加 CIDR,如:198.18.0.0/15',
proxyHost: '代理服務器',
proxyHostHint: '設置代理服務器地址,支持:http(s)、socks5、socks5h 等協議',
moviePilotAutoUpdate: '自動檢查版本更新',
moviePilotAutoUpdateHint: '每 6 小時檢查主程式版本更新並提示,下載和安裝需手動確認',
moviePilotDevUpdate: '追蹤 Dev 開發版',
moviePilotDevUpdateHint: '每次重新啟動時更新到目前 v3 開發分支的最新程式碼',
autoUpdateResource: '自動更新站點資源',
+10 -8
View File
@@ -99,6 +99,7 @@ const SystemSettings = ref<any>({
DB_WAL_ENABLE: false,
AUTO_UPDATE_RESOURCE: true,
MOVIEPILOT_AUTO_UPDATE: false,
MOVIEPILOT_UPDATE_DEV: false,
DATA_CLEANUP_ENABLE: false,
DATA_CLEANUP_MESSAGE_DAYS: 90,
DATA_CLEANUP_DOWNLOAD_HISTORY_DAYS: 180,
@@ -1117,13 +1118,6 @@ function onMediaServerChange(mediaserver: MediaServerConf, name: string) {
if (index !== -1) mediaServers.value[index] = mediaserver
}
const moviePilotDevUpdate = computed({
get: () => SystemSettings.value.Advanced.MOVIEPILOT_AUTO_UPDATE === 'dev',
set: enabled => {
SystemSettings.value.Advanced.MOVIEPILOT_AUTO_UPDATE = enabled ? 'dev' : 'false'
},
})
// Fanart
const fanartLanguageSelection = computed({
get: () => {
@@ -2131,7 +2125,15 @@ watch(currentLlmSnapshotKey, (snapshotKey, previousSnapshotKey) => {
</VCol>
<VCol cols="12" md="6">
<VSwitch
v-model="moviePilotDevUpdate"
v-model="SystemSettings.Advanced.MOVIEPILOT_AUTO_UPDATE"
:label="t('setting.system.moviePilotAutoUpdate')"
:hint="t('setting.system.moviePilotAutoUpdateHint')"
persistent-hint
/>
</VCol>
<VCol cols="12" md="6">
<VSwitch
v-model="SystemSettings.Advanced.MOVIEPILOT_UPDATE_DEV"
:label="t('setting.system.moviePilotDevUpdate')"
:hint="t('setting.system.moviePilotDevUpdateHint')"
persistent-hint
@@ -1006,7 +1006,30 @@ describe('AccountSettingSystem', () => {
expect(mocks.toastSuccess).toHaveBeenCalledWith('高级设置保存成功')
})
it('round-trips all advanced system switches and enables only the Dev update mode', async () => {
it.each([
[false, false],
[false, true],
[true, false],
[true, true],
])('loads and saves independent update switches (%s, %s)', async (autoUpdate, updateDev) => {
systemEnv.MOVIEPILOT_AUTO_UPDATE = autoUpdate
systemEnv.MOVIEPILOT_UPDATE_DEV = updateDev
await renderSettings()
const dialog = await openAdvancedTab('系统')
const autoSwitch = dialog.getByLabelText('自动检查版本更新')
const devSwitch = dialog.getByLabelText('跟踪 Dev 开发版')
expect((autoSwitch as HTMLInputElement).checked).toBe(autoUpdate)
expect((devSwitch as HTMLInputElement).checked).toBe(updateDev)
await fireEvent.click(autoSwitch)
expect((devSwitch as HTMLInputElement).checked).toBe(updateDev)
await fireEvent.click(dialog.getByRole('button', { name: '保存' }))
await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument())
expect(findPost('system/env')?.[1]).toEqual(
expect.objectContaining({ MOVIEPILOT_AUTO_UPDATE: !autoUpdate, MOVIEPILOT_UPDATE_DEV: updateDev }),
)
})
it('round-trips all advanced system switches with independent boolean update settings', async () => {
await renderSettings()
const dialog = await openAdvancedTab('系统')
for (const label of [
@@ -1018,6 +1041,7 @@ describe('AccountSettingSystem', () => {
'分享工作流数据',
'大内存模式',
'数据库WAL模式',
'自动检查版本更新',
'跟踪 Dev 开发版',
'自动更新站点资源',
]) {
@@ -1033,7 +1057,8 @@ describe('AccountSettingSystem', () => {
BIG_MEMORY_MODE: true,
DB_WAL_ENABLE: true,
GLOBAL_IMAGE_CACHE: true,
MOVIEPILOT_AUTO_UPDATE: 'dev',
MOVIEPILOT_AUTO_UPDATE: true,
MOVIEPILOT_UPDATE_DEV: true,
PLUGIN_STATISTIC_SHARE: false,
SUBSCRIBE_STATISTIC_SHARE: false,
USAGE_STATISTIC_SHARE: false,