mirror of
https://github.com/unraid/api.git
synced 2025-12-31 13:39:52 -06:00
feat: WIP first pass at UpdateOs page replacement component
This commit is contained in:
@@ -43,32 +43,32 @@ 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",
|
||||
"basefile": "unRAIDServer-6.12.4-x86_64.zip",
|
||||
"date": "2023-08-31",
|
||||
"url": "https://dl.stable.unraid.net/unRAIDServer-6.12.4-x86_64.zip",
|
||||
"changelog": "https://unraid.net/blog/unraid-os-6.12.4-release-notes",
|
||||
"md5": "9050bddcf415f2d0518804e551c1be98",
|
||||
"size": 12345122,
|
||||
"sha256": "fda177bb1336270b24e4df0fd0c1dd0596c44699204f57c83ce70a0f19173be4",
|
||||
"plugin_url": "https://dl.stable.unraid.net/unRAIDServer-6.12.4.plg",
|
||||
"plugin_sha256": "83850536ed6982bd582ed107d977d59e9b9b786363e698b14d1daf52e2dec2d9"
|
||||
},
|
||||
// {
|
||||
// "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",
|
||||
// "basefile": "unRAIDServer-6.12.4-x86_64.zip",
|
||||
// "date": "2023-08-31",
|
||||
// "url": "https://dl.stable.unraid.net/unRAIDServer-6.12.4-x86_64.zip",
|
||||
// "changelog": "https://unraid.net/blog/unraid-os-6.12.4-release-notes",
|
||||
// "md5": "9050bddcf415f2d0518804e551c1be98",
|
||||
// "size": 12345122,
|
||||
// "sha256": "fda177bb1336270b24e4df0fd0c1dd0596c44699204f57c83ce70a0f19173be4",
|
||||
// "plugin_url": "https://dl.stable.unraid.net/unRAIDServer-6.12.4.plg",
|
||||
// "plugin_sha256": "83850536ed6982bd582ed107d977d59e9b9b786363e698b14d1daf52e2dec2d9"
|
||||
// },
|
||||
{
|
||||
"version": "6.12.3",
|
||||
"name": "Unraid Server 6.12.3",
|
||||
|
||||
@@ -70,7 +70,7 @@ export const serverState: Server = {
|
||||
bgColor: '',
|
||||
descriptionShow: true,
|
||||
metaColor: '',
|
||||
name: 'black',
|
||||
name: 'white',
|
||||
textColor: ''
|
||||
},
|
||||
uptime,
|
||||
|
||||
@@ -30,11 +30,11 @@ defineEmits(['click']);
|
||||
const classes = computed(() => {
|
||||
switch (props.btnStyle) {
|
||||
case 'fill':
|
||||
return 'text-white bg-gradient-to-r from-unraid-red to-orange hover:from-unraid-red/60 hover:to-orange/60 focus:from-unraid-red/60 focus:to-orange/60';
|
||||
return 'text-white bg-gradient-to-r from-unraid-red to-orange shadow-none hover:from-unraid-red/60 hover:to-orange/60 focus:from-unraid-red/60 focus:to-orange/60 hover:shadow-md focus:shadow-md';
|
||||
case 'outline':
|
||||
return 'text-orange bg-gradient-to-r from-transparent to-transparent border-2 border-solid border-orange hover:text-white focus:text-white hover:from-unraid-red hover:to-orange focus:from-unraid-red focus:to-orange hover:border-transparent focus:border-transparent';
|
||||
return 'text-orange bg-gradient-to-r from-transparent to-transparent border-2 border-solid border-orange shadow-none hover:text-white focus:text-white hover:from-unraid-red hover:to-orange focus:from-unraid-red focus:to-orange hover:shadow-md focus:shadow-md';
|
||||
case 'underline':
|
||||
return 'opacity-75 hover:opacity-100 focus:opacity-100 underline transition hover:text-alpha hover:bg-beta focus:text-alpha focus:bg-beta';
|
||||
return 'opacity-75 hover:opacity-100 focus:opacity-100 underline transition shadow-none hover:text-alpha hover:bg-beta focus:text-alpha focus:bg-beta hover:shadow-md focus:shadow-md';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -45,3 +45,9 @@ provide(I18nInjectionKey, i18n);
|
||||
<template>
|
||||
<slot />
|
||||
</template>
|
||||
|
||||
<style>
|
||||
/* unraid-i18n-host {
|
||||
font-size: 16px;
|
||||
} */
|
||||
</style>
|
||||
109
web/components/Ui/Badge.vue
Normal file
109
web/components/Ui/Badge.vue
Normal file
@@ -0,0 +1,109 @@
|
||||
<script setup lang="ts">
|
||||
import { XCircleIcon } from '@heroicons/vue/24/solid';
|
||||
import BrandLoading from '~/components/Brand/Loading.vue';
|
||||
import BrandLoadingWhite from '~/components/Brand/LoadingWhite.vue';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
color?: 'gray' | 'red' | 'yellow' | 'green' | 'blue' | 'indigo' | 'purple' | 'pink' | 'orange' | 'black' | 'white' | 'transparent' | 'current';
|
||||
icon?: typeof XCircleIcon | typeof BrandLoading | typeof BrandLoadingWhite;
|
||||
iconRight?: typeof XCircleIcon | typeof BrandLoading | typeof BrandLoadingWhite;
|
||||
size?: '12px' | '14px' | '16px' | '18px' | '20px' | '24px';
|
||||
}>(), {
|
||||
color: 'gray',
|
||||
icon: undefined,
|
||||
iconRight: undefined,
|
||||
size: '16px',
|
||||
});
|
||||
|
||||
const computedStyleClasses = computed(() => {
|
||||
let colorClasses = '';
|
||||
let textSize = '';
|
||||
let iconSize = '';
|
||||
switch (props.color) {
|
||||
case 'red':
|
||||
colorClasses = 'bg-unraid-red text-white group-hover:bg-orange-dark group-focus:bg-orange-dark';
|
||||
break;
|
||||
case 'yellow':
|
||||
colorClasses = 'bg-yellow-100 text-yellow-800 group-hover:bg-yellow-200 group-focus:bg-yellow-200';
|
||||
break;
|
||||
case 'green':
|
||||
colorClasses = 'bg-green-100 text-green-800 group-hover:bg-green-200 group-focus:bg-green-200';
|
||||
break;
|
||||
case 'blue':
|
||||
colorClasses = 'bg-blue-100 text-blue-800 group-hover:bg-blue-200 group-focus:bg-blue-200';
|
||||
break;
|
||||
case 'indigo':
|
||||
colorClasses = 'bg-indigo-100 text-indigo-800 group-hover:bg-indigo-200 group-focus:bg-indigo-200';
|
||||
break;
|
||||
case 'purple':
|
||||
colorClasses = 'bg-purple-100 text-purple-800 group-hover:bg-purple-200 group-focus:bg-purple-200';
|
||||
break;
|
||||
case 'pink':
|
||||
colorClasses = 'bg-pink-100 text-pink-800 group-hover:bg-pink-200 group-focus:bg-pink-200';
|
||||
break;
|
||||
case 'orange':
|
||||
colorClasses = 'bg-orange text-white group-hover:bg-orange-dark group-focus:bg-orange-dark';
|
||||
break;
|
||||
case 'black':
|
||||
colorClasses = 'bg-black text-white group-hover:bg-gray-800 group-focus:bg-gray-800';
|
||||
break;
|
||||
case 'white':
|
||||
colorClasses = 'bg-white text-black group-hover:bg-gray-100 group-focus:bg-gray-100';
|
||||
break;
|
||||
case 'transparent':
|
||||
colorClasses = 'bg-transparent text-black group-hover:bg-gray-100 group-focus:bg-gray-100';
|
||||
break;
|
||||
case 'current':
|
||||
colorClasses = 'bg-current text-black group-hover:bg-gray-100 group-focus:bg-gray-100';
|
||||
break;
|
||||
case 'gray':
|
||||
colorClasses = 'bg-gray-200 text-gray-800 group-hover:bg-gray-300 group-focus:bg-gray-300';
|
||||
break;
|
||||
}
|
||||
switch (props.size) {
|
||||
case '12px':
|
||||
textSize = 'text-12px px-8px py-4px';
|
||||
iconSize = 'w-12px';
|
||||
break;
|
||||
case '14px':
|
||||
textSize = 'text-14px px-8px py-4px';
|
||||
iconSize = 'w-14px';
|
||||
break;
|
||||
case '16px':
|
||||
textSize = 'text-16px px-12px py-8px';
|
||||
iconSize = 'w-16px';
|
||||
break;
|
||||
case '18px':
|
||||
textSize = 'text-18px px-12px py-8px';
|
||||
iconSize = 'w-18px';
|
||||
break;
|
||||
case '20px':
|
||||
textSize = 'text-20px px-16px py-12px';
|
||||
iconSize = 'w-20px';
|
||||
break;
|
||||
case '24px':
|
||||
textSize = 'text-24px px-16px py-12px';
|
||||
iconSize = 'w-24px';
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
badge: `${textSize} ${colorClasses}`,
|
||||
icon: iconSize,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span
|
||||
class="inline-flex items-center rounded-full font-semibold leading-none transition-all duration-200 ease-in-out"
|
||||
:class="[
|
||||
computedStyleClasses.badge,
|
||||
icon || iconRight ? 'gap-8px' : '',
|
||||
]"
|
||||
>
|
||||
<component :is="icon" v-if="icon" class="flex-shrink-0" :class="computedStyleClasses.icon" />
|
||||
<slot></slot>
|
||||
<component :is="iconRight" v-if="iconRight" class="flex-shrink-0" :class="computedStyleClasses.icon" />
|
||||
</span>
|
||||
</template>
|
||||
14
web/components/Ui/CardWrapper.vue
Normal file
14
web/components/Ui/CardWrapper.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{ increasedPadding?: boolean }>(), {
|
||||
increasedPadding: false,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="group/card p-4 text-left relative flex flex-col flex-1 text-beta bg-alpha border-2 border-solid border-gamma/50 rounded-md shadow-md hover:shadow-orange/50 transition-all"
|
||||
:class="increasedPadding && 'md:p-6'"
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
@@ -3,118 +3,44 @@
|
||||
* @todo require keyfile to be set before allowing user to check for updates
|
||||
* @todo require keyfile to update
|
||||
* @todo require valid guid / server state to update
|
||||
* @todo detect downgrade possibility
|
||||
*/
|
||||
import { Switch, SwitchGroup, SwitchLabel } from '@headlessui/vue'
|
||||
import { ArrowTopRightOnSquareIcon, BellAlertIcon } from '@heroicons/vue/24/solid';
|
||||
import dayjs from 'dayjs';
|
||||
import dayjs, { extend } from 'dayjs';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ref, watchEffect } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import { useUpdateOsStore } from '~/store/updateOsActions';
|
||||
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import '~/assets/main.css';
|
||||
|
||||
import { useServerStore } from '~/store/server';
|
||||
import { useUpdateOsStore, useUpdateOsActionsStore } from '~/store/updateOsActions';
|
||||
import type { UserProfileLink } from '~/types/userProfile';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const serverStore = useServerStore();
|
||||
const updateOsStore = useUpdateOsStore();
|
||||
const updateOsActionsStore = useUpdateOsActionsStore();
|
||||
|
||||
const { guid, keyfile, osVersion } = storeToRefs(serverStore);
|
||||
const { available, cachedReleasesTimestamp } = storeToRefs(updateOsStore);
|
||||
|
||||
const includeNext = ref(false);
|
||||
|
||||
const updateButton = ref<UserProfileLink | undefined>();
|
||||
|
||||
const parsedCachedReleasesTimestamp = computed(() => {
|
||||
if (!cachedReleasesTimestamp.value) { return ''; }
|
||||
return dayjs(cachedReleasesTimestamp.value).format('YYYY-MM-DD HH:mm:ss');
|
||||
export interface Props {
|
||||
restoreVersion?: string;
|
||||
}
|
||||
withDefaults(defineProps<Props>(), {
|
||||
restoreVersion: '',
|
||||
});
|
||||
|
||||
const check = () => {
|
||||
updateOsStore.checkForUpdate({
|
||||
cache: true,
|
||||
guid: guid.value,
|
||||
includeNext: includeNext.value,
|
||||
keyfile: keyfile.value,
|
||||
osVersion: osVersion.value,
|
||||
skipCache: true,
|
||||
});
|
||||
};
|
||||
const updateOsStore = useUpdateOsStore();
|
||||
const { cachedReleasesTimestamp } = storeToRefs(updateOsStore);
|
||||
|
||||
watchEffect(() => {
|
||||
if (available.value) {
|
||||
updateButton.value = updateOsActionsStore.initUpdateOsCallback();
|
||||
} else {
|
||||
updateButton.value = undefined;
|
||||
}
|
||||
extend(relativeTime);
|
||||
const parsedReleaseTimestamp = computed(() => {
|
||||
if (!cachedReleasesTimestamp.value) { return ''; }
|
||||
return {
|
||||
formatted: dayjs(cachedReleasesTimestamp.value).format('YYYY-MM-DD HH:mm:ss'),
|
||||
relative: dayjs().to(dayjs(cachedReleasesTimestamp.value)),
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid gap-y-24px">
|
||||
<div class="grid gap-y-16px">
|
||||
<h1 class="text-24px">{{ t('Update Unraid OS') }}</h1>
|
||||
<p>Current Version: {{ osVersion }}</p>
|
||||
<p>Status: {{ available ? 'Update Available' : 'Up-to-date' }}</p>
|
||||
</div>
|
||||
|
||||
<div class="text-16px text-alpha bg-beta text-left relative flex flex-col justify-around border-2 border-solid shadow-xl transform overflow-hidden rounded-lg transition-all sm:w-full">
|
||||
<div class="px-16px py-20px sm:p-24px">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-20px">
|
||||
<div class="grid gap-y-16px">
|
||||
<h3 class="text-20px font-semibold leading-6 text-gray-900 flex flex-row items-center gap-8px">
|
||||
<BellAlertIcon class="w-20px shrink-0" />
|
||||
<span>
|
||||
{{ available ? t(updateButton?.text ?? '', updateButton?.textParams) : t('Check for Updates')}}
|
||||
</span>
|
||||
</h3>
|
||||
<div class="max-w-xl text-sm text-gray-500 whitespace-normal">
|
||||
<p class="text-18px">{{ 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.') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col sm:flex-shrink-0 items-center gap-16px">
|
||||
<SwitchGroup v-if="!available" as="div">
|
||||
<div class="flex flex-shrink-0 items-center gap-16px">
|
||||
<Switch v-model="includeNext" :class="[includeNext ? 'bg-green-500' : 'bg-gray-200', 'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2']">
|
||||
<span :class="[includeNext ? 'translate-x-5' : 'translate-x-0', 'pointer-events-none relative inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out']">
|
||||
<span :class="[includeNext ? 'opacity-0 duration-100 ease-out' : 'opacity-100 duration-200 ease-in', 'absolute inset-0 flex h-full w-full items-center justify-center transition-opacity']" aria-hidden="true">
|
||||
<svg class="h-12px w-12px text-gray-400" fill="none" viewBox="0 0 12 12">
|
||||
<path d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
</span>
|
||||
<span :class="[includeNext ? 'opacity-100 duration-200 ease-in' : 'opacity-0 duration-100 ease-out', 'absolute inset-0 flex h-full w-full items-center justify-center transition-opacity']" aria-hidden="true">
|
||||
<svg class="h-12px w-12px text-green-500" fill="currentColor" viewBox="0 0 12 12">
|
||||
<path d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z" />
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</Switch>
|
||||
<SwitchLabel class="text-14px">{{ t('Include Prereleases') }}</SwitchLabel>
|
||||
</div>
|
||||
</SwitchGroup>
|
||||
<span class="flex flex-col gap-y-8px">
|
||||
<BrandButton
|
||||
v-if="available && updateButton"
|
||||
@click="updateButton?.click"
|
||||
:external="updateButton?.external"
|
||||
:icon-right="ArrowTopRightOnSquareIcon"
|
||||
:name="updateButton?.name"
|
||||
:text="t('View changelog & update')" />
|
||||
<BrandButton v-else @click="check" btn-style="outline" :text="t('Check Now')" />
|
||||
<span class="text-14px text-gamma text-center">{{ t('Last checked: {0}', [parsedCachedReleasesTimestamp]) }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-y-24px max-w-1024px mx-auto">
|
||||
<UpdateOsStatus :release-check-time="parsedReleaseTimestamp" :t="t" />
|
||||
<UpdateOsUpdate :release-check-time="parsedReleaseTimestamp" :t="t" />
|
||||
<UpdateOsDowngrade v-if="restoreVersion" :version="restoreVersion" :t="t" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
88
web/components/UpdateOs/CheckButton.vue
Normal file
88
web/components/UpdateOs/CheckButton.vue
Normal file
@@ -0,0 +1,88 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* @todo require keyfile to be set before allowing user to check for updates
|
||||
* @todo require keyfile to update
|
||||
* @todo require valid guid / server state to update
|
||||
* @todo detect downgrade possibility
|
||||
*/
|
||||
import { Switch, SwitchGroup, SwitchLabel } from '@headlessui/vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ref, watchEffect } from 'vue';
|
||||
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import '~/assets/main.css';
|
||||
|
||||
import { useServerStore } from '~/store/server';
|
||||
import { useUpdateOsStore, useUpdateOsActionsStore } from '~/store/updateOsActions';
|
||||
import type { UserProfileLink } from '~/types/userProfile';
|
||||
import { stat } from 'fs';
|
||||
|
||||
const props = defineProps<{
|
||||
releaseCheckTime: {
|
||||
formatted: string;
|
||||
relative: string;
|
||||
};
|
||||
t: any;
|
||||
}>();
|
||||
|
||||
const serverStore = useServerStore();
|
||||
const updateOsStore = useUpdateOsStore();
|
||||
const updateOsActionsStore = useUpdateOsActionsStore();
|
||||
|
||||
const { guid, keyfile, osVersion } = storeToRefs(serverStore);
|
||||
const { available } = storeToRefs(updateOsStore);
|
||||
|
||||
const includeNext = ref(false);
|
||||
|
||||
const status = ref<'ready' | 'checking'>('ready');
|
||||
|
||||
const buttonText = computed(() => {
|
||||
if (status.value === 'checking') {
|
||||
return props.t('Checking...');
|
||||
}
|
||||
return props.t('Check For Updates');
|
||||
});
|
||||
|
||||
const check = async () => {
|
||||
status.value = 'checking';
|
||||
|
||||
await updateOsStore.checkForUpdate({
|
||||
cache: true,
|
||||
guid: guid.value,
|
||||
includeNext: includeNext.value,
|
||||
keyfile: keyfile.value,
|
||||
osVersion: osVersion.value,
|
||||
skipCache: true,
|
||||
}).finally(() => {
|
||||
status.value = 'ready';
|
||||
})
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col sm:flex-shrink-0 items-center gap-16px">
|
||||
<SwitchGroup as="div">
|
||||
<div class="flex flex-shrink-0 items-center gap-16px">
|
||||
<Switch v-model="includeNext" :class="[includeNext ? 'bg-green-500' : 'bg-gray-200', 'relative inline-flex h-24px w-[44px] flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2']">
|
||||
<span :class="[includeNext ? 'translate-x-20px' : 'translate-x-0', 'pointer-events-none relative inline-block h-20px w-20px transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out']">
|
||||
<span :class="[includeNext ? 'opacity-0 duration-100 ease-out' : 'opacity-100 duration-200 ease-in', 'absolute inset-0 flex h-full w-full items-center justify-center transition-opacity']" aria-hidden="true">
|
||||
<svg class="h-12px w-12px text-gray-400" fill="none" viewBox="0 0 12 12">
|
||||
<path d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
</span>
|
||||
<span :class="[includeNext ? 'opacity-100 duration-200 ease-in' : 'opacity-0 duration-100 ease-out', 'absolute inset-0 flex h-full w-full items-center justify-center transition-opacity']" aria-hidden="true">
|
||||
<svg class="h-12px w-12px text-green-500" fill="currentColor" viewBox="0 0 12 12">
|
||||
<path d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z" />
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</Switch>
|
||||
<SwitchLabel class="text-14px">{{ t('Include Prereleases') }}</SwitchLabel>
|
||||
</div>
|
||||
</SwitchGroup>
|
||||
<span class="flex flex-col gap-y-8px">
|
||||
<BrandButton @click="check" :disabled="status === 'checking'" btn-style="outline" :text="buttonText" class="flex-0" />
|
||||
<span class="text-14px opacity-75 text-center" :title="releaseCheckTime.formatted">{{ t('Last checked: {0}', [releaseCheckTime.relative]) }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
64
web/components/UpdateOs/Downgrade.vue
Normal file
64
web/components/UpdateOs/Downgrade.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<script lang="ts" setup>
|
||||
import { ArrowUturnDownIcon, InformationCircleIcon } from '@heroicons/vue/24/solid';
|
||||
import type { SemVer } from 'semver';
|
||||
import { ref } from 'vue';
|
||||
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import '~/assets/main.css';
|
||||
|
||||
import { useUpdateOsActionsStore } from '~/store/updateOsActions';
|
||||
import type { UserProfileLink } from '~/types/userProfile';
|
||||
|
||||
const props = defineProps<{
|
||||
t: any;
|
||||
version: string;
|
||||
}>();
|
||||
|
||||
const updateOsActionsStore = useUpdateOsActionsStore();
|
||||
|
||||
const downgradeButton = ref<UserProfileLink | undefined>({
|
||||
click: () => {
|
||||
// @ts-ignore – global function provided by the webgui on the update page
|
||||
downgrade();
|
||||
},
|
||||
name: 'downgrade',
|
||||
text: props.t('Begin restore to Unraid {0}', [props.version]),
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UiCardWrapper :increased-padding="true">
|
||||
<div class="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-20px sm:gap-24px">
|
||||
<div class="grid gap-y-16px">
|
||||
<h3 class="text-20px font-semibold leading-6 flex flex-row items-center gap-8px">
|
||||
<ArrowUturnDownIcon class="w-20px shrink-0" />
|
||||
{{ t('Downgrade Unraid OS to {0}', [version]) }}
|
||||
</h3>
|
||||
<div class="text-16px leading-relaxed opacity-75 whitespace-normal">
|
||||
<p>{{ 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.') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="downgradeButton" class="flex flex-col sm:flex-shrink-0 items-center gap-16px">
|
||||
<BrandButton
|
||||
@click="downgradeButton?.click"
|
||||
btn-style="underline"
|
||||
:icon="InformationCircleIcon"
|
||||
:text="t('View Changelog for {0}', [version])" />
|
||||
<BrandButton
|
||||
@click="downgradeButton?.click"
|
||||
btn-style="outline"
|
||||
:external="downgradeButton?.external"
|
||||
:icon="ArrowUturnDownIcon"
|
||||
:name="downgradeButton?.name"
|
||||
:text="downgradeButton?.text" />
|
||||
</div>
|
||||
</div>
|
||||
</UiCardWrapper>
|
||||
</template>
|
||||
|
||||
<style lang="postcss">
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
</style>
|
||||
66
web/components/UpdateOs/Status.vue
Normal file
66
web/components/UpdateOs/Status.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
BellAlertIcon,
|
||||
CheckCircleIcon,
|
||||
InformationCircleIcon,
|
||||
} from '@heroicons/vue/24/solid';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
import { useServerStore } from '~/store/server';
|
||||
import { useUpdateOsStore } from '~/store/updateOsActions';
|
||||
|
||||
const props = defineProps<{
|
||||
releaseCheckTime: {
|
||||
formatted: string;
|
||||
relative: string;
|
||||
};
|
||||
t: any;
|
||||
}>();
|
||||
|
||||
const serverStore = useServerStore();
|
||||
const updateOsStore = useUpdateOsStore();
|
||||
|
||||
const { guid, keyfile, osVersion } = storeToRefs(serverStore);
|
||||
const { available } = storeToRefs(updateOsStore);
|
||||
|
||||
const viewReleaseNotes = () => {
|
||||
// @ts-ignore – this is a global function provided by the webgui
|
||||
if (typeof openChanges === 'function') {
|
||||
// @ts-ignore
|
||||
openChanges(
|
||||
'showchanges /var/tmp/unRAIDServer.txt',
|
||||
props.t('{0} Release Notes', [osVersion.value]),
|
||||
);
|
||||
} else {
|
||||
alert('Unable to open release notes');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid gap-y-16px">
|
||||
<h1 class="text-24px">{{ t('Update Unraid OS') }}</h1>
|
||||
<div class="flex flex-col md:flex-row gap-16px justify-start md:items-start md:justify-between">
|
||||
<div class="inline-flex gap-8px">
|
||||
<button
|
||||
@click="viewReleaseNotes"
|
||||
class="group"
|
||||
:title="t('View changelog for current version {0}', [osVersion])"
|
||||
>
|
||||
<UiBadge :icon="InformationCircleIcon">
|
||||
{{ t('Current Version {0}', [osVersion]) }}
|
||||
</UiBadge>
|
||||
</button>
|
||||
<UiBadge
|
||||
:color="available ? 'orange' : 'green'"
|
||||
:icon="available ? BellAlertIcon : CheckCircleIcon"
|
||||
:title="t('Last checked: {0}', [releaseCheckTime.relative])"
|
||||
>
|
||||
{{ available ? 'Update Available' : 'Up-to-date' }}
|
||||
</UiBadge>
|
||||
</div>
|
||||
|
||||
<UpdateOsCheckButton :releaseCheckTime="releaseCheckTime" :t="t" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
102
web/components/UpdateOs/Update.vue
Normal file
102
web/components/UpdateOs/Update.vue
Normal file
@@ -0,0 +1,102 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* @todo require keyfile to be set before allowing user to check for updates
|
||||
* @todo require keyfile to update
|
||||
* @todo require valid guid / server state to update
|
||||
* @todo detect downgrade possibility
|
||||
*/
|
||||
import { Switch, SwitchGroup, SwitchLabel } from '@headlessui/vue'
|
||||
import {
|
||||
ArrowPathIcon,
|
||||
ArrowTopRightOnSquareIcon,
|
||||
BellAlertIcon,
|
||||
} from '@heroicons/vue/24/solid';
|
||||
import dayjs from 'dayjs';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ref, watchEffect } from 'vue';
|
||||
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import '~/assets/main.css';
|
||||
|
||||
import { useServerStore } from '~/store/server';
|
||||
import { useUpdateOsStore, useUpdateOsActionsStore } from '~/store/updateOsActions';
|
||||
import type { UserProfileLink } from '~/types/userProfile';
|
||||
|
||||
const props = defineProps<{
|
||||
releaseCheckTime: {
|
||||
formatted: string;
|
||||
relative: string;
|
||||
};
|
||||
t: any;
|
||||
}>();
|
||||
|
||||
const serverStore = useServerStore();
|
||||
const updateOsStore = useUpdateOsStore();
|
||||
const updateOsActionsStore = useUpdateOsActionsStore();
|
||||
|
||||
const { guid, keyfile, osVersion } = storeToRefs(serverStore);
|
||||
const { available } = storeToRefs(updateOsStore);
|
||||
|
||||
const includeNext = ref(false);
|
||||
|
||||
const updateButton = ref<UserProfileLink | undefined>();
|
||||
|
||||
const availableText = computed(() => {
|
||||
if (available.value && updateButton?.value?.text && updateButton?.value?.textParams) {
|
||||
return props.t(updateButton?.value.text, updateButton?.value.textParams);
|
||||
}
|
||||
});
|
||||
|
||||
const check = () => {
|
||||
updateOsStore.checkForUpdate({
|
||||
cache: true,
|
||||
guid: guid.value,
|
||||
includeNext: includeNext.value,
|
||||
keyfile: keyfile.value,
|
||||
osVersion: osVersion.value,
|
||||
skipCache: true,
|
||||
});
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
if (available.value) {
|
||||
updateButton.value = updateOsActionsStore.initUpdateOsCallback();
|
||||
} else {
|
||||
updateButton.value = undefined;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UiCardWrapper :increased-padding="true">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-20px sm:gap-24px">
|
||||
<div class="grid gap-y-16px">
|
||||
<h3 class="text-20px font-semibold leading-6 flex flex-row items-center gap-8px">
|
||||
<BellAlertIcon v-if="available" class="w-20px shrink-0" />
|
||||
<ArrowPathIcon v-else class="w-20px shrink-0" />
|
||||
<span>
|
||||
{{ availableText ? availableText : t('Check for Updates')}}
|
||||
</span>
|
||||
</h3>
|
||||
<div class="text-16px leading-relaxed whitespace-normal opacity-75">
|
||||
<p>{{ 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.') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<BrandButton
|
||||
v-if="available && updateButton"
|
||||
@click="updateButton?.click"
|
||||
:external="updateButton?.external"
|
||||
:icon-right="ArrowTopRightOnSquareIcon"
|
||||
:name="updateButton?.name"
|
||||
:text="t('View changelog & update')" />
|
||||
</div>
|
||||
</UiCardWrapper>
|
||||
</template>
|
||||
|
||||
<style lang="postcss">
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
</style>
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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 => {
|
||||
<span class="absolute bottom-[-3px] inset-x-0 h-2px w-full bg-gradient-to-r from-unraid-red to-orange rounded opacity-0 group-hover:opacity-100 group-focus:opacity-100 transition-opacity" />
|
||||
</span>
|
||||
|
||||
<BellAlertIcon v-if="osUpdateAvailable" class="hover:animate-pulse text-white fill-current relative w-16px h-16px" />
|
||||
|
||||
<Bars3Icon v-if="!dropdownVisible" class="w-20px" />
|
||||
<Bars3BottomRightIcon v-else class="w-20px" />
|
||||
|
||||
<BrandAvatar />
|
||||
<span class="relative">
|
||||
<BrandAvatar />
|
||||
<!-- <span v-if="osUpdateAvailable" class="absolute z-10 -bottom-1 -right-3 w-24px h-24px flex items-center justify-center shadow border border-white bg-gradient-to-r from-unraid-red to-orange rounded-full">
|
||||
<BellAlertIcon class="hover:animate-pulse text-white fill-current relative w-12px h-12px" />
|
||||
</span> -->
|
||||
</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
12
web/package-lock.json
generated
12
web/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -18,7 +18,9 @@ onBeforeMount(() => {
|
||||
<h3 class="text-lg font-semibold font-mono">
|
||||
UserProfileCe
|
||||
</h3>
|
||||
<UserProfileCe :server="serverState" />
|
||||
<header class="bg-beta py-4">
|
||||
<UserProfileCe :server="serverState" />
|
||||
</header>
|
||||
<hr class="border-black dark:border-white">
|
||||
<h3 class="text-lg font-semibold font-mono">
|
||||
DownloadApiLogsCe
|
||||
@@ -43,7 +45,7 @@ onBeforeMount(() => {
|
||||
<h3 class="text-lg font-semibold font-mono">
|
||||
UpdateOsCe
|
||||
</h3>
|
||||
<UpdateOsCe />
|
||||
<UpdateOsCe :restore-version="'6.11.2'" />
|
||||
<hr class="border-black dark:border-white">
|
||||
<h3 class="text-lg font-semibold font-mono">
|
||||
ModalsCe
|
||||
|
||||
@@ -16,7 +16,9 @@ onBeforeMount(() => {
|
||||
<h3 class="text-lg font-semibold font-mono">
|
||||
UserProfileCe
|
||||
</h3>
|
||||
<unraid-user-profile :server="JSON.stringify(serverState)" />
|
||||
<header class="bg-beta py-4">
|
||||
<unraid-user-profile :server="JSON.stringify(serverState)" />
|
||||
</header>
|
||||
<hr class="border-black dark:border-white">
|
||||
<h3 class="text-lg font-semibold font-mono">
|
||||
DownloadApiLogsCe
|
||||
|
||||
@@ -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<ReleasesResponse | undefined> => {
|
||||
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,
|
||||
|
||||
@@ -96,6 +96,7 @@ export default <Partial<Config>>{
|
||||
'350px': '350px',
|
||||
'640px': '640px',
|
||||
'800px': '800px',
|
||||
'1024px': '1024px',
|
||||
},
|
||||
screens: {
|
||||
'2xs': '470px',
|
||||
|
||||
Reference in New Issue
Block a user