Status: {{ available ? 'Update Available' : 'Up-to-date' }}
-
-
-
-
-
-
-
-
-
- {{ available ? t(updateButton?.text ?? '', updateButton?.textParams) : t('Check for Updates')}}
-
-
-
-
{{ t('Receive the latest and greatest for Unraid OS. Whether it new features, security patches, or bug fixes – keeping your server up-to-date ensures the best experience that Unraid has to offer.') }}
+
+ {{ t('Downgrade Unraid OS to {0}', [version]) }}
+
+
+
{{ t('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 download the Diagnostics zip once the downgrade process is started. From there please open a bug report on our forums.') }}
{{ t('Receive the latest and greatest for Unraid OS. Whether it new features, security patches, or bug fixes – keeping your server up-to-date ensures the best experience that Unraid has to offer.') }}
+
+
+
+
+
+
+
+
+
+
diff --git a/web/components/UserProfile.ce.vue b/web/components/UserProfile.ce.vue
index 2f524b5a1..dadfb920e 100644
--- a/web/components/UserProfile.ce.vue
+++ b/web/components/UserProfile.ce.vue
@@ -36,6 +36,7 @@ const {
connectPluginInstalled,
} = storeToRefs(serverStore);
const { bannerGradient, theme } = storeToRefs(useThemeStore());
+const { isOsVersionStable } = storeToRefs(updateOsStore);
const hideDropdown = computed(() => state.value === 'PRO' && !connectPluginInstalled.value);
@@ -84,9 +85,11 @@ onBeforeMount(() => {
}
callbackStore.watcher();
+
updateOsStore.checkForUpdate({
cache: true,
guid: guid.value,
+ includeNext: isOsVersionStable.value, // @todo ensure this is correct
keyfile: keyfile.value,
osVersion: osVersion.value,
});
diff --git a/web/components/UserProfile/DropdownTrigger.vue b/web/components/UserProfile/DropdownTrigger.vue
index 2376557f3..8a26dc7ec 100644
--- a/web/components/UserProfile/DropdownTrigger.vue
+++ b/web/components/UserProfile/DropdownTrigger.vue
@@ -3,6 +3,7 @@ import { storeToRefs } from 'pinia';
import {
Bars3Icon,
Bars3BottomRightIcon,
+ BellAlertIcon,
ExclamationTriangleIcon,
InformationCircleIcon,
ShieldExclamationIcon,
@@ -11,6 +12,7 @@ import {
import { useDropdownStore } from '~/store/dropdown';
import { useErrorsStore } from '~/store/errors';
import { useServerStore } from '~/store/server';
+import { useUpdateOsStore } from '~/store/updateOsActions';
const props = defineProps<{ t: any; }>();
@@ -18,6 +20,7 @@ const dropdownStore = useDropdownStore();
const { dropdownVisible } = storeToRefs(dropdownStore);
const { errors } = storeToRefs(useErrorsStore());
const { state, stateData } = storeToRefs(useServerStore());
+const { available: osUpdateAvailable } = storeToRefs(useUpdateOsStore());
const showErrorIcon = computed(() => errors.value.length || stateData.value.error);
@@ -49,9 +52,16 @@ const title = computed((): string => {
+
+
-
+
+
+
+
diff --git a/web/locales/en_US.json b/web/locales/en_US.json
index 2b7b1de9c..fbb9cc8c0 100644
--- a/web/locales/en_US.json
+++ b/web/locales/en_US.json
@@ -208,10 +208,24 @@
"Unraid OS Update Available": "Unraid OS Update Available",
"Update Unraid OS confirmation required": "Update Unraid OS confirmation required",
"Please confirm the update details below": "Please confirm the update details below",
- "Current Version: Unraid {0}": "Current Version: Unraid {0}",
+ "Current Version {0}": "Current Version {0}",
"New Version: {0}": "New Version: {0}",
"This update will require a reboot": "This update will require a reboot",
"Confirm and start update": "Confirm and start update",
"Update Unraid OS": "Update Unraid OS",
- "Last checked: {0}": "Last checked: {0}"
+ "Last checked: {0}": "Last checked: {0}",
+ "Downgrade Unraid OS": "Downgrade Unraid OS",
+ "Downgrade Unraid OS to {0}": "Downgrade Unraid OS to {0}",
+ "Begin restore to Unraid {0}": "Begin restore to Unraid {0}",
+ "Version available for restore {0}": "Version available for restore {0}",
+ "Check for Updates": "Check for Updates",
+ "Include Prereleases": "Include Prereleases",
+ "Receive the latest and greatest for Unraid OS. Whether it new features, security patches, or bug fixes – keeping your server up-to-date ensures the best experience that Unraid has to offer.": "Receive the latest and greatest for Unraid OS. Whether it new features, security patches, or bug fixes – keeping your server up-to-date ensures the best experience that Unraid has to offer.",
+ "Check For Updates": "Check For Updates",
+ "Checking...": "Checking...",
+ "View changelog for current version {0}": "View changelog for current version {0}",
+ "View Changelog for {0}": "View Changelog for {0}",
+ "View changelog & update": "View changelog & update",
+ "{0} Release Notes": "{0} Release Notes",
+ "Unable to open release notes": "Unable to open release notes"
}
diff --git a/web/package-lock.json b/web/package-lock.json
index c02888349..cd122c7b5 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -20,6 +20,7 @@
"graphql-tag": "^2.12.6",
"graphql-ws": "^5.14.0",
"hex-to-rgba": "^2.0.1",
+ "marked": "^9.0.3",
"semver": "^7.5.4",
"vue-i18n": "^9.2.2",
"wretch": "^2.6.0"
@@ -13269,6 +13270,17 @@
"node": ">=0.10.0"
}
},
+ "node_modules/marked": {
+ "version": "9.0.3",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-9.0.3.tgz",
+ "integrity": "sha512-pI/k4nzBG1PEq1J3XFEHxVvjicfjl8rgaMaqclouGSMPhk7Q3Ejb2ZRxx/ZQOcQ1909HzVoWCFYq6oLgtL4BpQ==",
+ "bin": {
+ "marked": "bin/marked.js"
+ },
+ "engines": {
+ "node": ">= 16"
+ }
+ },
"node_modules/mdn-data": {
"version": "2.0.30",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
diff --git a/web/package.json b/web/package.json
index 2feaa8535..f03228fe5 100644
--- a/web/package.json
+++ b/web/package.json
@@ -49,6 +49,7 @@
"graphql-tag": "^2.12.6",
"graphql-ws": "^5.14.0",
"hex-to-rgba": "^2.0.1",
+ "marked": "^9.0.3",
"semver": "^7.5.4",
"vue-i18n": "^9.2.2",
"wretch": "^2.6.0"
diff --git a/web/pages/index.vue b/web/pages/index.vue
index 63f4e1c0d..398261b04 100644
--- a/web/pages/index.vue
+++ b/web/pages/index.vue
@@ -18,7 +18,9 @@ onBeforeMount(() => {
DownloadApiLogsCe
diff --git a/web/store/updateOs.ts b/web/store/updateOs.ts
index caeb96f56..a9414ffb8 100644
--- a/web/store/updateOs.ts
+++ b/web/store/updateOs.ts
@@ -71,6 +71,10 @@ export const useUpdateOsStoreGeneric = (
// getters
const cachedReleasesTimestamp = computed(() => releases.value?.timestamp);
const isOsVersionStable = computed(() => !isVersionStable(osVersion.value));
+ const isAvailableStable = computed(() => {
+ if (!available.value) return undefined;
+ return !isVersionStable(available.value);
+ });
const filteredStableReleases = computed(() => {
if (!osVersion.value) return undefined;
@@ -106,6 +110,7 @@ export const useUpdateOsStoreGeneric = (
});
// actions
const setReleasesState = (response: ReleasesResponse) => {
+ console.debug('[setReleasesState]');
releases.value = {
timestamp: Date.now(),
response,
@@ -113,44 +118,64 @@ export const useUpdateOsStoreGeneric = (
}
const cacheReleasesResponse = () => {
+ console.debug('[cacheReleasesResponse]');
localStorage.setItem(RELEASES_LOCAL_STORAGE_KEY, JSON.stringify(releases.value));
};
- const purgeReleasesCache = () => {
+ const purgeReleasesCache = async () => {
+ console.debug('[purgeReleasesCache]');
releases.value = undefined;
- localStorage.removeItem(RELEASES_LOCAL_STORAGE_KEY);
+ await localStorage.removeItem(RELEASES_LOCAL_STORAGE_KEY);
};
const requestReleases = async (payload: RequestReleasesPayload): Promise => {
+ console.debug('[requestReleases]', payload);
+
if (!payload || !payload.guid || !payload.keyfile) {
throw new Error('Invalid Payload for updateOs.requestReleases');
}
if (payload.skipCache) {
- purgeReleasesCache();
+ await 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;
- }
- }
+ * 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.
+ */
+ else if (!payload.skipCache && 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) {
+ // cache is expired, purge it
+ console.debug('[requestReleases] cache EXPIRED');
+ await purgeReleasesCache();
+ } else {
+ // if the cache is valid return the existing response
+ console.debug('[requestReleases] cache VALID');
+ 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;
+ console.debug('[requestReleases] fetching new releases', testReleasesResponse);
+ /**
+ * @todo replace with real api call, note that the structuredClone is required otherwise Vue will not provided a reactive object from the original static response
+ * const response: ReleasesResponse = await request.url(OS_RELEASES.toString()).get().json();
+ */
+ const response: ReleasesResponse = await structuredClone(testReleasesResponse);
+ console.debug('[requestReleases] response', 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 data
+ */
+ console.debug('[requestReleases] checking for next releases', payload.includeNext, response.next)
+ if (!payload.includeNext && response.next) {
+ console.debug('[requestReleases] removing next releases from data')
+ delete response.next;
+ }
// save it to local state
setReleasesState(response);
@@ -175,6 +200,9 @@ export const useUpdateOsStoreGeneric = (
// 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;
+ // reset available
+ available.value = '';
+
// gets releases from cache or fetches from api
await requestReleases(payload);
@@ -182,16 +210,6 @@ export const useUpdateOsStoreGeneric = (
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 data
- */
- console.debug('[checkForUpdate] checking for next releases', payload.includeNext, isOsVersionStable.value, releases.value.response.next)
- if (!payload.includeNext || isOsVersionStable.value && releases.value.response.next) {
- console.debug('[checkForUpdate] removing next releases from data')
- 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) {
@@ -247,6 +265,7 @@ export const useUpdateOsStoreGeneric = (
// getters
cachedReleasesTimestamp,
isOsVersionStable,
+ isAvailableStable,
filteredStableReleases,
filteredNextReleases,
allFilteredReleases,
diff --git a/web/tailwind.config.ts b/web/tailwind.config.ts
index 66d54a06b..b2788dcf1 100644
--- a/web/tailwind.config.ts
+++ b/web/tailwind.config.ts
@@ -96,6 +96,7 @@ export default >{
'350px': '350px',
'640px': '640px',
'800px': '800px',
+ '1024px': '1024px',
},
screens: {
'2xs': '470px',