mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-23 07:50:15 +00:00
[refactor] Improve updates/notifications domain organization (#5590)
* [refactor] Move update-related functionality to platform/updates domain Reorganizes release management, version compatibility, and notification functionality following Domain-Driven Design principles, mirroring VSCode's architecture pattern. - Move releaseService.ts to platform/updates/common/ - Move releaseStore.ts to platform/updates/common/ - Move versionCompatibilityStore.ts to platform/updates/common/ - Move useFrontendVersionMismatchWarning.ts to platform/updates/common/ - Move toastStore.ts to platform/updates/common/ - Move ReleaseNotificationToast.vue to platform/updates/components/ - Move WhatsNewPopup.vue to platform/updates/components/ - Update 25+ import paths across codebase and tests This creates a cohesive "updates" domain containing all functionality related to software updates, version checking, release notifications, and user communication about application state changes. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix imports --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
119
src/platform/updates/common/releaseService.ts
Normal file
119
src/platform/updates/common/releaseService.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import axios, { AxiosError, AxiosResponse } from 'axios'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { COMFY_API_BASE_URL } from '@/config/comfyApi'
|
||||
import type { components, operations } from '@/types/comfyRegistryTypes'
|
||||
import { isAbortError } from '@/utils/typeGuardUtil'
|
||||
|
||||
const releaseApiClient = axios.create({
|
||||
baseURL: COMFY_API_BASE_URL,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
|
||||
// Use generated types from OpenAPI spec
|
||||
export type ReleaseNote = components['schemas']['ReleaseNote']
|
||||
type GetReleasesParams = operations['getReleaseNotes']['parameters']['query']
|
||||
|
||||
// Use generated error response type
|
||||
type ErrorResponse = components['schemas']['ErrorResponse']
|
||||
|
||||
// Release service for fetching release notes
|
||||
export const useReleaseService = () => {
|
||||
const isLoading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
// No transformation needed - API response matches the generated type
|
||||
|
||||
// Handle API errors with context
|
||||
const handleApiError = (
|
||||
err: unknown,
|
||||
context: string,
|
||||
routeSpecificErrors?: Record<number, string>
|
||||
): string => {
|
||||
if (!axios.isAxiosError(err))
|
||||
return err instanceof Error
|
||||
? `${context}: ${err.message}`
|
||||
: `${context}: Unknown error occurred`
|
||||
|
||||
const axiosError = err as AxiosError<ErrorResponse>
|
||||
|
||||
if (axiosError.response) {
|
||||
const { status, data } = axiosError.response
|
||||
|
||||
if (routeSpecificErrors && routeSpecificErrors[status])
|
||||
return routeSpecificErrors[status]
|
||||
|
||||
switch (status) {
|
||||
case 400:
|
||||
return `Bad request: ${data?.message || 'Invalid input'}`
|
||||
case 401:
|
||||
return 'Unauthorized: Authentication required'
|
||||
case 403:
|
||||
return `Forbidden: ${data?.message || 'Access denied'}`
|
||||
case 404:
|
||||
return `Not found: ${data?.message || 'Resource not found'}`
|
||||
case 500:
|
||||
return `Server error: ${data?.message || 'Internal server error'}`
|
||||
default:
|
||||
return `${context}: ${data?.message || axiosError.message}`
|
||||
}
|
||||
}
|
||||
|
||||
return `${context}: ${axiosError.message}`
|
||||
}
|
||||
|
||||
// Execute API request with error handling
|
||||
const executeApiRequest = async <T>(
|
||||
apiCall: () => Promise<AxiosResponse<T>>,
|
||||
errorContext: string,
|
||||
routeSpecificErrors?: Record<number, string>
|
||||
): Promise<T | null> => {
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const response = await apiCall()
|
||||
return response.data
|
||||
} catch (err) {
|
||||
// Don't treat cancellations as errors
|
||||
if (isAbortError(err)) return null
|
||||
|
||||
error.value = handleApiError(err, errorContext, routeSpecificErrors)
|
||||
return null
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch release notes from API
|
||||
const getReleases = async (
|
||||
params: GetReleasesParams,
|
||||
signal?: AbortSignal
|
||||
): Promise<ReleaseNote[] | null> => {
|
||||
const endpoint = '/releases'
|
||||
const errorContext = 'Failed to get releases'
|
||||
const routeSpecificErrors = {
|
||||
400: 'Invalid project or version parameter'
|
||||
}
|
||||
|
||||
const apiResponse = await executeApiRequest(
|
||||
() =>
|
||||
releaseApiClient.get<ReleaseNote[]>(endpoint, {
|
||||
params,
|
||||
signal
|
||||
}),
|
||||
errorContext,
|
||||
routeSpecificErrors
|
||||
)
|
||||
|
||||
return apiResponse
|
||||
}
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
error,
|
||||
getReleases
|
||||
}
|
||||
}
|
||||
289
src/platform/updates/common/releaseStore.ts
Normal file
289
src/platform/updates/common/releaseStore.ts
Normal file
@@ -0,0 +1,289 @@
|
||||
import { until } from '@vueuse/core'
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { useSystemStatsStore } from '@/stores/systemStatsStore'
|
||||
import { isElectron } from '@/utils/envUtil'
|
||||
import { compareVersions, stringToLocale } from '@/utils/formatUtil'
|
||||
|
||||
import { type ReleaseNote, useReleaseService } from './releaseService'
|
||||
|
||||
// Store for managing release notes
|
||||
export const useReleaseStore = defineStore('release', () => {
|
||||
// State
|
||||
const releases = ref<ReleaseNote[]>([])
|
||||
const isLoading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
// Services
|
||||
const releaseService = useReleaseService()
|
||||
const systemStatsStore = useSystemStatsStore()
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
// Current ComfyUI version
|
||||
const currentComfyUIVersion = computed(
|
||||
() => systemStatsStore?.systemStats?.system?.comfyui_version ?? ''
|
||||
)
|
||||
|
||||
// Release data from settings
|
||||
const locale = computed(() => settingStore.get('Comfy.Locale'))
|
||||
const releaseVersion = computed(() =>
|
||||
settingStore.get('Comfy.Release.Version')
|
||||
)
|
||||
const releaseStatus = computed(() => settingStore.get('Comfy.Release.Status'))
|
||||
const releaseTimestamp = computed(() =>
|
||||
settingStore.get('Comfy.Release.Timestamp')
|
||||
)
|
||||
const showVersionUpdates = computed(() =>
|
||||
settingStore.get('Comfy.Notification.ShowVersionUpdates')
|
||||
)
|
||||
|
||||
// Most recent release
|
||||
const recentRelease = computed(() => {
|
||||
return releases.value[0] ?? null
|
||||
})
|
||||
|
||||
// 3 most recent releases
|
||||
const recentReleases = computed(() => {
|
||||
return releases.value.slice(0, 3)
|
||||
})
|
||||
|
||||
// Helper constants
|
||||
const THREE_DAYS_MS = 3 * 24 * 60 * 60 * 1000 // 3 days
|
||||
|
||||
// New version available?
|
||||
const isNewVersionAvailable = computed(
|
||||
() =>
|
||||
!!recentRelease.value &&
|
||||
compareVersions(
|
||||
recentRelease.value.version,
|
||||
currentComfyUIVersion.value
|
||||
) > 0
|
||||
)
|
||||
|
||||
const isLatestVersion = computed(
|
||||
() =>
|
||||
!!recentRelease.value &&
|
||||
!compareVersions(recentRelease.value.version, currentComfyUIVersion.value)
|
||||
)
|
||||
|
||||
const hasMediumOrHighAttention = computed(() =>
|
||||
recentReleases.value
|
||||
.slice(0, -1)
|
||||
.some(
|
||||
(release) =>
|
||||
release.attention === 'medium' || release.attention === 'high'
|
||||
)
|
||||
)
|
||||
|
||||
// Show toast if needed
|
||||
const shouldShowToast = computed(() => {
|
||||
// Only show on desktop version
|
||||
if (!isElectron()) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Skip if notifications are disabled
|
||||
if (!showVersionUpdates.value) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!isNewVersionAvailable.value) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Skip if low attention
|
||||
if (!hasMediumOrHighAttention.value) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Skip if user already skipped or changelog seen
|
||||
if (
|
||||
releaseVersion.value === recentRelease.value?.version &&
|
||||
['skipped', 'changelog seen'].includes(releaseStatus.value)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
// Show red-dot indicator
|
||||
const shouldShowRedDot = computed(() => {
|
||||
// Only show on desktop version
|
||||
if (!isElectron()) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Skip if notifications are disabled
|
||||
if (!showVersionUpdates.value) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Already latest → no dot
|
||||
if (!isNewVersionAvailable.value) {
|
||||
return false
|
||||
}
|
||||
|
||||
const { version } = recentRelease.value
|
||||
|
||||
// Changelog seen → clear dot
|
||||
if (
|
||||
releaseVersion.value === version &&
|
||||
releaseStatus.value === 'changelog seen'
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Attention medium / high (levels 2 & 3)
|
||||
if (hasMediumOrHighAttention.value) {
|
||||
// Persist until changelog is opened
|
||||
return true
|
||||
}
|
||||
|
||||
// Attention low (level 1) and skipped → keep up to 3 d
|
||||
if (
|
||||
releaseVersion.value === version &&
|
||||
releaseStatus.value === 'skipped' &&
|
||||
releaseTimestamp.value &&
|
||||
Date.now() - releaseTimestamp.value >= THREE_DAYS_MS
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Not skipped → show
|
||||
return true
|
||||
})
|
||||
|
||||
// Show "What's New" popup
|
||||
const shouldShowPopup = computed(() => {
|
||||
// Only show on desktop version
|
||||
if (!isElectron()) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Skip if notifications are disabled
|
||||
if (!showVersionUpdates.value) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!isLatestVersion.value) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Hide if already seen
|
||||
if (
|
||||
releaseVersion.value === recentRelease.value.version &&
|
||||
releaseStatus.value === "what's new seen"
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
// Action handlers for user interactions
|
||||
async function handleSkipRelease(version: string): Promise<void> {
|
||||
if (
|
||||
version !== recentRelease.value?.version ||
|
||||
releaseStatus.value === 'changelog seen'
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
await settingStore.set('Comfy.Release.Version', version)
|
||||
await settingStore.set('Comfy.Release.Status', 'skipped')
|
||||
await settingStore.set('Comfy.Release.Timestamp', Date.now())
|
||||
}
|
||||
|
||||
async function handleShowChangelog(version: string): Promise<void> {
|
||||
if (version !== recentRelease.value?.version) {
|
||||
return
|
||||
}
|
||||
|
||||
await settingStore.set('Comfy.Release.Version', version)
|
||||
await settingStore.set('Comfy.Release.Status', 'changelog seen')
|
||||
await settingStore.set('Comfy.Release.Timestamp', Date.now())
|
||||
}
|
||||
|
||||
async function handleWhatsNewSeen(version: string): Promise<void> {
|
||||
if (version !== recentRelease.value?.version) {
|
||||
return
|
||||
}
|
||||
|
||||
await settingStore.set('Comfy.Release.Version', version)
|
||||
await settingStore.set('Comfy.Release.Status', "what's new seen")
|
||||
await settingStore.set('Comfy.Release.Timestamp', Date.now())
|
||||
}
|
||||
|
||||
// Fetch releases from API
|
||||
async function fetchReleases(): Promise<void> {
|
||||
if (isLoading.value) {
|
||||
return
|
||||
}
|
||||
|
||||
// Skip fetching if notifications are disabled
|
||||
if (!showVersionUpdates.value) {
|
||||
return
|
||||
}
|
||||
|
||||
// Skip fetching if API nodes are disabled via argv
|
||||
if (
|
||||
systemStatsStore.systemStats?.system?.argv?.includes(
|
||||
'--disable-api-nodes'
|
||||
)
|
||||
) {
|
||||
return
|
||||
}
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
// Ensure system stats are loaded
|
||||
if (!systemStatsStore.systemStats) {
|
||||
await until(systemStatsStore.isInitialized)
|
||||
}
|
||||
|
||||
const fetchedReleases = await releaseService.getReleases({
|
||||
project: 'comfyui',
|
||||
current_version: currentComfyUIVersion.value,
|
||||
form_factor: systemStatsStore.getFormFactor(),
|
||||
locale: stringToLocale(locale.value)
|
||||
})
|
||||
|
||||
if (fetchedReleases !== null) {
|
||||
releases.value = fetchedReleases
|
||||
} else if (releaseService.error.value) {
|
||||
error.value = releaseService.error.value
|
||||
}
|
||||
} catch (err) {
|
||||
error.value =
|
||||
err instanceof Error ? err.message : 'Unknown error occurred'
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize store
|
||||
async function initialize(): Promise<void> {
|
||||
await fetchReleases()
|
||||
}
|
||||
|
||||
return {
|
||||
releases,
|
||||
isLoading,
|
||||
error,
|
||||
recentRelease,
|
||||
recentReleases,
|
||||
shouldShowToast,
|
||||
shouldShowRedDot,
|
||||
shouldShowPopup,
|
||||
shouldShowUpdateButton: isNewVersionAvailable,
|
||||
handleSkipRelease,
|
||||
handleShowChangelog,
|
||||
handleWhatsNewSeen,
|
||||
fetchReleases,
|
||||
initialize
|
||||
}
|
||||
})
|
||||
39
src/platform/updates/common/toastStore.ts
Normal file
39
src/platform/updates/common/toastStore.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
// Within Vue component context, you can directly call useToast().add()
|
||||
// instead of going through the store.
|
||||
// The store is useful when you need to call it from outside the Vue component context.
|
||||
import { defineStore } from 'pinia'
|
||||
import type { ToastMessageOptions } from 'primevue/toast'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export const useToastStore = defineStore('toast', () => {
|
||||
const messagesToAdd = ref<ToastMessageOptions[]>([])
|
||||
const messagesToRemove = ref<ToastMessageOptions[]>([])
|
||||
const removeAllRequested = ref(false)
|
||||
|
||||
function add(message: ToastMessageOptions) {
|
||||
messagesToAdd.value = [...messagesToAdd.value, message]
|
||||
}
|
||||
|
||||
function remove(message: ToastMessageOptions) {
|
||||
messagesToRemove.value = [...messagesToRemove.value, message]
|
||||
}
|
||||
|
||||
function removeAll() {
|
||||
removeAllRequested.value = true
|
||||
}
|
||||
|
||||
function addAlert(message: string) {
|
||||
add({ severity: 'warn', summary: 'Alert', detail: message })
|
||||
}
|
||||
|
||||
return {
|
||||
messagesToAdd,
|
||||
messagesToRemove,
|
||||
removeAllRequested,
|
||||
|
||||
add,
|
||||
remove,
|
||||
removeAll,
|
||||
addAlert
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,94 @@
|
||||
import { whenever } from '@vueuse/core'
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { useToastStore } from './toastStore'
|
||||
import { useVersionCompatibilityStore } from './versionCompatibilityStore'
|
||||
|
||||
interface UseFrontendVersionMismatchWarningOptions {
|
||||
immediate?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Composable for handling frontend version mismatch warnings.
|
||||
*
|
||||
* Displays toast notifications when the frontend version is incompatible with the backend,
|
||||
* either because the frontend is outdated or newer than the backend expects.
|
||||
* Automatically dismisses warnings when shown and persists dismissal state for 7 days.
|
||||
*
|
||||
* @param options - Configuration options
|
||||
* @param options.immediate - If true, automatically shows warning when version mismatch is detected
|
||||
* @returns Object with methods and computed properties for managing version warnings
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Show warning immediately when mismatch detected
|
||||
* const { showWarning, shouldShowWarning } = useFrontendVersionMismatchWarning({ immediate: true })
|
||||
*
|
||||
* // Manual control
|
||||
* const { showWarning } = useFrontendVersionMismatchWarning()
|
||||
* showWarning() // Call when needed
|
||||
* ```
|
||||
*/
|
||||
export function useFrontendVersionMismatchWarning(
|
||||
options: UseFrontendVersionMismatchWarningOptions = {}
|
||||
) {
|
||||
const { immediate = false } = options
|
||||
const { t } = useI18n()
|
||||
const toastStore = useToastStore()
|
||||
const versionCompatibilityStore = useVersionCompatibilityStore()
|
||||
|
||||
// Track if we've already shown the warning
|
||||
let hasShownWarning = false
|
||||
|
||||
const showWarning = () => {
|
||||
// Prevent showing the warning multiple times
|
||||
if (hasShownWarning) return
|
||||
|
||||
const message = versionCompatibilityStore.warningMessage
|
||||
if (!message) return
|
||||
|
||||
const detailMessage = t('g.frontendOutdated', {
|
||||
frontendVersion: message.frontendVersion,
|
||||
requiredVersion: message.requiredVersion
|
||||
})
|
||||
|
||||
const fullMessage = t('g.versionMismatchWarningMessage', {
|
||||
warning: t('g.versionMismatchWarning'),
|
||||
detail: detailMessage
|
||||
})
|
||||
|
||||
toastStore.addAlert(fullMessage)
|
||||
hasShownWarning = true
|
||||
|
||||
// Automatically dismiss the warning so it won't show again for 7 days
|
||||
versionCompatibilityStore.dismissWarning()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// Only set up the watcher if immediate is true
|
||||
if (immediate) {
|
||||
whenever(
|
||||
() => versionCompatibilityStore.shouldShowWarning,
|
||||
() => {
|
||||
showWarning()
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
once: true
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
showWarning,
|
||||
shouldShowWarning: computed(
|
||||
() => versionCompatibilityStore.shouldShowWarning
|
||||
),
|
||||
dismissWarning: versionCompatibilityStore.dismissWarning,
|
||||
hasVersionMismatch: computed(
|
||||
() => versionCompatibilityStore.hasVersionMismatch
|
||||
)
|
||||
}
|
||||
}
|
||||
138
src/platform/updates/common/versionCompatibilityStore.ts
Normal file
138
src/platform/updates/common/versionCompatibilityStore.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
import { until, useStorage } from '@vueuse/core'
|
||||
import { defineStore } from 'pinia'
|
||||
import * as semver from 'semver'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import config from '@/config'
|
||||
import { useSystemStatsStore } from '@/stores/systemStatsStore'
|
||||
|
||||
const DISMISSAL_DURATION_MS = 7 * 24 * 60 * 60 * 1000 // 7 days
|
||||
|
||||
export const useVersionCompatibilityStore = defineStore(
|
||||
'versionCompatibility',
|
||||
() => {
|
||||
const systemStatsStore = useSystemStatsStore()
|
||||
|
||||
const frontendVersion = computed(() => config.app_version)
|
||||
const backendVersion = computed(
|
||||
() => systemStatsStore.systemStats?.system?.comfyui_version ?? ''
|
||||
)
|
||||
const requiredFrontendVersion = computed(
|
||||
() =>
|
||||
systemStatsStore.systemStats?.system?.required_frontend_version ?? ''
|
||||
)
|
||||
|
||||
const isFrontendOutdated = computed(() => {
|
||||
if (
|
||||
!frontendVersion.value ||
|
||||
!requiredFrontendVersion.value ||
|
||||
!semver.valid(frontendVersion.value) ||
|
||||
!semver.valid(requiredFrontendVersion.value)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
// Returns true if required version is greater than frontend version
|
||||
return semver.gt(requiredFrontendVersion.value, frontendVersion.value)
|
||||
})
|
||||
|
||||
const isFrontendNewer = computed(() => {
|
||||
// We don't warn about frontend being newer than backend
|
||||
// Only warn when frontend is outdated (behind required version)
|
||||
return false
|
||||
})
|
||||
|
||||
const hasVersionMismatch = computed(() => {
|
||||
return isFrontendOutdated.value
|
||||
})
|
||||
|
||||
const versionKey = computed(() => {
|
||||
if (
|
||||
!frontendVersion.value ||
|
||||
!backendVersion.value ||
|
||||
!requiredFrontendVersion.value
|
||||
) {
|
||||
return null
|
||||
}
|
||||
return `${frontendVersion.value}-${backendVersion.value}-${requiredFrontendVersion.value}`
|
||||
})
|
||||
|
||||
// Use reactive storage for dismissals - creates a reactive ref that syncs with localStorage
|
||||
// All version mismatch dismissals are stored in a single object for clean localStorage organization
|
||||
const dismissalStorage = useStorage(
|
||||
'comfy.versionMismatch.dismissals',
|
||||
{} as Record<string, number>,
|
||||
localStorage,
|
||||
{
|
||||
serializer: {
|
||||
read: (value: string) => {
|
||||
try {
|
||||
return JSON.parse(value)
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
write: (value: Record<string, number>) => JSON.stringify(value)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const isDismissed = computed(() => {
|
||||
if (!versionKey.value) return false
|
||||
|
||||
const dismissedUntil = dismissalStorage.value[versionKey.value]
|
||||
if (!dismissedUntil) return false
|
||||
|
||||
// Check if dismissal has expired
|
||||
return Date.now() < dismissedUntil
|
||||
})
|
||||
|
||||
const shouldShowWarning = computed(() => {
|
||||
return hasVersionMismatch.value && !isDismissed.value
|
||||
})
|
||||
|
||||
const warningMessage = computed(() => {
|
||||
if (isFrontendOutdated.value) {
|
||||
return {
|
||||
type: 'outdated' as const,
|
||||
frontendVersion: frontendVersion.value,
|
||||
requiredVersion: requiredFrontendVersion.value
|
||||
}
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
async function checkVersionCompatibility() {
|
||||
if (!systemStatsStore.systemStats) {
|
||||
await until(systemStatsStore.isInitialized)
|
||||
}
|
||||
}
|
||||
|
||||
function dismissWarning() {
|
||||
if (!versionKey.value) return
|
||||
|
||||
const dismissUntil = Date.now() + DISMISSAL_DURATION_MS
|
||||
dismissalStorage.value = {
|
||||
...dismissalStorage.value,
|
||||
[versionKey.value]: dismissUntil
|
||||
}
|
||||
}
|
||||
|
||||
async function initialize() {
|
||||
await checkVersionCompatibility()
|
||||
}
|
||||
|
||||
return {
|
||||
frontendVersion,
|
||||
backendVersion,
|
||||
requiredFrontendVersion,
|
||||
hasVersionMismatch,
|
||||
shouldShowWarning,
|
||||
warningMessage,
|
||||
isFrontendOutdated,
|
||||
isFrontendNewer,
|
||||
checkVersionCompatibility,
|
||||
dismissWarning,
|
||||
initialize
|
||||
}
|
||||
}
|
||||
)
|
||||
Reference in New Issue
Block a user