diff --git a/web/_data/osReleases.ts b/web/_data/osReleases.ts
index ec185f9d9..1a5225796 100644
--- a/web/_data/osReleases.ts
+++ b/web/_data/osReleases.ts
@@ -43,6 +43,19 @@ const testOsReleasesResponse: OsReleasesResponse = {
}
],
"stable": [
+ {
+ "version": "6.12.5",
+ "name": "Unraid Server 6.12.5",
+ "basefile": "unRAIDServer-6.12.5-x86_64.zip",
+ "date": "2023-08-31",
+ "url": "https://dl.stable.unraid.net/unRAIDServer-6.12.5-x86_64.zip",
+ "changelog": "https://unraid.net/blog/unraid-os-6.12.5-release-notes",
+ "md5": "FAKEbddcf415f2d0518804e551c16125",
+ "size": 12345122,
+ "sha256": "fda177bb1336270b24e4df0fd0c1dd0596c44699204f57c83ce70a0f19173be4",
+ "plugin_url": "https://dl.stable.unraid.net/unRAIDServer-6.12.5.plg",
+ "plugin_sha256": "83850536ed6982bd582ed107d977d59e9b9b786363e698b14d1daf52e2dec2d9"
+ },
{
"version": "6.12.4",
"name": "Unraid Server 6.12.4",
diff --git a/web/components/UserProfile.ce.vue b/web/components/UserProfile.ce.vue
index 9b8bca468..2f524b5a1 100644
--- a/web/components/UserProfile.ce.vue
+++ b/web/components/UserProfile.ce.vue
@@ -7,7 +7,7 @@ import { useCallbackStore } from '~/store/callbackActions';
import { useDropdownStore } from '~/store/dropdown';
import { useServerStore } from '~/store/server';
import { useThemeStore } from '~/store/theme';
-import { useUpdateOsStore } from '~/store/updateOs';
+import { useUpdateOsStore } from '~/store/updateOsActions';
import type { Server } from '~/types/server';
import 'tailwindcss/tailwind.css';
import '~/assets/main.css';
@@ -25,7 +25,16 @@ const serverStore = useServerStore();
const updateOsStore = useUpdateOsStore();
const { dropdownVisible } = storeToRefs(dropdownStore);
-const { name, description, lanIp, state, connectPluginInstalled } = storeToRefs(serverStore);
+const {
+ name,
+ description,
+ guid,
+ keyfile,
+ lanIp,
+ osVersion,
+ state,
+ connectPluginInstalled,
+} = storeToRefs(serverStore);
const { bannerGradient, theme } = storeToRefs(useThemeStore());
const hideDropdown = computed(() => state.value === 'PRO' && !connectPluginInstalled.value);
@@ -75,7 +84,12 @@ onBeforeMount(() => {
}
callbackStore.watcher();
- updateOsStore.checkForOsUpdate();
+ updateOsStore.checkForUpdate({
+ cache: true,
+ guid: guid.value,
+ keyfile: keyfile.value,
+ osVersion: osVersion.value,
+ });
});
diff --git a/web/components/UserProfile/CallbackFeedback.vue b/web/components/UserProfile/CallbackFeedback.vue
index cd63e0e29..0a3f0d358 100644
--- a/web/components/UserProfile/CallbackFeedback.vue
+++ b/web/components/UserProfile/CallbackFeedback.vue
@@ -12,7 +12,7 @@ import { useCallbackActionsStore } from '~/store/callbackActions';
import { useInstallKeyStore } from '~/store/installKey';
// import { usePromoStore } from '~/store/promo';
import { useServerStore } from '~/store/server';
-import { useUpdateOsStore } from '~/store/updateOs';
+import { useUpdateOsActionsStore } from '~/store/updateOsActions';
export interface Props {
open?: boolean;
@@ -28,7 +28,7 @@ const callbackActionsStore = useCallbackActionsStore();
const installKeyStore = useInstallKeyStore();
// const promoStore = usePromoStore();
const serverStore = useServerStore();
-const updateOsStore = useUpdateOsStore();
+const updateOsActionStore = useUpdateOsActionsStore();
const {
accountAction,
@@ -54,7 +54,7 @@ const {
const {
status: updateOsStatus,
callbackUpdateRelease,
-} = storeToRefs(updateOsStore);
+} = storeToRefs(updateOsActionStore);
/**
* Post sign in success state:
* If we're on the Connect settings page in the webGUI
@@ -111,7 +111,7 @@ const close = () => {
};
const cancelUpdateOs = () => {
- updateOsStore.setStatus('ready');
+ updateOsActionStore.setStatus('ready');
callbackActionsStore.setCallbackStatus('ready')
};
@@ -317,7 +317,7 @@ const { copy, copied, isSupported } = useClipboard({ source: keyUrl.value });
/>
diff --git a/web/components/UserProfile/DropdownContent.vue b/web/components/UserProfile/DropdownContent.vue
index 1754c8369..8d7814c19 100644
--- a/web/components/UserProfile/DropdownContent.vue
+++ b/web/components/UserProfile/DropdownContent.vue
@@ -6,7 +6,7 @@ import { ACCOUNT, CONNECT_DASHBOARD, PLUGIN_SETTINGS } from '~/helpers/urls';
import { useErrorsStore } from '~/store/errors';
// import { usePromoStore } from '~/store/promo';
import { useServerStore } from '~/store/server';
-import { useUpdateOsStore } from '~/store/updateOs';
+import { useUpdateOsStore, useUpdateOsActionsStore } from '~/store/updateOsActions';
import type { UserProfileLink } from '~/types/userProfile';
const props = defineProps<{ t: any; }>();
@@ -16,7 +16,8 @@ const errorsStore = useErrorsStore();
const { errors } = storeToRefs(errorsStore);
const { keyActions, connectPluginInstalled, registered, stateData } = storeToRefs(useServerStore());
-const { updateAvailable, initUpdateOsCallback } = storeToRefs(useUpdateOsStore());
+const { available: osUpdateAvailable } = storeToRefs(useUpdateOsStore());
+const { initUpdateOsCallback } = storeToRefs(useUpdateOsActionsStore());
const signInAction = computed(() => stateData.value.actions?.filter((act: { name: string; }) => act.name === 'signIn') ?? []);
const signOutAction = computed(() => stateData.value.actions?.filter((act: { name: string; }) => act.name === 'signOut') ?? []);
@@ -26,7 +27,7 @@ const links = computed(():UserProfileLink[] => {
...(registered.value && connectPluginInstalled.value
? [
{
- emphasize: !updateAvailable.value, // only emphasize when we don't have an update available
+ emphasize: !osUpdateAvailable.value, // only emphasize when we don't have an update available
external: true,
href: CONNECT_DASHBOARD.toString(),
icon: ArrowTopRightOnSquareIcon,
@@ -93,7 +94,7 @@ const showKeyline = computed(() => showConnectStatus.value && (keyActions.value?
-
+
diff --git a/web/store/callback.ts b/web/store/callback.ts
index 9510a4dc5..7d17a1b71 100644
--- a/web/store/callback.ts
+++ b/web/store/callback.ts
@@ -34,26 +34,6 @@ export type PurchaseActionTypes = Purchase | Redeem | Upgrade;
export type ServerActionTypes = AccountActionTypes | AccountKeyActionTypes | PurchaseActionTypes;
-export interface OsRelease {
- version: string; // 6.12.4
- name: string; // Unraid Server 6.12.4
- basefile: string; // unRAIDServer-6.12.4-x86_64.zip
- date: string; // 2023-08-31
- url: string; // https://dl.stable.unraid.net/unRAIDServer-6.12.4-x86_64.zip
- changelog: string; // https://unraid.net/blog/unraid-os-6.12.4-release-notes
- md5: string; // 9050bddcf415f2d0518804e551c1be98
- size: number; // 12345122
- sha256: string; // fda177bb1336270b24e4df0fd0c1dd0596c44699204f57c83ce70a0f19173be4
- plugin_url: string; // https://dl.stable.unraid.net/unRAIDServer-6.12.4.plg
- plugin_sha256: string; // 83850536ed6982bd582ed107d977d59e9b9b786363e698b14d1daf52e2dec2d9"
-}
-export interface OsReleasesResponse {
- stable: OsRelease[];
- next?: OsRelease[];
- preview?: OsRelease[];
- test?: OsRelease[];
-}
-
/**
* Represents a server, payload comes from the server to account.unraid.net
*/
diff --git a/web/store/callbackActions.ts b/web/store/callbackActions.ts
index e5be31ef4..e3217e52b 100644
--- a/web/store/callbackActions.ts
+++ b/web/store/callbackActions.ts
@@ -4,7 +4,7 @@ import { addPreventClose, removePreventClose } from '~/composables/preventClose'
import { useAccountStore } from '~/store/account';
import { useInstallKeyStore } from '~/store/installKey';
import { useServerStore } from '~/store/server';
-import { useUpdateOsStore } from '~/store/updateOs';
+import { useUpdateOsStore, useUpdateOsActionsStore } from '~/store/updateOsActions';
import { useCallbackStoreGeneric, type CallbackActionsStore, type ExternalKeyActions, type QueryPayloads } from '~/store/callback';
export const useCallbackActionsStore = defineStore('callbackActions', () => {
@@ -12,6 +12,7 @@ export const useCallbackActionsStore = defineStore('callbackActions', () => {
const installKeyStore = useInstallKeyStore();
const serverStore = useServerStore();
const updateOsStore = useUpdateOsStore();
+ const updateOsActionsStore = useUpdateOsActionsStore();
type CallbackStatus = 'closing' | 'error' | 'loading' | 'ready' | 'success';
const callbackStatus = ref('ready');
@@ -61,7 +62,11 @@ export const useCallbackActionsStore = defineStore('callbackActions', () => {
}
if (action.type === 'updateOs' && action?.releaseHash) {
- updateOsStore.confirmUpdateOs(action.releaseHash);
+ const foundRelease = updateOsStore.findReleaseByMd5(action.releaseHash);
+ if (!foundRelease) {
+ throw new Error('Release not found');
+ }
+ updateOsActionsStore.confirmUpdateOs(foundRelease);
if (array.length === 1) { // only 1 action, skip refresh server state
// removing query string relase is set so users can't refresh the page and go through the same actions
window.history.replaceState(null, '', window.location.pathname);
diff --git a/web/store/server.ts b/web/store/server.ts
index f468b3a46..0aeec2795 100644
--- a/web/store/server.ts
+++ b/web/store/server.ts
@@ -836,6 +836,7 @@ export const useServerStore = defineStore('server', () => {
deviceCount,
expireTime,
guid,
+ keyfile,
inIframe,
locale,
lanIp,
diff --git a/web/store/updateOs.ts b/web/store/updateOs.ts
index 2aab82a9f..c02ee2af3 100644
--- a/web/store/updateOs.ts
+++ b/web/store/updateOs.ts
@@ -1,20 +1,47 @@
-import testOsReleasesResponse from '~/_data/osReleases'; // test data
+import testReleasesResponse from '~/_data/osReleases'; // test data
-import { BellAlertIcon } from '@heroicons/vue/24/solid';
import { defineStore, createPinia, setActivePinia } from 'pinia';
import gt from 'semver/functions/gt';
-import coerce from 'semver/functions/coerce';
+import prerelease from 'semver/functions/prerelease';
import type { SemVer } from 'semver';
+import { computed, ref } from 'vue';
-import useInstallPlugin from '~/composables/installPlugin';
-import { request } from '~/composables/services/request';
-import { ACCOUNT_CALLBACK, OS_RELEASES } from '~/helpers/urls';
-import { useCallbackStore } from '~/store/callbackActions';
-import { useErrorsStore } from '~/store/errors';
-import { useServerStore } from '~/store/server';
-import type { InstallPluginPayload } from '~/composables/installPlugin';
-import type { OsRelease, OsReleasesResponse } from '~/store/callback';
-import type { ServerStateDataAction } from '~/types/server';
+export interface RequestReleasesPayload {
+ cache?: boolean; // saves response to localStorage
+ guid: string;
+ includeNext?: boolean; // if a user is on a stable release and they want to see what's available on the next branch
+ keyfile: string;
+ osVersion: SemVer | string;
+ skipCache?: boolean; // forces a refetch from the api
+}
+
+export interface Release {
+ version: string; // 6.12.4
+ name: string; // Unraid Server 6.12.4
+ basefile: string; // unRAIDServer-6.12.4-x86_64.zip
+ date: string; // 2023-08-31
+ url: string; // https://dl.stable.unraid.net/unRAIDServer-6.12.4-x86_64.zip
+ changelog: string; // https://unraid.net/blog/unraid-os-6.12.4-release-notes
+ md5: string; // 9050bddcf415f2d0518804e551c1be98
+ size: number; // 12345122
+ sha256: string; // fda177bb1336270b24e4df0fd0c1dd0596c44699204f57c83ce70a0f19173be4
+ plugin_url: string; // https://dl.stable.unraid.net/unRAIDServer-6.12.4.plg
+ plugin_sha256: string; // 83850536ed6982bd582ed107d977d59e9b9b786363e698b14d1daf52e2dec2d9"
+}
+export interface ReleasesResponse {
+ stable: Release[];
+ next?: Release[];
+ preview?: Release[];
+ test?: Release[];
+}
+export interface CachedReleasesResponse {
+ timestamp: number;
+ response: ReleasesResponse;
+}
+
+export interface UpdateOsActionStore {
+ osVersion: SemVer | string;
+}
/**
* @see https://stackoverflow.com/questions/73476371/using-pinia-with-vue-js-web-components
@@ -22,189 +49,163 @@ import type { ServerStateDataAction } from '~/types/server';
*/
setActivePinia(createPinia());
-export interface CachedOsReleasesResponse {
- timestamp: number;
- response: OsReleasesResponse;
-}
+export const RELEASES_LOCAL_STORAGE_KEY = 'unraidReleasesResponse';
-export const useUpdateOsStore = defineStore('updateOs', () => {
- const callbackStore = useCallbackStore();
- const errorsStore = useErrorsStore();
- const serverStore = useServerStore();
-
- const { install: installPlugin } = useInstallPlugin();
-
- // State
- const status = ref<'confirming' | 'failed' | 'ready' | 'success' | 'updating' | 'downgrading'>('ready');
- const releasesJson = ref(localStorage.getItem('releasesJson') ? JSON.parse(localStorage.getItem('releasesJson') ?? '') : undefined);
- const callbackUpdateRelease = ref(); // used when coming back from callback, this will be the release to install
- const updateAvailable = ref(); // used locally to show update action button
- const downgradeAvailable = ref(false);
-
- // Getters
- const currentOsVersion = computed(() => serverStore?.osVersion);
- const isOsVersionStable = computed(() => serverStore?.isOsVersionStable); // used to determine if we should look for stable or next releases
- // const currentOsVersionNext = computed((): boolean => serverStore?.osVersionNext);
-
- // Actions
- const fetchOsReleases = async () => {
- try {
- // const response: OsReleasesResponse = await request.url(OS_RELEASES.toString()).get().json();
- const response = testOsReleasesResponse;
- releasesJson.value = {
+export const useUpdateOsStoreGeneric = (
+ useUpdateOsActions: () => UpdateOsActionStore,
+) =>
+ defineStore('updateOs', () => {
+ const updateOsActions = useUpdateOsActions();
+ // state
+ const available = ref('');
+ const releases = ref(localStorage.getItem(RELEASES_LOCAL_STORAGE_KEY) ? JSON.parse(localStorage.getItem(RELEASES_LOCAL_STORAGE_KEY) ?? '') : undefined);
+ const osVersion = ref(updateOsActions.osVersion);
+ // getters
+ const isOsVersionStable = computed(() => {
+ const hasPrerelease = prerelease(osVersion.value);
+ return !hasPrerelease;
+ });
+ // actions
+ const setReleasesState = (response: ReleasesResponse) => {
+ releases.value = {
timestamp: Date.now(),
response,
};
- localStorage.setItem('releasesJson', JSON.stringify(releasesJson.value));
- } catch (error) {
- console.error('[fetchOsReleases]', error);
- }
- };
- const purgeReleasesJsonCache = () => {
- releasesJson.value = undefined;
- localStorage.removeItem('releasesJson');
- };
- const checkForOsUpdate = async (skipCache: boolean = false, includeNext: boolean = false) => {
- if (!currentOsVersion.value) {
- return console.error('[checkForOsUpdate] currentOsVersion not found, skipping OS update check');
}
- if (skipCache) { // forces new check
- purgeReleasesJsonCache();
- }
-
- try {
- /**
- * Compare the timestamp of the cached data to the current time,
- * if it's older than 7 days, reset releasesJson.
- * Which will trigger a new API call to get the releases.
- * Otherwise skip the API call and use the cached data.
- */
- if (releasesJson.value) {
- const currentTime = new Date().getTime();
- const localState = releasesJson.value;
- const cacheDuration = import.meta.env.DEV ? 30000 : 604800000; // 30 seconds for testing, 7 days for prod
- if (currentTime - localState.timestamp > cacheDuration) {
- purgeReleasesJsonCache();
- await fetchOsReleases();
- }
- } else {
- await fetchOsReleases();
- }
-
- if (releasesJson.value && releasesJson.value.response) {
- /**
- * If we're on stable and the user hasn't requested to include next releases in the check
- * then remove next releases from the cached data
- */
- if (!includeNext && isOsVersionStable.value && releasesJson.value.response.next) {
- delete releasesJson.value.response.next;
- }
-
- Object.keys(releasesJson.value.response ?? {}).forEach(key => {
- if (!releasesJson.value) { // this is just to make TS happy (it's already checked above…thanks github copilot for knowing what I needed)
- return;
- }
-
- if (updateAvailable.value) {
- return;
- }
-
- const releases = releasesJson.value.response[key as keyof OsReleasesResponse];
-
- if (releases && releases.length > 0) {
- releases.find(release => {
- if (gt(release.version, currentOsVersion.value)) { /** @todo '6.12.0' temporary for dev. Replace with currentOsVersion.value */
- updateAvailable.value = release;
- return true; // stop looping, we found an update
- }
- });
- }
- });
- }
- } catch (error) {
- console.error('[checkForOsUpdate]', error);
- }
- };
-
- const initUpdateOsCallback = computed((): ServerStateDataAction => {
- return {
- click: () => {
- callbackStore.send(
- ACCOUNT_CALLBACK.toString(),
- [{
- server: {
- ...serverStore.serverAccountPayload,
- },
- type: 'updateOs',
- }],
- serverStore.inIframe,
- );
- },
- emphasize: true,
- external: true,
- icon: BellAlertIcon,
- name: 'updateOs',
- text: 'Unraid OS Update Available',
- }
- });
- /**
- * @description When receiving the callback the Account update page we'll use the provided releaseMd5 to find the release in the releasesJson cache.
- */
- const confirmUpdateOs = async (releaseMd5: string) => {
- /** this should never happen, but if it does we should probably try to fetch the releases again */
- if (!releasesJson.value) {
- await fetchOsReleases();
+ const cacheReleasesResponse = () => {
+ localStorage.setItem(RELEASES_LOCAL_STORAGE_KEY, JSON.stringify(releases.value));
};
- Object.keys(releasesJson.value?.response ?? {}).forEach(key => {
- const releases = releasesJson.value?.response[key as keyof OsReleasesResponse];
+ const purgeReleasesCache = () => {
+ releases.value = undefined;
+ localStorage.removeItem(RELEASES_LOCAL_STORAGE_KEY);
+ };
- if (releases && releases.length > 0) {
- releases.forEach(release => {
- if (release.md5 === releaseMd5) {
- callbackUpdateRelease.value = release;
- return;
+ const requestReleases = async (payload: RequestReleasesPayload): Promise => {
+ if (!payload || !payload.guid || !payload.keyfile) {
+ throw new Error('Invalid Payload for updateOs.requestReleases');
+ }
+
+ if (payload.skipCache) {
+ purgeReleasesCache();
+ }
+
+ /**
+ * Compare the timestamp of the cached releases data to the current time,
+ * if it's older than 7 days, reset releases.
+ * Which will trigger a new API call to get the releases.
+ * Otherwise skip the API call and use the cached data.
+ */
+ if (releases.value) {
+ 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) {
+ purgeReleasesCache();
+ } else {
+ // if the cache is valid return the existing 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 {
+ // const response: ReleasesResponse = await request.url(OS_RELEASES.toString()).get().json();
+ const response: ReleasesResponse = await testReleasesResponse;
+
+ // save it to local state
+ setReleasesState(response);
+ if (payload.cache) {
+ cacheReleasesResponse();
+ }
+
+ return response;
+ } catch (error) {
+ console.error('[requestReleases]', error);
+ }
+ };
+
+ const checkForUpdate = async (payload: RequestReleasesPayload) => {
+ console.debug('[checkForUpdate]', payload);
+
+ if (!payload || !payload.osVersion || !payload.guid || !payload.keyfile) {
+ console.error('[checkForUpdate] invalid payload');
+ throw new Error('Invalid Payload for updateOs.checkForUpdate');
+ }
+
+ // set the osVersion since this is the first thing in this store using it…that way we don't need to import the server store in this store.
+ osVersion.value = payload.osVersion;
+
+ // gets releases from cache or fetches from api
+ await requestReleases(payload);
+
+ if (!releases.value) {
+ return console.error('[checkForUpdate] no releases found');
+ }
+
+ /**
+ * If we're on stable and the user hasn't requested to include next releases in the check
+ * then remove next releases from the cached data
+ */
+ if (!payload.includeNext && isOsVersionStable.value && releases.value.response.next) {
+ delete releases.value.response.next;
+ }
+
+ 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;
+ }
+ // if we've already found an available update, skip the rest
+ if (available.value) {
+ return;
+ }
+
+ const branchReleases = releases.value.response[key as keyof ReleasesResponse];
+
+ if (!branchReleases || branchReleases.length === 0) {
+ return;
+ }
+
+ branchReleases.find(release => {
+ if (gt(release.version, osVersion.value)) {
+ available.value = release.version;
+ return true;
}
});
- }
- })
+ });
+ };
- setStatus('confirming');
- };
+ const findReleaseByMd5 = (releaseMd5: string): Release | null => {
+ let releaseForReturn: Release | null = null;
- const installOsUpdate = () => {
- if (!callbackUpdateRelease.value) {
- return console.error('[installOsUpdate] release not found');
- }
+ Object.keys(releases.value?.response ?? {}).forEach(key => {
+ const branchReleases = releases.value?.response[key as keyof ReleasesResponse];
- status.value = 'updating';
- installPlugin({
- modalTitle: `${callbackUpdateRelease.value.name} Update`,
- pluginUrl: callbackUpdateRelease.value.url,
- update: true,
- });
- };
+ if (releaseForReturn || !branchReleases || branchReleases.length == 0) {
+ return;
+ }
- const downgradeOs = async () => {
- setStatus('downgrading');
- };
+ branchReleases.find(release => {
+ if (release.md5 === releaseMd5) {;
+ releaseForReturn = release;
+ return release;
+ }
+ });
+ });
- const setStatus = (payload: typeof status.value) => {
- status.value = payload;
- };
+ return releaseForReturn;
+ };
- return {
- // State
- callbackUpdateRelease,
- status,
- updateAvailable,
- // Actions
- checkForOsUpdate,
- confirmUpdateOs,
- downgradeOs,
- installOsUpdate,
- initUpdateOsCallback,
- setStatus,
- };
-});
+ return {
+ // state
+ available,
+ releases,
+ // getters
+ isOsVersionStable,
+ // actions
+ checkForUpdate,
+ findReleaseByMd5,
+ requestReleases,
+ };
+ });
diff --git a/web/store/updateOsActions.ts b/web/store/updateOsActions.ts
new file mode 100644
index 000000000..5fba937df
--- /dev/null
+++ b/web/store/updateOsActions.ts
@@ -0,0 +1,113 @@
+import testOsReleasesResponse from '~/_data/osReleases'; // test data
+
+import { BellAlertIcon } from '@heroicons/vue/24/solid';
+import { defineStore, createPinia, setActivePinia } from 'pinia';
+import gt from 'semver/functions/gt';
+import coerce from 'semver/functions/coerce';
+import type { SemVer } from 'semver';
+
+import useInstallPlugin from '~/composables/installPlugin';
+import { request } from '~/composables/services/request';
+
+import { ACCOUNT_CALLBACK, OS_RELEASES } from '~/helpers/urls';
+
+import { useCallbackStore } from '~/store/callbackActions';
+import { useErrorsStore } from '~/store/errors';
+import { useServerStore } from '~/store/server';
+import {
+ useUpdateOsStoreGeneric,
+ type Release,
+ type ReleasesResponse,
+ type CachedReleasesResponse,
+ type UpdateOsActionStore,
+} from '~/store/updateOs';
+
+import { type InstallPluginPayload } from '~/composables/installPlugin';
+import type { ServerStateDataAction } from '~/types/server';
+
+/**
+ * @see https://stackoverflow.com/questions/73476371/using-pinia-with-vue-js-web-components
+ * @see https://github.com/vuejs/pinia/discussions/1085
+ */
+setActivePinia(createPinia());
+
+export const useUpdateOsActionsStore = defineStore('updateOsActions', () => {
+ const callbackStore = useCallbackStore();
+ const errorsStore = useErrorsStore();
+ const serverStore = useServerStore();
+ const updateOsStoreGeneric = useUpdateOsStoreGeneric();
+
+ const { install: installPlugin } = useInstallPlugin();
+
+ // State
+ const osVersion = computed(() => serverStore.osVersion);
+ /** used when coming back from callback, this will be the release to install */
+ const status = ref<'confirming' | 'failed' | 'ready' | 'success' | 'updating' | 'downgrading'>('ready');
+ const callbackUpdateRelease = ref(null);
+
+ // Actions
+ const initUpdateOsCallback = computed((): ServerStateDataAction => {
+ return {
+ click: () => {
+ callbackStore.send(
+ ACCOUNT_CALLBACK.toString(),
+ [{
+ server: {
+ ...serverStore.serverAccountPayload,
+ },
+ type: 'updateOs',
+ }],
+ serverStore.inIframe,
+ );
+ },
+ emphasize: true,
+ external: true,
+ icon: BellAlertIcon,
+ name: 'updateOs',
+ text: 'Unraid OS Update Available',
+ }
+ });
+ /**
+ * @description When receiving the callback the Account update page we'll use the provided releaseMd5 to find the release in the releases cache.
+ */
+ const confirmUpdateOs = async (release: Release) => {
+ callbackUpdateRelease.value = release;
+ setStatus('confirming');
+ };
+
+ const installOsUpdate = () => {
+ if (!callbackUpdateRelease.value) {
+ return console.error('[installOsUpdate] release not found');
+ }
+
+ setStatus('updating');
+ installPlugin({
+ modalTitle: `${callbackUpdateRelease.value.name} Update`,
+ pluginUrl: callbackUpdateRelease.value.plugin_url,
+ update: true,
+ });
+ };
+
+ const downgradeOs = async () => {
+ setStatus('downgrading');
+ };
+
+ const setStatus = (payload: typeof status.value) => {
+ status.value = payload;
+ };
+
+ return {
+ // State
+ osVersion,
+ callbackUpdateRelease,
+ status,
+ // Actions
+ confirmUpdateOs,
+ downgradeOs,
+ installOsUpdate,
+ initUpdateOsCallback,
+ setStatus,
+ };
+});
+
+export const useUpdateOsStore = useUpdateOsStoreGeneric(useUpdateOsActionsStore as unknown as () => UpdateOsActionStore);