refactor: WIP registration update expiration

This commit is contained in:
Zack Spear
2023-09-26 14:05:22 -07:00
committed by Zack Spear
parent 17a5767108
commit 90303689db
13 changed files with 205 additions and 102 deletions

View File

@@ -60,9 +60,11 @@ $serverState = [
"osVersion" => $var['version'],
"protocol" => $_SERVER['REQUEST_SCHEME'],
"regGen" => (int)$var['regGen'],
"regGuid" => $var['regGUID'],
"regTo" => $var['regTo'],
"regTm" => $var['regTm'] * 1000, // JS expects milliseconds
"regGuid" => @$var['regGUID'] ?? '',
"regTo" => @$var['regTo'] ?? '',
"regTm" => $var['regTm'] ? @$var['regTm'] * 1000 : '', // JS expects milliseconds
"regTy" => @$var['regTy'] ?? '',
"regUpdExpAt" => $var['regUpdExpAt'] ? @$var['regUpdExpAt'] * 1000 : '', // JS expects milliseconds
"registered" => $registered,
"registeredTime" => $myservers['remote']['regWizTime'] ?? '',
"site" => $_SERVER['REQUEST_SCHEME'] . "://" . $_SERVER['HTTP_HOST'],

View File

@@ -29,12 +29,14 @@ const randomGuid = `1111-1111-${makeid(4)}-123412341234`; // this guid is regist
// EBLACKLISTED1
// EBLACKLISTED2
// ENOCONN
const state: ServerState = 'EEXPIRED';
const state: ServerState = 'TRIAL';
const regTy = 'Trial';
const uptime = Date.now() - 60 * 60 * 1000; // 1 hour ago
const oneHourFromNow = Date.now() + 60 * 60 * 1000; // 1 hour from now
let expireTime = 0;
if (state === 'TRIAL') { expireTime = Date.now() + 60 * 60 * 1000; } // in 1 hour
if (state === 'EEXPIRED') { expireTime = uptime; } // 1 hour ago
if (state === 'TRIAL') { expireTime = oneHourFromNow; } // in 1 hour
else if (state === 'EEXPIRED') { expireTime = uptime; } // 1 hour ago
export const serverState: Server = {
apiKey: 'unupc_fab6ff6ffe51040595c6d9ffb63a353ba16cc2ad7d93f813a2e80a5810',
@@ -63,6 +65,9 @@ export const serverState: Server = {
regGen: 0,
regTm: uptime,
regTo: 'Zack Spear',
regTy,
// regUpdExpAt: oneHourFromNow,
regUpdExpAt: uptime,
// "regGuid": "0781-5583-8355-81071A2B0211",
site: 'http://localhost:4321',
state,

View File

@@ -51,8 +51,8 @@ const classes = computed(() => {
:class="classes"
@click="click ?? $emit('click')"
>
<component :is="icon" v-if="icon" class="flex-shrink-0 w-14px" />
<component v-if="icon" :is="icon" class="flex-shrink-0 w-14px" />
{{ text }}
<component :is="iconRight" v-if="iconRight" class="flex-shrink-0 w-14px" />
<component v-if="iconRight" :is="iconRight" class="flex-shrink-0 w-14px" />
</component>
</template>

View File

@@ -14,7 +14,7 @@ const { keyActions } = storeToRefs(useServerStore());
<ul v-if="keyActions" class="flex flex-col gap-y-8px">
<li v-for="action in keyActions" :key="action.name">
<BrandButton
class="w-full max-w-300px"
class="w-full sm:max-w-300px"
:disabled="action?.disabled"
:external="action?.external"
:href="action?.href"

View File

@@ -28,6 +28,9 @@ import { useServerStore } from '~/store/server';
import 'tailwindcss/tailwind.css';
import '~/assets/main.css';
import KeyActions from '~/components/KeyActions.vue';
import RegistrationUpgradeExpiration from '~/components/Registration/UpgradeExpiration.vue';
const { t } = useI18n();
export interface Props {
@@ -45,71 +48,59 @@ const {
guid,
flashVendor,
flashProduct,
keyActions,
keyfile,
regGuid,
regTm,
regTo,
regTy,
regUpdExpAt,
regUpdExpired,
state,
stateData,
stateDataError,
} = storeToRefs(serverStore);
const devicesAvailable = computed(() => {
switch(regTy.value) {
case 'Basic':
return 6;
case 'Plus':
return 12;
case 'Pro':
case 'Trial':
return 'unlimited';
}
});
const items = computed(() => {
return [
...(regTo.value
? [
{
label: 'Registered to',
text: regTo.value,
},
]
: []
),
...(regTo.value
? [
{
label: 'Registered on',
text: dayjs(regTm.value).format('YYYY-MM-DD HH:mm'),
},
]
: []
),
...(regTy.value ? [{ label: t('License key type'), text: regTy.value }] : []),
...(regTo.value ? [ { label: t('Registered to'), text: regTo.value }] : []),
...(regTo.value ? [{ label: t('Registered on'), text: dayjs(regTm.value).format('YYYY-MM-DD HH:mm')}] : []),
/**
* @todo factor in grandfathered users and display a different message
*/
...(regTo.value
? [
{
label: 'Updates Expire',
text: dayjs(regTm.value).format('YYYY-MM-DD HH:mm'),
},
]
: []
),
...(state.value === 'EGUID'
? [
{
label: 'Registered GUID',
text: regGuid.value,
},
]
: []
),
{
label: 'Flash GUID',
text: guid.value,
},
{
label: 'Flash Vendor',
text: flashVendor.value,
},
{
label: 'Flash Product',
text: flashProduct.value,
},
{
label: 'Attached Storage Devices',
text: deviceCount.value,
},
...(regUpdExpAt.value
? [{
error: regUpdExpired.value,
label: t('OS Update Eligibility'),
component: RegistrationUpgradeExpiration,
componentProps: { t: t },
}]
: []),
...(state.value === 'EGUID' ? [{ label: t('Registered GUID'), text: regGuid.value }] : [] ),
{ label: t('Flash GUID'), text: guid.value },
{ label: t('Flash Vendor'), text: flashVendor.value },
{ label: t('Flash Product'), text: flashProduct.value },
{ label: t('Attached Storage Devices'), text: t('{0} out of {1} devices', [deviceCount.value, devicesAvailable.value]) },
...(regUpdExpAt.value
? [{
label: t('License key actions'),
component: KeyActions,
componentProps: { t: t },
}]
: []),
];
});
</script>
@@ -133,22 +124,21 @@ const items = computed(() => {
v-html="stateData.message"
class="prose text-16px leading-relaxed whitespace-normal opacity-75"></div>
</header>
<section>
<dl>
<RegistrationItem
v-for="item in items"
:key="item.label"
:label="item.label"
:text="item.text"
:t="t" />
<div class="p-16px sm:px-20px sm:grid sm:grid-cols-3 sm:gap-16px">
<dt class="text-16px font-medium leading-normal">&nbsp;</dt>
<dd class="mt-2 text-16px sm:col-span-2 sm:mt-0">
<KeyActions :t="t" />
</dd>
</div>
</dl>
</section>
<dl>
<RegistrationItem
v-for="item in items"
:key="item.label"
:component="item?.component"
:component-props="item?.componentProps"
:error="item.error ?? false"
:label="item.label"
:text="item.text"
>
<template v-if="item.component" #right>
<component :is="item.component" v-bind="item.componentProps" />
</template>
</RegistrationItem>
</dl>
</div>
</UiCardWrapper>
</UiPageContainer>

View File

@@ -1,13 +1,15 @@
<script setup lang="ts">
import { ShieldExclamationIcon } from '@heroicons/vue/24/solid';
import { storeToRefs } from 'pinia';
import { useThemeStore } from '~/store/theme';
export interface Props {
error?: boolean;
label?: string;
text?: number | string | undefined;
t: any;
}
withDefaults(defineProps<Props>(), {
const props = withDefaults(defineProps<Props>(), {
error: false,
label: '',
text: '',
});
@@ -17,15 +19,28 @@ const { darkMode } = storeToRefs(useThemeStore());
const evenBgColor = computed(() => {
return darkMode.value ? 'even:bg-grey-darkest' : 'even:bg-black/5';
});
onMounted(() => {
console.debug('[Item.onMounted]', props);
})
</script>
<template>
<div :class="evenBgColor" class="text-16px p-16px sm:px-20px sm:grid sm:grid-cols-3 sm:gap-16px">
<dt class="font-semibold">{{ t(label) }}</dt>
<div
:class="[
!error && evenBgColor,
error && 'text-white bg-unraid-red',
]"
class="text-16px p-16px sm:px-20px sm:grid sm:grid-cols-3 sm:gap-16px items-start"
>
<dt class="font-semibold flex flex-row justify-start items-center gap-x-8px">
<ShieldExclamationIcon v-if="error" class="w-16px h-16px fill-current" />
<span>{{ label }}</span>
</dt>
<dd class="mt-4px leading-normal sm:col-span-2 sm:mt-0">
<span v-if="text" class="opacity-75">{{ text }}</span>
<template v-else-if="$slots['text']">
<slot name="text"></slot>
<span v-if="text" class="opacity-75 select-all">{{ text }}</span>
<template v-if="$slots['right']">
<slot name="right"></slot>
</template>
</dd>
</div>

View File

@@ -0,0 +1,60 @@
<script setup lang="ts">
import { storeToRefs } from 'pinia';
import useTimeHelper from '~/composables/time';
import { useServerStore } from '~/store/server';
export interface Props {
t: any;
}
const props = defineProps<Props>();
const { buildStringFromValues, dateDiff, formatDate } = useTimeHelper(props.t);
const serverStore = useServerStore();
const { regTy, regUpdExpAt, regUpdExpired, state } = storeToRefs(serverStore);
const parsedTime = ref<string>('');
const formattedTime = computed<string>(() => formatDate(regUpdExpAt.value));
const output = computed(() => {
if (!regUpdExpAt.value) {
return undefined;
}
return {
title: regUpdExpired.value
? props.t('Expired at {0}', [formattedTime.value])
: props.t('Expires at {0}', [formattedTime.value]),
text: regUpdExpired.value
? props.t('Expired {0}', [parsedTime.value])
: props.t('Expires in {0}', [parsedTime.value]),
};
});
const runDiff = () => {
parsedTime.value = buildStringFromValues(dateDiff((regUpdExpAt.value).toString(), false));
};
let interval: string | number | NodeJS.Timeout | undefined;
onBeforeMount(() => {
console.debug('[UpgradeExpiration.onBeforeMount]', props);
runDiff();
interval = setInterval(() => {
runDiff();
}, 1000);
});
onBeforeUnmount(() => {
clearInterval(interval);
});
</script>
<template>
<p
v-if="output"
:title="output.title"
>
{{ output.text }}
</p>
</template>

View File

@@ -1,4 +1,5 @@
<script lang="ts" setup>
import { TransitionRoot } from '@headlessui/vue';
import {
ArrowTopRightOnSquareIcon,
ArrowUturnDownIcon,
@@ -21,8 +22,6 @@ const props = defineProps<{
version: string;
}>();
const updateOsActionsStore = useUpdateOsActionsStore();
const visible = ref(false);
const toggleVisible = () => {
visible.value = !visible.value;
@@ -40,13 +39,7 @@ const downgradeButton = ref<UserProfileLink | undefined>({
<template>
<UiCardWrapper :increased-padding="true">
<div
class="flex flex-col sm:flex-row sm:justify-between gap-20px sm:gap-24px"
:class="{
'sm:items-center': !visible,
'sm:items-start': visible,
}"
>
<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-normal flex flex-row items-center gap-8px">
<ArrowUturnDownIcon class="w-20px shrink-0" />
@@ -61,6 +54,7 @@ const downgradeButton = ref<UserProfileLink | undefined>({
v-if="!visible"
@click="toggleVisible"
:btn-style="'outline'"
:icon="InformationCircleIcon"
:text="t('Learn More')" />
<div v-else-if="downgradeButton" class="flex flex-col sm:flex-shrink-0 items-center gap-16px">
@@ -71,7 +65,9 @@ const downgradeButton = ref<UserProfileLink | undefined>({
:icon="LifebuoyIcon"
:icon-right="ArrowTopRightOnSquareIcon"
:text="t('Open a bug report')" />
<p class="opacity-75">{{ t('Original release date {0}', [releaseDate]) }}</p>
<p v-if="releaseDate" class="opacity-75">{{ t('Original release date {0}', [releaseDate]) }}</p>
<BrandButton
@click="downgradeButton?.click"
btn-style="outline"

View File

@@ -26,10 +26,6 @@ const updateOsActionsStore = useUpdateOsActionsStore();
const { guid, keyfile, osVersion } = storeToRefs(serverStore);
const { available, parsedReleaseTimestamp } = storeToRefs(updateOsStore);
const { rebootType, rebootTypeText } = storeToRefs(updateOsActionsStore);
watchEffect(() => {
console.debug('[rebootType]', rebootType.value, rebootTypeText.value);
});
</script>
<template>

View File

@@ -8,6 +8,7 @@ import {
ArrowPathIcon,
ArrowTopRightOnSquareIcon,
BellAlertIcon,
WrenchScrewdriverIcon,
} from '@heroicons/vue/24/solid';
import dayjs from 'dayjs';
import { storeToRefs } from 'pinia';
@@ -70,7 +71,8 @@ watchEffect(() => {
<BrandButton
v-if="ineligible"
href="/Tools/Registration"
:text="t('Go to Tools > Registration')"
:icon="WrenchScrewdriverIcon"
:text="t('Go to Tools > Registration to fix')"
/>
<BrandButton
v-else-if="available && updateButton"

View File

@@ -241,6 +241,24 @@
"Go to Tools > Update": "Go to Tools > Update",
"A valid keyfile and USB Flash boot device are required to check for updates.": "A valid keyfile and USB Flash boot device are required to check for updates.",
"Please fix any errors and try again.": "Please fix any errors and try again.",
"Go to Tools > Registration": "Go to Tools > Registration",
"Original release date {0}": "Original release date {0}"
"Go to Tools > Registration to fix": "Go to Tools > Registration to fix",
"Original release date {0}": "Original release date {0}",
"Registered to": "Registered to",
"Registered on": "Registered on",
"Updates Expire": "Updates Expire",
"Flash GUID": "Flash GUID",
"Flash Vendor": "Flash Vendor",
"Flash Product": "Flash Product",
"Attached Storage Devices": "Attached Storage Devices",
"{0} out of {1} devices": "{0} out of {1} devices",
"Unable to check for updates": "Unable to check for updates",
"License key actions": "License key actions",
"License key type": "License key type",
"OS Update Eligibility Expiration": "OS Update Eligibility Expiration",
"Expired at {0}": "Expired at {0}",
"Expires at {0}": "Expires at {0}",
"Expired {0}": "Expired {0}",
"Expires in {0}": "Expires in {0}",
"Renew your license key now": "Renew your license key now",
"Renew Key to Enable OS Updates": "Renew Key to Enable OS Updates"
}

View File

@@ -89,6 +89,9 @@ export const useServerStore = defineStore('server', () => {
const regGuid = ref<string>('');
const regTm = ref<number>(0);
const regTo = ref<string>('');
const regTy = ref<string>('');
const regUpdExpAt = ref<number>(0);
const regUpdExpired = computed(() => regUpdExpAt.value < Date.now()); // @todo temp solution until webgui provides
const site = ref<string>('');
const state = ref<ServerState>();
const theme = ref<Theme>();
@@ -262,6 +265,13 @@ export const useServerStore = defineStore('server', () => {
name: 'redeem',
text: 'Redeem Activation Code',
};
const renewAction = ref<ServerStateDataAction>({
click: () => { purchaseStore.redeem(); },
external: true,
icon: KeyIcon,
name: 'renew',
text: 'Renew Key to Enable OS Updates',
});
const replaceAction: ServerStateDataAction = {
click: () => { accountStore.replace(); },
external: true,
@@ -716,6 +726,8 @@ export const useServerStore = defineStore('server', () => {
if (typeof data?.registered !== 'undefined') { registered.value = data.registered; }
if (typeof data?.regGen !== 'undefined') { regGen.value = data.regGen; }
if (typeof data?.regGuid !== 'undefined') { regGuid.value = data.regGuid; }
if (typeof data?.regTy !== 'undefined') { regTy.value = data.regTy; }
if (typeof data?.regUpdExpAt !== 'undefined') { regUpdExpAt.value = data.regUpdExpAt; }
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; }
@@ -853,6 +865,9 @@ export const useServerStore = defineStore('server', () => {
regGuid,
regTm,
regTo,
regTy,
regUpdExpAt,
regUpdExpired,
site,
state,
theme,
@@ -860,6 +875,7 @@ export const useServerStore = defineStore('server', () => {
username,
refreshServerStateStatus,
isOsVersionStable,
renewAction,
// getters
authAction,
deprecatedUnraidSSL,

View File

@@ -64,6 +64,9 @@ export interface Server {
regGuid?: string;
regTm?: number;
regTo?: string;
regTy?: string;
regUpdExpAt?: number;
regUpdExpired?: boolean;
site?: string;
state?: ServerState;
theme?: Theme | undefined;
@@ -113,7 +116,7 @@ export interface ServerPurchaseCallbackSendPayload {
site: string;
}
export type ServerStateDataKeyActions = 'purchase' | 'redeem' | 'upgrade' | 'recover' | 'replace' | 'trialExtend' | 'trialStart' | 'updateOs';
export type ServerStateDataKeyActions = 'purchase' | 'redeem' | 'upgrade' | 'recover' | 'renew' | 'replace' | 'trialExtend' | 'trialStart' | 'updateOs';
export type ServerStateDataAccountActions = 'signIn' | 'signOut' | 'troubleshoot';