mirror of
https://github.com/unraid/api.git
synced 2026-01-06 08:39:54 -06:00
feat: update os notifications enabled usage + link to enable & more options to account app
This commit is contained in:
@@ -49,6 +49,7 @@ class ServerState
|
||||
private $caseModel = '';
|
||||
private $keyfileBase64UrlSafe = '';
|
||||
private $updateOsCheck;
|
||||
private $updateOsNotificationsEnabled = false;
|
||||
private $updateOsResponse;
|
||||
private $updateOsIgnoredReleases = [];
|
||||
|
||||
@@ -168,8 +169,9 @@ class ServerState
|
||||
}
|
||||
|
||||
$this->updateOsCheck = new UnraidOsCheck();
|
||||
$this->updateOsResponse = $this->updateOsCheck->getUnraidOSCheckResult();
|
||||
$this->updateOsIgnoredReleases = $this->updateOsCheck->getIgnoredReleases();
|
||||
$this->updateOsNotificationsEnabled = !empty(@$this->getWebguiGlobal('notify')['unraidos']);
|
||||
$this->updateOsResponse = $this->updateOsCheck->getUnraidOSCheckResult();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -251,14 +253,18 @@ class ServerState
|
||||
$serverState['combinedKnownOrigins'] = $this->combinedKnownOrigins;
|
||||
}
|
||||
|
||||
if ($this->updateOsResponse) {
|
||||
$serverState['updateOsResponse'] = $this->updateOsResponse;
|
||||
}
|
||||
|
||||
if ($this->updateOsIgnoredReleases) {
|
||||
$serverState['updateOsIgnoredReleases'] = $this->updateOsIgnoredReleases;
|
||||
}
|
||||
|
||||
if ($this->updateOsNotificationsEnabled) {
|
||||
$serverState['updateOsNotificationsEnabled'] = $this->updateOsNotificationsEnabled;
|
||||
}
|
||||
|
||||
if ($this->updateOsResponse) {
|
||||
$serverState['updateOsResponse'] = $this->updateOsResponse;
|
||||
}
|
||||
|
||||
return $serverState;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
<script lang="ts" setup>
|
||||
import { ArrowTopRightOnSquareIcon, EyeIcon, IdentificationIcon, KeyIcon, XMarkIcon } from '@heroicons/vue/24/solid';
|
||||
import {
|
||||
ArrowTopRightOnSquareIcon,
|
||||
CogIcon,
|
||||
EyeIcon,
|
||||
IdentificationIcon,
|
||||
KeyIcon,
|
||||
XMarkIcon,
|
||||
} from '@heroicons/vue/24/solid';
|
||||
import { Switch, SwitchGroup, SwitchLabel } from '@headlessui/vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
@@ -27,11 +34,12 @@ const updateOsStore = useUpdateOsStore();
|
||||
const updateOsChangelogStore = useUpdateOsChangelogStore();
|
||||
|
||||
const {
|
||||
dateTimeFormat,
|
||||
updateOsResponse,
|
||||
updateOsIgnoredReleases,
|
||||
regExp,
|
||||
regUpdatesExpired,
|
||||
dateTimeFormat,
|
||||
updateOsIgnoredReleases,
|
||||
updateOsNotificationsEnabled,
|
||||
updateOsResponse,
|
||||
} = storeToRefs(serverStore);
|
||||
const {
|
||||
available,
|
||||
@@ -49,6 +57,12 @@ const {
|
||||
// @todo - when true change primary action button to be close and hide secondary button
|
||||
const ignoreThisRelease = ref(false);
|
||||
|
||||
const notificationsSettings = computed(() => {
|
||||
return !updateOsNotificationsEnabled.value
|
||||
? props.t('Go to Settings > Notifications to enable automatic OS update notifications for future releases.')
|
||||
: undefined;
|
||||
});
|
||||
|
||||
interface ModalCopy {
|
||||
title: string;
|
||||
description?: string;
|
||||
@@ -84,15 +98,35 @@ const modalCopy = computed((): ModalCopy | null => {
|
||||
description: description ? `<p>${formattedReleaseDate}</p><p>${description}</p>` : formattedReleaseDate,
|
||||
};
|
||||
} else if (!available.value && !availableWithRenewal.value) {
|
||||
/** @todo - conditionally show this description for when the setting isn't set */
|
||||
return {
|
||||
title: props.t('Unraid OS is up-to-date'),
|
||||
description: props.t('Go to Settings > Notifications to enable OS update notifications for future releases.'),
|
||||
description: notificationsSettings.value ?? undefined,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const showNotificationsSettingsLink = computed(() => {
|
||||
return !updateOsNotificationsEnabled.value && !available.value && !availableWithRenewal.value;
|
||||
});
|
||||
|
||||
const extraLinks = computed((): ButtonProps[] | null => {
|
||||
if (!showNotificationsSettingsLink.value) { return null; }
|
||||
|
||||
const buttons: ButtonProps[] = [];
|
||||
|
||||
if (showNotificationsSettingsLink.value) {
|
||||
buttons.push({
|
||||
btnStyle: 'outline',
|
||||
href: '/Settings/Notifications',
|
||||
icon: CogIcon,
|
||||
text: props.t('Enable update notifications'),
|
||||
});
|
||||
}
|
||||
|
||||
return buttons;
|
||||
});
|
||||
|
||||
const actionButtons = computed((): ButtonProps[] | null => {
|
||||
// update not available or no action buttons default closing
|
||||
if (!available.value || ignoreThisRelease.value) { return null; }
|
||||
@@ -173,6 +207,13 @@ onBeforeMount(() => {
|
||||
setUserFormattedReleaseDate();
|
||||
}
|
||||
});
|
||||
|
||||
const modalWidth = computed(() => {
|
||||
if (availableWithRenewal.value) { // wider since we'll have four buttons
|
||||
return 'max-w-800px';
|
||||
}
|
||||
return 'max-w-640px';
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -182,41 +223,58 @@ onBeforeMount(() => {
|
||||
:title="modalCopy?.title"
|
||||
:description="modalCopy?.description"
|
||||
:show-close-x="!checkForUpdatesLoading"
|
||||
max-width="max-w-640px"
|
||||
:max-width="modalWidth"
|
||||
@close="close"
|
||||
>
|
||||
<template v-if="renderMainSlot" #main>
|
||||
<BrandLoading v-if="checkForUpdatesLoading" class="w-[150px] mx-auto" />
|
||||
<div v-else-if="available || availableWithRenewal" class="mx-auto">
|
||||
<SwitchGroup>
|
||||
<div class="flex justify-center items-center gap-8px p-8px rounded">
|
||||
<Switch
|
||||
v-model="ignoreThisRelease"
|
||||
:class="ignoreThisRelease ? 'bg-gradient-to-r from-unraid-red to-orange' : 'bg-transparent'"
|
||||
class="relative inline-flex h-24px w-[48px] items-center rounded-full overflow-hidden"
|
||||
>
|
||||
<span v-show="!ignoreThisRelease" class="absolute z-0 inset-0 opacity-10 bg-beta" />
|
||||
<span
|
||||
:class="ignoreThisRelease ? 'translate-x-[26px]' : 'translate-x-[2px]'"
|
||||
class="inline-block h-20px w-20px transform rounded-full bg-white transition"
|
||||
/>
|
||||
</Switch>
|
||||
<SwitchLabel class="text-16px">
|
||||
{{ t('Ignore this release until next reboot') }}
|
||||
</SwitchLabel>
|
||||
</div>
|
||||
</SwitchGroup>
|
||||
</div>
|
||||
<div v-else-if="updateOsIgnoredReleases.length > 0" class="w-full flex flex-col gap-8px">
|
||||
<h3 class="text-left text-16px font-semibold italic">
|
||||
{{ t('Ignored Releases') }}
|
||||
</h3>
|
||||
<UpdateOsIgnoredRelease
|
||||
v-for="ignoredRelease in updateOsIgnoredReleases"
|
||||
:key="ignoredRelease"
|
||||
:label="ignoredRelease"
|
||||
:t="t"
|
||||
/>
|
||||
<div v-else class="flex flex-col gap-y-16px">
|
||||
<div v-if="extraLinks" class="flex flex-col xs:flex-row justify-center gap-8px">
|
||||
<BrandButton
|
||||
v-for="item in extraLinks"
|
||||
:key="item.text"
|
||||
:btn-style="item.btnStyle ?? undefined"
|
||||
:href="item.href ?? undefined"
|
||||
:icon="item.icon"
|
||||
:icon-right="item.iconRight"
|
||||
:icon-right-hover-display="item.iconRightHoverDisplay"
|
||||
:text="t(item.text)"
|
||||
:title="item.title ? t(item.title) : undefined"
|
||||
@click="item.click ? item.click() : undefined"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="available || availableWithRenewal" class="mx-auto">
|
||||
<SwitchGroup>
|
||||
<div class="flex justify-center items-center gap-8px p-8px rounded">
|
||||
<Switch
|
||||
v-model="ignoreThisRelease"
|
||||
:class="ignoreThisRelease ? 'bg-gradient-to-r from-unraid-red to-orange' : 'bg-transparent'"
|
||||
class="relative inline-flex h-24px w-[48px] items-center rounded-full overflow-hidden"
|
||||
>
|
||||
<span v-show="!ignoreThisRelease" class="absolute z-0 inset-0 opacity-10 bg-beta" />
|
||||
<span
|
||||
:class="ignoreThisRelease ? 'translate-x-[26px]' : 'translate-x-[2px]'"
|
||||
class="inline-block h-20px w-20px transform rounded-full bg-white transition"
|
||||
/>
|
||||
</Switch>
|
||||
<SwitchLabel class="text-16px">
|
||||
{{ t('Ignore this release until next reboot') }}
|
||||
</SwitchLabel>
|
||||
</div>
|
||||
</SwitchGroup>
|
||||
</div>
|
||||
<div v-else-if="updateOsIgnoredReleases.length > 0" class="w-full max-w-640px mx-auto flex flex-col gap-8px">
|
||||
<h3 class="text-left text-16px font-semibold italic">
|
||||
{{ t('Ignored Releases') }}
|
||||
</h3>
|
||||
<UpdateOsIgnoredRelease
|
||||
v-for="ignoredRelease in updateOsIgnoredReleases"
|
||||
:key="ignoredRelease"
|
||||
:label="ignoredRelease"
|
||||
:t="t"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -228,12 +286,20 @@ onBeforeMount(() => {
|
||||
'justify-center': !actionButtons,
|
||||
}"
|
||||
>
|
||||
<BrandButton
|
||||
btn-style="underline-hover-red"
|
||||
:icon="XMarkIcon"
|
||||
:text="t('Close')"
|
||||
@click="close"
|
||||
/>
|
||||
<div class="flex flex-col xs:flex-row justify-start gap-8px">
|
||||
<BrandButton
|
||||
btn-style="underline"
|
||||
:icon="ArrowTopRightOnSquareIcon"
|
||||
:text="t('More options')"
|
||||
@click="accountStore.updateOs()"
|
||||
/>
|
||||
<BrandButton
|
||||
btn-style="underline-hover-red"
|
||||
:icon="XMarkIcon"
|
||||
:text="t('Close')"
|
||||
@click="close"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="actionButtons" class="flex flex-col xs:flex-row justify-end gap-8px">
|
||||
<BrandButton
|
||||
v-for="item in actionButtons"
|
||||
|
||||
@@ -353,5 +353,9 @@
|
||||
"Continue": "Continue",
|
||||
"Verify to Update": "Verify to Update",
|
||||
"Release date {0}": "Release date {0}",
|
||||
"Update Released": "Update Released"
|
||||
"Update Released": "Update Released",
|
||||
"Go to Tools > Update OS for more options.": "Go to Tools > Update OS for more options.",
|
||||
"Go to Settings > Notifications to enable automatic OS update notifications for future releases.": "Go to Settings > Notifications to enable automatic OS update notifications for future releases.",
|
||||
"More options": "More options",
|
||||
"Extend Key": "Extend Key"
|
||||
}
|
||||
|
||||
@@ -118,6 +118,7 @@ export const useServerStore = defineStore('server', () => {
|
||||
});
|
||||
const updateOsResponse = ref<ServerUpdateOsResponse>();
|
||||
const updateOsIgnoredReleases = ref<string[]>([]);
|
||||
const updateOsNotificationsEnabled = ref<boolean>(false);
|
||||
const uptime = ref<number>(0);
|
||||
const username = ref<string>(''); // @todo potentially move to a user store
|
||||
const wanFQDN = ref<string>('');
|
||||
@@ -820,8 +821,9 @@ export const useServerStore = defineStore('server', () => {
|
||||
if (typeof data?.site !== 'undefined') { site.value = data.site; }
|
||||
if (typeof data?.state !== 'undefined') { state.value = data.state; }
|
||||
if (typeof data?.theme !== 'undefined') { theme.value = data.theme; }
|
||||
if (typeof data?.updateOsResponse !== 'undefined') { updateOsResponse.value = data.updateOsResponse; }
|
||||
if (typeof data?.updateOsIgnoredReleases !== 'undefined') { updateOsIgnoredReleases.value = data.updateOsIgnoredReleases; }
|
||||
if (typeof data?.updateOsNotificationsEnabled !== 'undefined') { updateOsNotificationsEnabled.value = data.updateOsNotificationsEnabled; }
|
||||
if (typeof data?.updateOsResponse !== 'undefined') { updateOsResponse.value = data.updateOsResponse; }
|
||||
if (typeof data?.uptime !== 'undefined') { uptime.value = data.uptime; }
|
||||
if (typeof data?.username !== 'undefined') { username.value = data.username; }
|
||||
if (typeof data?.wanFQDN !== 'undefined') { wanFQDN.value = data.wanFQDN; }
|
||||
@@ -1031,6 +1033,7 @@ export const useServerStore = defineStore('server', () => {
|
||||
state,
|
||||
theme,
|
||||
updateOsIgnoredReleases,
|
||||
updateOsNotificationsEnabled,
|
||||
updateOsResponse,
|
||||
uptime,
|
||||
username,
|
||||
|
||||
@@ -102,8 +102,9 @@ export interface Server {
|
||||
site?: string;
|
||||
state?: ServerState;
|
||||
theme?: Theme | undefined;
|
||||
updateOsResponse?: ServerUpdateOsResponse;
|
||||
updateOsIgnoredReleases?: string[];
|
||||
updateOsNotificationsEnabled?: boolean;
|
||||
updateOsResponse?: ServerUpdateOsResponse;
|
||||
uptime?: number;
|
||||
username?: string;
|
||||
wanFQDN?: string;
|
||||
|
||||
Reference in New Issue
Block a user