refactor: downgrade and update improvements with store refactors

This commit is contained in:
Zack Spear
2023-11-06 13:20:06 -08:00
parent 1d67fa4c56
commit 799b77f09b
9 changed files with 83 additions and 47 deletions

View File

@@ -37,12 +37,20 @@ withDefaults(defineProps<Props>(), {
const updateOsActionsStore = useUpdateOsActionsStore();
const { rebootType } = storeToRefs(updateOsActionsStore);
const subtitle = computed(() => {
if (rebootType.value === 'downgrade') {
return t('Please finish the initiated update to enable a downgrade.');
}
return '';
});
</script>
<template>
<UiPageContainer>
<UpdateOsStatus
:title="t('Downgrade Unraid OS')"
:subtitle="subtitle"
:downgrade-not-available="restoreVersion === ''"
:t="t"
/>

View File

@@ -33,7 +33,7 @@ const showRebootRequired = computed(() => rebootType.value !== '');
<button
class="group leading-none"
:title="t('View release notes')"
@click="updateOsActionsStore.viewCurrentReleaseNotes(t('{0} Release Notes', [osVersion]))"
@click="updateOsActionsStore.viewReleaseNotes(t('{0} Release Notes', [osVersion]))"
>
<UiBadge
color="custom"

View File

@@ -23,6 +23,7 @@ const props = defineProps<{
}>();
const serverStore = useServerStore();
const { dateTimeFormat } = storeToRefs(serverStore);
const {
outputDateTimeFormatted: formattedReleaseDate,

View File

@@ -36,7 +36,7 @@ const serverStore = useServerStore();
const updateOsStore = useUpdateOsStore();
const updateOsActionsStore = useUpdateOsActionsStore();
const { dateTimeFormat, osVersion, regExp, regUpdatesExpired } = storeToRefs(serverStore);
const { dateTimeFormat, osVersion, rebootVersion, regExp, regUpdatesExpired } = storeToRefs(serverStore);
const { available, availableWithRenewal, parsedReleaseTimestamp } = storeToRefs(updateOsStore);
const { ineligibleText, rebootType, rebootTypeText, status } = storeToRefs(updateOsActionsStore);
@@ -75,7 +75,7 @@ const regExpOutput = computed(() => {
<button
class="group"
:title="t('View release notes')"
@click="updateOsActionsStore.viewCurrentReleaseNotes(t('{0} Release Notes', [osVersion]))"
@click="updateOsActionsStore.viewReleaseNotes(t('{0} Release Notes', [osVersion]))"
>
<UiBadge :icon="InformationCircleIcon" class="underline">
{{ t('Current Version {0}', [osVersion]) }}
@@ -151,9 +151,9 @@ const regExpOutput = computed(() => {
:t="t"
/>
<BrandButton
v-else-if="rebootType === 'downgrade' || rebootType === 'upgrade'"
v-else-if="rebootType === 'downgrade' || rebootType === 'update'"
:icon="ArrowPathIcon"
:text="rebootType === 'downgrade' ? t('Reboot Now to Downgrade') : t('Reboot Now to Update')"
:text="rebootType === 'downgrade' ? t('Reboot Now to Downgrade to {0}', [rebootVersion]) : t('Reboot Now to Update to {0}', [rebootVersion])"
@click="updateOsActionsStore.rebootServer()"
/>
</div>

View File

@@ -238,8 +238,12 @@
"Downgrades are only recommended if you're unable to solve a critical issue. In the rare event you need to downgrade we ask that you please provide us with Diagnostics so we can investigate your issue. You will be prompted with the option to download the Diagnostics zip once the downgrade process is started. From there please open a bug report on our forums with a description of the issue and include your diagnostics.": "Downgrades are only recommended if you're unable to solve a critical issue. In the rare event you need to downgrade we ask that you please provide us with Diagnostics so we can investigate your issue. You will be prompted with the option to download the Diagnostics zip once the downgrade process is started. From there please open a bug report on our forums with a description of the issue and include your diagnostics.",
"Reboot Now to Downgrade": "Reboot Now to Downgrade",
"Reboot Now to Update": "Reboot Now to Update",
"Reboot Now to Downgrade to {0}": "Reboot Now to Downgrade to {0}",
"Reboot Now to Update to {0}": "Reboot Now to Update to {0}",
"Reboot Required for Downgrade": "Reboot Required for Downgrade",
"Reboot Required for Update": "Reboot Required for Update",
"Reboot Required for Downgrade to {0}": "Reboot Required for Downgrade to {0}",
"Reboot Required for Update to {0}": "Reboot Required for Update to {0}",
"Updating 3rd party drivers": "Updating 3rd party drivers",
"Update Available": "Update Available",
"Up-to-date": "Up-to-date",
@@ -322,5 +326,7 @@
"Calculating OS Update Eligibility…": "Calculating OS Update Eligibility…",
"Cancel": "Cancel",
"Unknown error": "Unknown error",
"Installing Extended": "Installing Extended"
"Installing Extended": "Installing Extended",
"Please finish the initiated downgrade to enable updates.": "Please finish the initiated downgrade to enable updates.",
"Please finish the initiated update to enable a downgrade.": "Please finish the initiated update to enable a downgrade."
}

View File

@@ -89,6 +89,8 @@ export const useServerStore = defineStore('server', () => {
const name = ref<string>('');
const osVersion = ref<string>('');
const osVersionBranch = ref<ServerOsVersionBranch>('stable');
const rebootType = ref<'thirdPartyDriversDownloading' | 'downgrade' | 'update' | ''>('');
const rebootVersion = ref<string | undefined>();
const registered = ref<boolean>();
const regDev = ref<number>(0);
const regGen = ref<number>(0);
@@ -742,6 +744,7 @@ export const useServerStore = defineStore('server', () => {
* Actions
*/
const setServer = (data: Server) => {
console.debug('[setServer]', data);
if (typeof data?.apiKey !== 'undefined') { apiKey.value = data.apiKey; }
if (typeof data?.apiVersion !== 'undefined') { apiVersion.value = data.apiVersion; }
if (typeof data?.avatar !== 'undefined') { avatar.value = data.avatar; }
@@ -765,6 +768,8 @@ export const useServerStore = defineStore('server', () => {
if (typeof data?.name !== 'undefined') { name.value = data.name; }
if (typeof data?.osVersion !== 'undefined') { osVersion.value = data.osVersion; }
if (typeof data?.osVersionBranch !== 'undefined') { osVersionBranch.value = data.osVersionBranch; }
if (typeof data?.rebootType !== 'undefined') { rebootType.value = data.rebootType; }
if (typeof data?.rebootVersion !== 'undefined') { rebootVersion.value = data.rebootVersion; }
if (typeof data?.registered !== 'undefined') { registered.value = data.registered; }
if (typeof data?.regGen !== 'undefined') { regGen.value = data.regGen; }
if (typeof data?.regGuid !== 'undefined') { regGuid.value = data.regGuid; }
@@ -896,6 +901,12 @@ export const useServerStore = defineStore('server', () => {
});
};
watchEffect(() => {
if (rebootVersion.value) {
console.debug('[server.rebootVersion]', rebootVersion.value);
}
})
return {
// state
apiKey,
@@ -919,6 +930,8 @@ export const useServerStore = defineStore('server', () => {
name,
osVersion,
osVersionBranch,
rebootType,
rebootVersion,
registered,
regDev,
regGen,

View File

@@ -140,7 +140,7 @@ export const useUpdateOsStoreGeneric = (payload?: UpdateOsStorePayload) =>
const isAvailableStable = computed(() => available.value ? isVersionStable(available.value) : false);
const filteredNextReleases = computed(() => {
if (!osVersion.value) { return undefined; }
if (!osVersion.value) return undefined;
if (releases.value?.response?.next) {
return releases.value?.response?.next.filter(
@@ -151,7 +151,7 @@ export const useUpdateOsStoreGeneric = (payload?: UpdateOsStorePayload) =>
});
const filteredPreviewReleases = computed(() => {
if (!osVersion.value) { return undefined; }
if (!osVersion.value) return undefined;
if (releases.value?.response?.preview) {
return releases.value?.response?.preview.filter(
@@ -162,7 +162,7 @@ export const useUpdateOsStoreGeneric = (payload?: UpdateOsStorePayload) =>
});
const filteredStableReleases = computed(() => {
if (!osVersion.value) { return undefined; }
if (!osVersion.value) return undefined;
if (releases.value?.response?.stable) {
return releases.value?.response?.stable.filter(
@@ -173,7 +173,7 @@ export const useUpdateOsStoreGeneric = (payload?: UpdateOsStorePayload) =>
});
const filteredTestReleases = computed(() => {
if (!osVersion.value) { return undefined; }
if (!osVersion.value) return undefined;
if (releases.value?.response?.test) {
return releases.value?.response?.test.filter(
@@ -193,7 +193,7 @@ export const useUpdateOsStoreGeneric = (payload?: UpdateOsStorePayload) =>
...(filteredNextReleases.value && { next: [...filteredNextReleases.value] }),
...(filteredPreviewReleases.value && { preview: [...filteredPreviewReleases.value] }),
...(filteredTestReleases.value && { test: [...filteredTestReleases.value] }),
};
}
});
/**
@@ -203,30 +203,35 @@ export const useUpdateOsStoreGeneric = (payload?: UpdateOsStorePayload) =>
*/
const releasesUrl = computed((): typeof OS_RELEASES => {
const isOnAccountApp = window.location.origin === ACCOUNT.origin;
/**
* @note The webgui should only use stable and next URLs.
* Users with test or preview would need to manually check for updates.
*
* Alternatively, what could be done is the webgui URLs slightly differ in that the JSON only contains the sha256s of the releases.
* Rather than revealing the entire release object with the download URLs.
* This may require download URLs to be generated on the fly or the URLs are randomized and don't follow a specific pattern.
* Because https://stable.dl.unraid.net/unRAIDServer-6.12.4-x86_64.zip is a pretty obvious pattern.
* Even if it means just adding a randomized hash to the end of the URL.
* */
const webguiNextBranch = !isOnAccountApp && osVersionBranch.value === 'next';
const webguiPreviewBranch = !isOnAccountApp && osVersionBranch.value === 'preview';
const webguiTestBranch = !isOnAccountApp && osVersionBranch.value === 'test';
const accountAppLoggedIn = isOnAccountApp && isLoggedIn.value;
/** @todo should we remove the || checks directly below and only rely on the group? */
const accountAppPreviewBranch = accountAppLoggedIn && (osVersionBranch.value === 'preview' || (authUserGroups.value && authUserGroups.value.includes('download_preview')));
const accountAppTestBranch = accountAppLoggedIn && (osVersionBranch.value === 'test' || (authUserGroups.value && authUserGroups.value.includes('download_test')));
const accountAppPreviewBranch = accountAppLoggedIn && authUserGroups.value && authUserGroups.value.includes('download_preview');
const accountAppTestBranch = accountAppLoggedIn && authUserGroups.value && authUserGroups.value.includes('download_test');
console.debug('[releasesUrl]', {
osVersionBranch: osVersionBranch.value,
authUserGroups: authUserGroups.value,
isOnAccountApp,
webguiNextBranch,
webguiPreviewBranch,
webguiTestBranch,
accountAppLoggedIn,
accountAppPreviewBranch,
accountAppTestBranch,
});
const useNextBranch = webguiNextBranch || accountAppLoggedIn;
const usePreviewBranch = webguiPreviewBranch || accountAppPreviewBranch;
const useTestBranch = webguiTestBranch || accountAppTestBranch;
const usePreviewBranch = accountAppPreviewBranch;
const useTestBranch = accountAppTestBranch;
console.debug('[releasesUrl]', {
useNextBranch,
usePreviewBranch,
@@ -234,10 +239,10 @@ export const useUpdateOsStoreGeneric = (payload?: UpdateOsStorePayload) =>
});
let releasesUrl = OS_RELEASES;
if (useNextBranch) { releasesUrl = OS_RELEASES_NEXT; }
if (usePreviewBranch || useTestBranch || import.meta.env.VITE_OS_RELEASES_PREVIEW_FORCE) { releasesUrl = OS_RELEASES_PREVIEW; }
/** @todo implement separate test branch json once available */
// if (useTestBranch) releasesUrl = OS_RELEASES_PREVIEW.toString();
if (useNextBranch) releasesUrl = OS_RELEASES_NEXT;
if (usePreviewBranch) releasesUrl = OS_RELEASES_PREVIEW;
if (useTestBranch) releasesUrl = OS_RELEASES_TEST;
return releasesUrl;
});
// actions
@@ -246,7 +251,7 @@ export const useUpdateOsStoreGeneric = (payload?: UpdateOsStorePayload) =>
timestamp: Date.now(),
response,
};
};
}
const cacheReleasesResponse = () => {
localStorage.setItem(RELEASES_LOCAL_STORAGE_KEY, JSON.stringify(releases.value));
@@ -285,7 +290,7 @@ export const useUpdateOsStoreGeneric = (payload?: UpdateOsStorePayload) =>
const currentTime = new Date().getTime();
const cacheDuration = import.meta.env.DEV ? 30000 : 604800000; // 30 seconds for testing, 7 days for prod
if (currentTime - releases.value.timestamp > cacheDuration) {
// cache is expired, purge it
// cache is expired, purge it
console.debug('[requestReleases] cache EXPIRED');
await purgeReleasesCache();
} else {
@@ -293,7 +298,7 @@ export const useUpdateOsStoreGeneric = (payload?: UpdateOsStorePayload) =>
console.debug('[requestReleases] cache VALID', releases.value.response);
return releases.value.response;
}
}
}
// If here we're needing to fetch a new releases…whether it's the first time or b/c the cache was expired
try {
@@ -343,7 +348,7 @@ export const useUpdateOsStoreGeneric = (payload?: UpdateOsStorePayload) =>
return console.error('[checkForUpdate] no releases found');
}
Object.keys(releases.value.response ?? {}).forEach((key) => {
Object.keys(releases.value.response ?? {}).forEach(key => {
// this is just to make TS happy (it's already checked above…thanks github copilot for knowing what I needed)
if (!releases.value) {
return;
@@ -359,7 +364,7 @@ export const useUpdateOsStoreGeneric = (payload?: UpdateOsStorePayload) =>
return;
}
branchReleases.find((release) => {
branchReleases.find(release => {
if (gt(release.version, osVersion.value)) {
// before we set the available version, check if the license key updates have expired to ensure we don't show an update that the user can't install
if (regUpdatesExpired.value && releaseDateGtRegExpDate(release.date, regExp.value)) {
@@ -379,14 +384,14 @@ export const useUpdateOsStoreGeneric = (payload?: UpdateOsStorePayload) =>
const findRelease = (searchKey: keyof Release, searchValue: string): Release | null => {
const response = releases?.value?.response;
if (!response) { return null; }
if (!response) return null;
for (const key of Object.keys(response)) {
const branchReleases = response[key as keyof ReleasesResponse];
if (!branchReleases || branchReleases.length === 0) { continue; }
if (!branchReleases || branchReleases.length === 0) continue;
const foundRelease = branchReleases.find(release => release[searchKey] === searchValue);
if (foundRelease) { return foundRelease; }
if (foundRelease) return foundRelease;
}
return null;

View File

@@ -16,6 +16,7 @@ import {
} from '~/store/updateOs';
import type { ExternalUpdateOsAction } from '~/store/callback';
import type { ServerRebootType } from '~/types/server';
import type { UserProfileLink } from '~/types/userProfile';
/**
@@ -43,7 +44,7 @@ export const useUpdateOsActionsStore = defineStore('updateOsActions', () => {
/** used when coming back from callback, this will be the release to install */
const status = ref<'confirming' | 'checking' | 'ineligible' | 'failed' | 'ready' | 'success' | 'updating' | 'downgrading'>('ready');
const callbackUpdateRelease = ref<Release | null>(null);
const rebootType = ref<'thirdPartyDriversDownloading' | 'downgrade' | 'upgrade' | ''>('');
const rebootType = computed(() => serverStore.rebootType);
const rebootTypeText = computed(() => {
/** translations are handled by rendering template's `t()` */
switch (rebootType.value) {
@@ -51,12 +52,13 @@ export const useUpdateOsActionsStore = defineStore('updateOsActions', () => {
return 'Updating 3rd party drivers';
case 'downgrade':
return 'Reboot Required for Downgrade';
case 'upgrade':
case 'update':
return 'Reboot Required for Update';
default:
return '';
}
});
const rebootVersion = computed(() => serverStore.rebootVersion);
const ineligible = computed(() => !serverStore.guid || !serverStore.keyfile || !osVersion.value || regUpdatesExpired.value);
const ineligibleText = computed(() => { // translated in components
@@ -100,7 +102,7 @@ export const useUpdateOsActionsStore = defineStore('updateOsActions', () => {
},
type: 'updateOs',
}],
serverStore.inIframe,
serverStore.inIframe ? 'newTab' : '',
);
},
external: !!updateOsStore.available,
@@ -111,7 +113,7 @@ export const useUpdateOsActionsStore = defineStore('updateOsActions', () => {
};
};
const executeUpdateOsCallback = async () => {
const executeUpdateOsCallback = async (autoRedirectReplace?: boolean) => {
await callbackStore.send(
ACCOUNT_CALLBACK.toString(),
[{
@@ -120,7 +122,7 @@ export const useUpdateOsActionsStore = defineStore('updateOsActions', () => {
},
type: 'updateOs',
}],
serverStore.inIframe,
serverStore.inIframe ? 'newTab' : (autoRedirectReplace ? 'replace' : ''),
);
};
@@ -177,8 +179,10 @@ export const useUpdateOsActionsStore = defineStore('updateOsActions', () => {
// @ts-ignore • global set in the webgui
document.rebootNow.submit();
};
const viewCurrentReleaseNotes = (modalTitle:string, webguiFilePath?:string|undefined) => { // @ts-ignore
/**
* By default this will display current version's release notes
*/
const viewReleaseNotes = (modalTitle:string, webguiFilePath?:string|undefined) => { // @ts-ignore
if (typeof openChanges === 'function') { // @ts-ignore
openChanges(`showchanges ${webguiFilePath ?? '/var/tmp/unRAIDServer.txt'}`, modalTitle);
} else {
@@ -190,10 +194,6 @@ export const useUpdateOsActionsStore = defineStore('updateOsActions', () => {
status.value = payload;
};
const setRebootType = (payload: typeof rebootType.value) => {
rebootType.value = payload;
};
watchEffect(() => {
if (status.value === 'ready' && ineligible.value) {
setStatus('ineligible');
@@ -209,6 +209,7 @@ export const useUpdateOsActionsStore = defineStore('updateOsActions', () => {
regUpdatesExpired,
rebootType,
rebootTypeText,
rebootVersion,
status,
ineligible,
ineligibleText,
@@ -221,9 +222,8 @@ export const useUpdateOsActionsStore = defineStore('updateOsActions', () => {
executeUpdateOsCallback,
rebootServer,
setStatus,
setRebootType,
setUpdateOsAction,
viewCurrentReleaseNotes,
viewReleaseNotes,
getReleaseFromKeyServer,
};
});

View File

@@ -31,8 +31,9 @@ export type ServerState = 'BASIC'
| undefined;
export type ServerOsVersionBranch = 'stable' | 'next' | 'preview' | 'test';
export type ServerconnectPluginInstalled = 'dynamix.unraid.net.plg' | 'dynamix.unraid.net.staging.plg' | 'dynamix.unraid.net.plg_installFailed' | 'dynamix.unraid.net.staging.plg_installFailed' | '';
export type ServerRebootType = 'thirdPartyDriversDownloading' | 'downgrade' | 'update' | '';
export interface Server {
apiKey?: string;
apiVersion?: string;
@@ -58,6 +59,8 @@ export interface Server {
name?: string;
osVersion?: string;
osVersionBranch?: ServerOsVersionBranch;
rebootType?: ServerRebootType;
rebootVersion?: string;
registered?: boolean;
regDev?: number;
regGen?: number;