mirror of
https://github.com/unraid/api.git
synced 2026-01-02 14:40:01 -06:00
Compare commits
1 Commits
v4.27.1
...
feat/tools
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab1abc4ffe |
@@ -24,7 +24,7 @@ defineProps<{
|
||||
@click="replaceRenewStore.check"
|
||||
/>
|
||||
|
||||
<Badge v-else :variant="replaceStatusOutput.variant" :icon="replaceStatusOutput.icon" size="md">
|
||||
<Badge v-else :variant="replaceStatusOutput.variant" :icon="replaceStatusOutput.icon" size="md" class="self-center">
|
||||
{{ t(replaceStatusOutput.text ?? 'Unknown') }}
|
||||
</Badge>
|
||||
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { storeToRefs } from "pinia";
|
||||
import { computed, h } from "vue";
|
||||
|
||||
import { ArrowPathIcon, ArrowTopRightOnSquareIcon } from '@heroicons/vue/24/solid';
|
||||
import { BrandButton } from '@unraid/ui';
|
||||
import { DOCS_REGISTRATION_LICENSING } from '~/helpers/urls';
|
||||
import { ArrowPathIcon, ArrowTopRightOnSquareIcon } from "@heroicons/vue/24/solid";
|
||||
import { BrandButton, BrandLoading } from "@unraid/ui";
|
||||
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
import { DOCS_REGISTRATION_LICENSING } from "~/helpers/urls";
|
||||
|
||||
import useDateTimeHelper from '~/composables/dateTime';
|
||||
import { useReplaceRenewStore } from '~/store/replaceRenew';
|
||||
import { useServerStore } from '~/store/server';
|
||||
import type { ComposerTranslation } from "vue-i18n";
|
||||
|
||||
import useDateTimeHelper from "~/composables/dateTime";
|
||||
import { useReplaceRenewStore } from "~/store/replaceRenew";
|
||||
import { useServerStore } from "~/store/server";
|
||||
|
||||
export interface Props {
|
||||
t: ComposerTranslation;
|
||||
@@ -20,15 +22,29 @@ const props = defineProps<Props>();
|
||||
const replaceRenewStore = useReplaceRenewStore();
|
||||
const serverStore = useServerStore();
|
||||
|
||||
const { renewStatus } = storeToRefs(replaceRenewStore);
|
||||
const { dateTimeFormat, regExp, regUpdatesExpired, renewAction } = storeToRefs(serverStore);
|
||||
const { renewStatus, validationResponseTimestamp } = storeToRefs(replaceRenewStore);
|
||||
const { dateTimeFormat, regExp, regUpdatesExpired, renewAction } = storeToRefs(
|
||||
serverStore
|
||||
);
|
||||
|
||||
const reload = () => {
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
const { outputDateTimeReadableDiff: readableDiffRegExp, outputDateTimeFormatted: formattedRegExp } =
|
||||
useDateTimeHelper(dateTimeFormat.value, props.t, true, regExp.value);
|
||||
const {
|
||||
outputDateTimeReadableDiff: readableDiffRegExp,
|
||||
outputDateTimeFormatted: formattedRegExp,
|
||||
} = useDateTimeHelper(dateTimeFormat.value, props.t, true, regExp.value);
|
||||
|
||||
const {
|
||||
outputDateTimeReadableDiff: readableDiffValidationResponseTimestamp,
|
||||
outputDateTimeFormatted: formattedValidationResponseTimestamp,
|
||||
} = useDateTimeHelper(
|
||||
dateTimeFormat.value,
|
||||
props.t,
|
||||
false,
|
||||
validationResponseTimestamp.value ?? undefined
|
||||
);
|
||||
|
||||
const output = computed(() => {
|
||||
if (!regExp.value) {
|
||||
@@ -36,29 +52,68 @@ const output = computed(() => {
|
||||
}
|
||||
return {
|
||||
text: regUpdatesExpired.value
|
||||
? props.t('Ineligible for feature updates released after {0}', [formattedRegExp.value])
|
||||
: props.t('Eligible for free feature updates until {0}', [formattedRegExp.value]),
|
||||
? props.t("Ineligible for feature updates released after {0}", [
|
||||
formattedRegExp.value,
|
||||
])
|
||||
: props.t("Eligible for free feature updates until {0}", [formattedRegExp.value]),
|
||||
title: regUpdatesExpired.value
|
||||
? props.t('Ineligible as of {0}', [readableDiffRegExp.value])
|
||||
: props.t('Eligible for free feature updates for {0}', [readableDiffRegExp.value]),
|
||||
? props.t("Ineligible as of {0}", [readableDiffRegExp.value])
|
||||
: props.t("Eligible for free feature updates for {0}", [readableDiffRegExp.value]),
|
||||
};
|
||||
});
|
||||
|
||||
const showCheckTimestamp = computed(() => {
|
||||
return (
|
||||
validationResponseTimestamp.value && validationResponseTimestamp.value > regExp.value
|
||||
);
|
||||
});
|
||||
|
||||
const reloadPage = () => {
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
const BrandLoadingIcon = () => h(BrandLoading, { size: 'sm' });
|
||||
|
||||
const statusContent = computed(() => {
|
||||
switch (renewStatus.value) {
|
||||
case "installed":
|
||||
return {
|
||||
text: props.t(
|
||||
"Your license key was automatically renewed and installed. Reload the page to see updated details."
|
||||
),
|
||||
action: {
|
||||
icon: ArrowPathIcon,
|
||||
title: "Reload Page",
|
||||
onClick: reloadPage,
|
||||
},
|
||||
};
|
||||
case "checking":
|
||||
return {
|
||||
component: BrandLoadingIcon,
|
||||
text: props.t("Checking for extended license..."),
|
||||
};
|
||||
case "error":
|
||||
return { text: props.t("Error checking for extended license.") };
|
||||
case "ready":
|
||||
return {
|
||||
text: showCheckTimestamp.value ? `Last checked: ${formattedValidationResponseTimestamp.value}` : null,
|
||||
action: {
|
||||
icon: ArrowPathIcon,
|
||||
title: "Check Again for Extended License",
|
||||
onClick: () => replaceRenewStore.check(true), // consider debouncing this
|
||||
},
|
||||
};
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="output" class="flex flex-col gap-8px">
|
||||
<RegistrationUpdateExpiration :t="t" />
|
||||
|
||||
<p class="text-14px opacity-90">
|
||||
<template v-if="renewStatus === 'installed'">
|
||||
{{
|
||||
t(
|
||||
'Your license key was automatically renewed and installed. Reload the page to see updated details.'
|
||||
)
|
||||
}}
|
||||
</template>
|
||||
</p>
|
||||
<div class="flex flex-wrap items-start justify-between gap-8px">
|
||||
<div class="flex flex-wrap items-center justify-between gap-8px">
|
||||
<BrandButton
|
||||
v-if="renewStatus === 'installed'"
|
||||
:icon="ArrowPathIcon"
|
||||
@@ -87,6 +142,21 @@ const output = computed(() => {
|
||||
:text="t('Learn More')"
|
||||
class="text-14px"
|
||||
/>
|
||||
|
||||
<p class="text-14px opacity-90 w-full flex flex-wrap gap-8px items-center">
|
||||
<template v-if="statusContent">
|
||||
<BrandButton
|
||||
v-if="statusContent.action"
|
||||
variant="underline"
|
||||
:icon="statusContent.action.icon"
|
||||
:title="statusContent.action.title"
|
||||
size="12px"
|
||||
@click="statusContent.action.onClick"
|
||||
/>
|
||||
<component :is="statusContent.component" v-if="statusContent.component" />
|
||||
<span v-if="statusContent.text">{{ statusContent.text }}</span>
|
||||
</template>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { storeToRefs } from "pinia";
|
||||
|
||||
import { TransitionRoot } from '@headlessui/vue';
|
||||
import { TransitionRoot } from "@headlessui/vue";
|
||||
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
import type { ComposerTranslation } from "vue-i18n";
|
||||
|
||||
import { useDropdownStore } from '~/store/dropdown';
|
||||
import { useServerStore } from '~/store/server';
|
||||
import { useDropdownStore } from "~/store/dropdown";
|
||||
import { useServerStore } from "~/store/server";
|
||||
|
||||
defineProps<{ t: ComposerTranslation }>();
|
||||
|
||||
@@ -15,7 +15,7 @@ const dropdownStore = useDropdownStore();
|
||||
const { dropdownVisible } = storeToRefs(dropdownStore);
|
||||
const { state } = storeToRefs(useServerStore());
|
||||
|
||||
const showLaunchpad = computed(() => state.value === 'ENOKEYFILE');
|
||||
const showLaunchpad = computed(() => state.value === "ENOKEYFILE");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -28,9 +28,7 @@ const showLaunchpad = computed(() => state.value === 'ENOKEYFILE');
|
||||
leave-from="opacity-100"
|
||||
leave-to="opacity-0 translate-y-[16px]"
|
||||
>
|
||||
<UpcDropdownWrapper
|
||||
class="DropdownWrapper_blip text-foreground absolute z-30 top-full right-0 transition-all"
|
||||
>
|
||||
<UpcDropdownWrapper>
|
||||
<UpcDropdownLaunchpad v-if="showLaunchpad" :t="t" />
|
||||
<UpcDropdownContent v-else :t="t" />
|
||||
</UpcDropdownWrapper>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav class="flex flex-col gap-y-8px p-8px bg-popover rounded-lg shadow-xl shadow-orange/10">
|
||||
<nav class="text-foreground absolute z-30 top-full right-0 flex flex-col gap-y-8px p-8px bg-popover rounded-lg shadow-xl shadow-orange/10">
|
||||
<slot />
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
@@ -17,16 +17,16 @@ import { BrandLoading } from '@unraid/ui';
|
||||
|
||||
import type { BadgeProps } from '@unraid/ui';
|
||||
import type {
|
||||
// type KeyLatestResponse,
|
||||
KeyLatestResponse,
|
||||
ValidateGuidResponse,
|
||||
} from '~/composables/services/keyServer';
|
||||
import type { WretchError } from 'wretch';
|
||||
|
||||
import {
|
||||
// keyLatest,
|
||||
keyLatest,
|
||||
validateGuid,
|
||||
} from '~/composables/services/keyServer';
|
||||
// import { useCallbackStore } from '~/store/callbackActions';
|
||||
import { useCallbackStore } from '~/store/callbackActions';
|
||||
import { useServerStore } from '~/store/server';
|
||||
|
||||
/**
|
||||
@@ -44,12 +44,12 @@ interface CachedValidationResponse extends ValidateGuidResponse {
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
const BrandLoadingIcon = () => h(BrandLoading, { variant: 'white' });
|
||||
const BrandLoadingIcon = () => h(BrandLoading, { size: 'sm' });
|
||||
|
||||
export const REPLACE_CHECK_LOCAL_STORAGE_KEY = 'unraidReplaceCheck';
|
||||
|
||||
export const useReplaceRenewStore = defineStore('replaceRenewCheck', () => {
|
||||
// const callbackStore = useCallbackStore();
|
||||
const callbackStore = useCallbackStore();
|
||||
const serverStore = useServerStore();
|
||||
|
||||
const guid = computed(() => serverStore.guid);
|
||||
@@ -156,6 +156,8 @@ export const useReplaceRenewStore = defineStore('replaceRenewCheck', () => {
|
||||
: undefined
|
||||
);
|
||||
|
||||
const validationResponseTimestamp = computed<number | null>(() => validationResponse.value?.timestamp ?? null);
|
||||
|
||||
const purgeValidationResponse = async () => {
|
||||
validationResponse.value = undefined;
|
||||
await sessionStorage.removeItem(REPLACE_CHECK_LOCAL_STORAGE_KEY);
|
||||
@@ -190,6 +192,8 @@ export const useReplaceRenewStore = defineStore('replaceRenewCheck', () => {
|
||||
error.value = { name: 'Error', message: 'Keyfile required to check replacement status' };
|
||||
}
|
||||
|
||||
console.log('[ReplaceCheck.check]');
|
||||
|
||||
try {
|
||||
if (skipCache) {
|
||||
await purgeValidationResponse();
|
||||
@@ -200,6 +204,7 @@ export const useReplaceRenewStore = defineStore('replaceRenewCheck', () => {
|
||||
|
||||
setKeyLinked('checking');
|
||||
setReplaceStatus('checking');
|
||||
setRenewStatus('checking');
|
||||
error.value = null;
|
||||
/**
|
||||
* If the session already has a validation response, use that instead of making a new request
|
||||
@@ -222,35 +227,38 @@ export const useReplaceRenewStore = defineStore('replaceRenewCheck', () => {
|
||||
(replaceStatus.value === 'eligible' || replaceStatus.value === 'ineligible') &&
|
||||
!validationResponse.value
|
||||
) {
|
||||
validationResponse.value = {
|
||||
key: keyfileShort.value,
|
||||
timestamp: Date.now(),
|
||||
...response,
|
||||
};
|
||||
sessionStorage.setItem(
|
||||
REPLACE_CHECK_LOCAL_STORAGE_KEY,
|
||||
JSON.stringify({
|
||||
key: keyfileShort.value,
|
||||
timestamp: Date.now(),
|
||||
...response,
|
||||
})
|
||||
JSON.stringify(validationResponse.value)
|
||||
);
|
||||
}
|
||||
|
||||
// if (response?.hasNewerKeyfile) {
|
||||
// setRenewStatus('checking');
|
||||
if (!response?.hasNewerKeyfile) {
|
||||
setRenewStatus('ready');
|
||||
} else if (response?.hasNewerKeyfile) {
|
||||
setRenewStatus('checking');
|
||||
|
||||
// const keyLatestResponse: KeyLatestResponse = await keyLatest({
|
||||
// keyfile: keyfile.value,
|
||||
// });
|
||||
const keyLatestResponse: KeyLatestResponse = await keyLatest({
|
||||
keyfile: keyfile.value,
|
||||
});
|
||||
|
||||
// if (keyLatestResponse?.license) {
|
||||
// callbackStore.send(
|
||||
// window.location.href,
|
||||
// [{
|
||||
// keyUrl: keyLatestResponse.license,
|
||||
// type: 'renew',
|
||||
// }],
|
||||
// undefined,
|
||||
// 'forUpc',
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
if (keyLatestResponse?.license) {
|
||||
callbackStore.send(
|
||||
window.location.href,
|
||||
[{
|
||||
keyUrl: keyLatestResponse.license,
|
||||
type: 'renew',
|
||||
}],
|
||||
undefined,
|
||||
'forUpc',
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
const catchError = err as WretchError;
|
||||
setReplaceStatus('error');
|
||||
@@ -266,6 +274,7 @@ export const useReplaceRenewStore = defineStore('replaceRenewCheck', () => {
|
||||
renewStatus,
|
||||
replaceStatus,
|
||||
replaceStatusOutput,
|
||||
validationResponseTimestamp,
|
||||
// actions
|
||||
check,
|
||||
purgeValidationResponse,
|
||||
|
||||
Reference in New Issue
Block a user