refactor: callback feedback status

This commit is contained in:
Zack Spear
2023-06-28 18:04:53 -07:00
committed by Zack Spear
parent df4999951d
commit 791e0aaeb0
11 changed files with 235 additions and 150 deletions

View File

@@ -41,18 +41,18 @@ body {
}
}
.BrandLoading_2,
.BrandLoading_4 {
.unraid_mark_2,
.unraid_mark_4 {
animation: mark_2 1.5s ease infinite;
}
.BrandLoading_3 {
.unraid_mark_3 {
animation: mark_3 1.5s ease infinite;
}
.BrandLoading_6,
.BrandLoading_8 {
.unraid_mark_6,
.unraid_mark_8 {
animation: mark_6 1.5s ease infinite;
}
.BrandLoading_7 {
.unraid_mark_7 {
animation: mark_7 1.5s ease infinite;
}

View File

@@ -22,7 +22,7 @@ withDefaults(defineProps<Props>(), {
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 133.52 76.97"
:class="`h-[${height}px]`"
:class="`unraid_mark h-[${height}px]`"
role="img"
>
<title>{{ title }}</title>
@@ -43,47 +43,47 @@ withDefaults(defineProps<Props>(), {
<path
d="m70,19.24zm57,0l6.54,0l0,38.49l-6.54,0l0,-38.49z"
fill="url(#unraidLoadingGradient)"
class="BrandLoading_9"
class="unraid_mark_9"
/>
<path
d="m70,19.24zm47.65,11.9l-6.55,0l0,-23.79l6.55,0l0,23.79z"
fill="url(#unraidLoadingGradient)"
class="BrandLoading_8"
class="unraid_mark_8"
/>
<path
d="m70,19.24zm31.77,-4.54l-6.54,0l0,-14.7l6.54,0l0,14.7z"
fill="url(#unraidLoadingGradient)"
class="BrandLoading_7"
class="unraid_mark_7"
/>
<path
d="m70,19.24zm15.9,11.9l-6.54,0l0,-23.79l6.54,0l0,23.79z"
fill="url(#unraidLoadingGradient)"
class="BrandLoading_6"
class="unraid_mark_6"
/>
<path
d="m63.49,19.24l6.51,0l0,38.49l-6.51,0l0,-38.49z"
fill="url(#unraidLoadingGradient)"
class="BrandLoading_5"
class="unraid_mark_5"
/>
<path
d="m70,19.24zm-22.38,26.6l6.54,0l0,23.78l-6.54,0l0,-23.78z"
fill="url(#unraidLoadingGradient)"
class="BrandLoading_4"
class="unraid_mark_4"
/>
<path
d="m70,19.24zm-38.26,43.03l6.55,0l0,14.73l-6.55,0l0,-14.73z"
fill="url(#unraidLoadingGradient)"
class="BrandLoading_3"
class="unraid_mark_3"
/>
<path
d="m70,19.24zm-54.13,26.6l6.54,0l0,23.78l-6.54,0l0,-23.78z"
fill="url(#unraidLoadingGradient)"
class="BrandLoading_2"
class="unraid_mark_2"
/>
<path
d="m70,19.24zm-63.46,38.49l-6.54,0l0,-38.49l6.54,0l0,38.49z"
fill="url(#unraidLoadingGradient)"
class="BrandLoading_1"
class="unraid_mark_1"
/>
</svg>
</template>

View File

@@ -6,13 +6,13 @@ import '~/assets/main.css';
import { useCallbackActionsStore } from '~/store/callbackActions';
import { usePromoStore } from '~/store/promo';
const { callbackFeedbackVisible } = storeToRefs(useCallbackActionsStore());
const { callbackStatus } = storeToRefs(useCallbackActionsStore());
const { promoVisible } = storeToRefs(usePromoStore());
</script>
<template>
<div class="relative z-[99999]">
<UpcCallbackFeedback :open="callbackFeedbackVisible" />
<UpcCallbackFeedback :open="callbackStatus !== 'ready'" />
<UpcPromo :open="promoVisible" />
</div>
</template>

View File

@@ -1,6 +1,6 @@
<script lang="ts" setup>
import { useClipboard } from '@vueuse/core'
import { CheckCircleIcon, ClipboardIcon, XCircleIcon } from '@heroicons/vue/24/solid';
import { ClipboardIcon } from '@heroicons/vue/24/solid';
import { storeToRefs } from 'pinia';
import 'tailwindcss/tailwind.css';
import '~/assets/main.css';
@@ -20,20 +20,29 @@ const accountStore = useAccountStore();
const callbackActionsStore = useCallbackActionsStore();
const installKeyStore = useInstallKeyStore();
const { accountUpdating, accountSuccess } = storeToRefs(accountStore);
const { callbackLoading } = storeToRefs(callbackActionsStore);
const { keyUrl, keyType, keyInstalling, keySuccess } = storeToRefs(installKeyStore);
const { accountActionStatus, accountActionStatusCopy } = storeToRefs(accountStore);
const { callbackData, callbackStatus } = storeToRefs(callbackActionsStore);
const { keyUrl, keyInstallStatus, keyInstallStatusCopy } = storeToRefs(installKeyStore);
const heading = computed(() => callbackLoading.value ? 'Performing actions' : 'Finished performing actions');
const subheading = computed(() => callbackLoading.value ? 'Please keep this window open' : '');
const heading = computed(() => callbackStatus.value === 'loading' ? 'Performing actions' : 'Finished performing actions');
const subheading = computed(() => callbackStatus.value === 'loading' ? 'Please keep this window open' : '');
const modalError = computed(() => callbackStatus.value === 'done' && (keyInstallStatus.value === 'failed' || accountActionStatus.value === 'failed'));
const modalSuccess = computed(() => {
if (!callbackData.value) return false;
// if we have multiple actions, we need both to be successful
return callbackData.value.actions.length > 1
? callbackStatus.value === 'done' && (keyInstallStatus.value === 'success' && accountActionStatus.value === 'success')
: callbackStatus.value === 'done' && (keyInstallStatus.value === 'success' || accountActionStatus.value === 'success');
});
// @todo keep for now as we may us`e this rather than refreshing once GQL is hooked up
// const close = () => {
// if (callbackLoading.value) return console.debug('[close] not allowed');
// callbackActionsStore.closeCallbackFeedback();
// if (callbackStatus.value === 'loading') return console.debug('[close] not allowed');
// callbackActionsStore.setCallbackStatus('ready');
// };
// @close="close"
// :show-close-x="!callbackLoading"
// :show-close-x="!callbackStatus === 'loading'"
const reload = () => window.location.reload();
@@ -44,8 +53,8 @@ const { text, copy, copied, isSupported } = useClipboard({ source: keyUrl.value
<Modal
:open="open"
max-width="max-w-640px"
:error="!callbackLoading && (keySuccess === false || accountSuccess === false)"
:success="!callbackLoading && (keySuccess === true || accountSuccess === true)"
:error="modalError"
:success="modalSuccess"
>
<div class="text-16px text-center relative w-full min-h-[20vh] flex flex-col justify-between gap-y-16px">
<header>
@@ -53,48 +62,34 @@ const { text, copy, copied, isSupported } = useClipboard({ source: keyUrl.value
<p v-if="subheading" class="text-16px opacity-80">{{ subheading }}</p>
</header>
<BrandLoading v-if="callbackLoading" class="w-90px mx-auto" />
<!-- <BrandLoading v-if="callbackStatus === 'loading'" class="w-90px mx-auto" /> -->
<template v-if="keyInstalling !== undefined">
<p v-if="keySuccess === undefined || callbackLoading">keyInstalling {{ keyType }} License Key</p>
<template v-else>
<div v-if="keySuccess === true" class="flex items-center justify-center gap-x-8px">
<CheckCircleIcon class="fill-green-600 w-24px" />
<p>Installed {{ keyType }} License Key</p>
<UpcCallbackFeedbackStatus
v-if="keyInstallStatus !== 'ready'"
:success="keyInstallStatus === 'success'"
:error="keyInstallStatus === 'failed'"
:text="keyInstallStatusCopy.text"
>
<template v-if="keyInstallStatus === 'failed'">
<div v-if="isSupported" class="flex justify-center">
<BrandButton
@click="copy(keyUrl)"
:icon="ClipboardIcon"
:text="copied ? 'Copied' : 'Copy Key URL'" />
</div>
<template v-else-if="keySuccess === false">
<div class="flex items-center justify-center gap-x-8px">
<XCircleIcon class="fill-unraid-red w-24px" />
<p class="text-unraid-red italic">{{ keyType }} License Key Install Failed</p>
</div>
<div v-if="isSupported" class="flex justify-center">
<BrandButton
@click="copy(keyUrl)"
:icon="ClipboardIcon"
:text="copied ? 'Copied' : 'Copy Key URL'" />
</div>
<p v-else>Copy your Key URL: {{ keyUrl }}</p>
<p>Then go to <a href="/Tools/Registration" class="opacity-75 hover:opacity-100 focus:opacity-100 underline transition">Tools > Registration</a> to manually install it</p>
</template>
<p v-else>Copy your Key URL: {{ keyUrl }}</p>
<p><a href="/Tools/Registration" class="opacity-75 hover:opacity-100 focus:opacity-100 underline transition">Then go to Tools > Registration to manually install it</a></p>
</template>
</template>
</UpcCallbackFeedbackStatus>
<template v-if="accountUpdating !== undefined">
<p v-if="accountSuccess === undefined || callbackLoading">Updating Connect account config</p>
<template v-else>
<div v-if="accountSuccess === true" class="flex items-center justify-center gap-x-8px">
<CheckCircleIcon class="fill-green-600 w-24px" />
<p>Connect config updated</p>
</div>
<div v-else-if="accountSuccess === false" class="flex items-center justify-center gap-x-8px">
<XCircleIcon class="fill-unraid-red w-24px" />
<p class="text-unraid-red italic">Connect config update failed</p>
</div>
</template>
</template>
<UpcCallbackFeedbackStatus
v-if="accountActionStatus !== 'ready'"
:success="accountActionStatus === 'success'"
:error="accountActionStatus === 'failed'"
:text="accountActionStatusCopy.text" />
<footer>
<div v-if="!callbackLoading && (keySuccess === true || accountSuccess === true)" class="w-full max-w-xs flex flex-col gap-y-16px mx-auto">
<div v-if="modalSuccess" class="w-full max-w-xs flex flex-col gap-y-16px mx-auto">
<button
@click="reload"
class="opacity-75 hover:opacity-100 focus:opacity-100 underline transition"
@@ -102,7 +97,7 @@ const { text, copy, copied, isSupported } = useClipboard({ source: keyUrl.value
{{ 'Reload Page to Finalize' }}
</button>
</div>
</footer>
</footer>
</div>
</Modal>
</template>

View File

@@ -0,0 +1,25 @@
<script setup lang="ts">
import { CheckCircleIcon, XCircleIcon } from '@heroicons/vue/24/solid';
export interface Props {
error?: boolean;
success?: boolean;
text?: string;
}
withDefaults(defineProps<Props>(), {
error: false,
success: false,
});
</script>
<template>
<div>
<div class="flex items-center justify-center gap-x-8px">
<CheckCircleIcon v-if="success" class="fill-green-600 w-24px" />
<XCircleIcon v-if="error" class="fill-unraid-red w-24px" />
<p>{{ text }}</p>
</div>
<slot></slot>
</div>
</template>

View File

@@ -25,7 +25,7 @@ const links = ref<UserProfileLink[]>([
</script>
<template>
<ul v-if="stateData.error" class="font-semibold list-reset flex flex-col gap-y-4px -mx-8px mb-4px py-12px px-16px text-white bg-unraid-red/90 rounded">
<ul v-if="stateData.error" class="text-white bg-unraid-red/90 font-semibold list-reset flex flex-col gap-y-4px mb-4px py-12px px-16px rounded">
<h3 class="text-18px">{{ stateData.heading }}</h3>
<p class="text-14px">{{ stateData.message }}</p>
<template v-if="links">

View File

@@ -3,7 +3,7 @@ import { useCallbackStore } from './callbackActions';
import { useServerStore } from './server';
import { WebguiUpdate } from '~/composables/services/webgui';
import { ACCOUNT } from '~/helpers/urls';
import type { CallbackAction } from '~/types/callback';
import type { ExternalSignIn, ExternalSignOut } from '~/store/callback';
/**
* @see https://stackoverflow.com/questions/73476371/using-pinia-with-vue-js-web-components
* @see https://github.com/vuejs/pinia/discussions/1085
@@ -15,8 +15,8 @@ export const useAccountStore = defineStore('account', () => {
const serverStore = useServerStore();
// State
const accountUpdating = ref<boolean | undefined>(undefined);
const accountSuccess = ref<boolean | undefined>(undefined);
const accountAction = ref<ExternalSignIn|ExternalSignOut>();
const accountActionStatus = ref<'failed' | 'ready' | 'success' | 'updating'>('ready');
// Actions
const recover = () => {
@@ -68,9 +68,11 @@ export const useAccountStore = defineStore('account', () => {
* @description Update myservers.cfg for both Sign In & Sign Out
* @note unraid-api requires apikey & token realted keys to be lowercase
*/
const updatePluginConfig = async (action: CallbackAction) => {
const updatePluginConfig = async (action: ExternalSignIn | ExternalSignOut) => {
console.debug('[accountStore.updatePluginConfig]', action);
accountUpdating.value = true;
accountAction.value = action;
accountActionStatus.value = 'updating';
const userPayload = {
...(action.user
? {
@@ -90,6 +92,7 @@ export const useAccountStore = defineStore('account', () => {
username: '',
}),
};
try {
const response = await WebguiUpdate
.formUrl({
@@ -101,26 +104,56 @@ export const useAccountStore = defineStore('account', () => {
.post()
.res(res => {
console.debug('[accountStore.updatePluginConfig] WebguiUpdate res', res);
accountSuccess.value = true;
accountActionStatus.value = 'success';
})
.catch(err => {
console.debug('[accountStore.updatePluginConfig] WebguiUpdate err', err);
accountSuccess.value = false;
accountActionStatus.value = 'failed';
});
return response;
} finally {
accountUpdating.value = false;
} catch(err) {
console.debug('[accountStore.updatePluginConfig] WebguiUpdate catch err', err);
accountActionStatus.value = 'failed';
}
};
watch(accountUpdating, (newV, oldV) => {
console.debug('[accountUpdating.watch]', newV, oldV);
const accountActionStatusCopy = computed((): { text: string; } => {
switch (accountActionStatus.value) {
case 'ready':
return {
text: 'Ready to update Connect account configuration',
};
case 'updating':
return {
text: accountAction.value?.type === 'signIn'
? 'Signing in...'
: 'Signing out...',
};
case 'success':
return {
text: accountAction.value?.type === 'signIn'
? 'Signed in successfully'
: 'Signed out successfully',
};
case 'failed':
return {
text: accountAction.value?.type === 'signIn'
? 'Sign in failed'
: 'Sign out failed',
};
}
});
watch(accountActionStatus, (newV, oldV) => {
console.debug('[accountActionStatus.watch]', newV, oldV);
});
return {
// State
accountUpdating,
accountSuccess,
accountAction,
accountActionStatus,
// Getters
accountActionStatusCopy,
// Actions
recover,
replace,

View File

@@ -19,17 +19,23 @@ export interface ServerAccountCallbackServerData {
state: string;
wanFQDN?: string;
}
export type ServerStateDataActionType =
| 'signIn'
| 'signOut'
| 'purchase'
| 'redeem'
| 'upgrade'
| 'recover'
| 'replace'
| 'trialExtend'
| 'trialStart'
| 'troubleshoot';
export type SignIn = 'signIn';
export type SignOut = 'signOut';
export type Troubleshoot = 'troubleshoot';
export type Recover = 'recover';
export type Replace = 'replace';
export type TrialExtend = 'trialExtend';
export type TrialStart = 'trialStart';
export type Purchase = 'purchase';
export type Redeem = 'redeem';
export type Upgrade = 'upgrade';
export type AccountAction = SignIn | SignOut | Troubleshoot;
export type AccountKeyAction = Recover | Replace | TrialExtend | TrialStart;
export type PurchaseAction = Purchase | Redeem | Upgrade;
export type ServerStateDataActionType = AccountAction | AccountKeyAction | PurchaseAction;
export interface ServerPayload {
server: ServerAccountCallbackServerData;
@@ -37,27 +43,22 @@ export interface ServerPayload {
}
export interface ExternalSignIn {
type: 'signIn';
type: SignIn;
apiKey: string;
user: UserInfo;
}
export interface ExternalSignOut {
type: 'signOut';
type: SignOut;
}
export interface ExternalKeyActions {
type: 'recover' | 'replace' | 'trialExtend' | 'trialStart';
type: PurchaseAction | AccountKeyAction;
keyUrl: string;
}
export interface ExternalPurchaseActions {
type: 'purchase' | 'redeem' | 'upgrade';
}
export type ExternalActions =
| ExternalSignIn
| ExternalSignOut
| ExternalKeyActions
| ExternalPurchaseActions;
| ExternalKeyActions;
export type UpcActions = ServerPayload;
@@ -105,7 +106,6 @@ export const useCallbackStoreGeneric = (
const encryptionKey = 'Uyv2o8e*FiQe8VeLekTqyX6Z*8XonB';
const sendType = 'fromUpc';
// state
const callbackLoading = ref(false);
const encryptedMessage = ref<string | null>(null);
// actions
@@ -136,7 +136,6 @@ export const useCallbackStoreGeneric = (
return console.debug('[callback.watcher] no callback to handle');
}
callbackLoading.value = true;
const decryptedMessage = AES.decrypt(callbackValue, encryptionKey);
const decryptedData: QueryPayloads = JSON.parse(decryptedMessage.toString(Utf8));
console.debug('[callback.watcher]', decryptedMessage, decryptedData);
@@ -145,8 +144,6 @@ export const useCallbackStoreGeneric = (
};
return {
// state
callbackLoading,
// actions
send,
watcher,

View File

@@ -2,7 +2,7 @@ import { defineStore } from 'pinia';
import { useAccountStore } from './account';
import { useInstallKeyStore } from './installKey';
import { useCallbackStoreGeneric, type ExternalPayload, type QueryPayloads } from './callback';
import { useCallbackStoreGeneric, type ExternalPayload, type ExternalActions, type ExternalKeyActions, type QueryPayloads } from './callback';
// import { useServerStore } from './server';
export const useCallbackActionsStore = defineStore(
@@ -11,31 +11,32 @@ export const useCallbackActionsStore = defineStore(
const accountStore = useAccountStore();
const installKeyStore = useInstallKeyStore();
// const serverStore = useServerStore();
type CallbackStatus = 'error' | 'loading' | 'ready' | 'done';
const callbackStatus = ref<CallbackStatus>('ready');
const callbackData = ref<ExternalPayload>();
const callbackError = ref();
const callbackLoading = ref(false);
const callbackFeedbackVisible = ref<boolean>(false);
const callbackKeyAction = ref<CallbackAction>();
const redirectToCallbackType = (decryptedData: QueryPayloads) => {
console.debug('[redirectToCallbackType]', { decryptedData });
if (!decryptedData.type || decryptedData.type === 'fromUpc' || !decryptedData.actions?.length) {
callbackError.value = 'Callback redirect type not present or incorrect';
callbackStatus.value = 'ready'; // default status
return console.error('[redirectToCallbackType]', callbackError.value);
}
// Display the feedback modal
callbackData.value = decryptedData;
callbackStatus.value = 'loading';
callbackFeedbackVisible.value = true;
callbackLoading.value = true;
// Parse the data and perform actions
callbackData.value.actions.forEach(async (action, index, array) => {
console.debug('[action]', action);
if (action?.keyUrl) {
await installKeyStore.install(action);
await installKeyStore.install(action as ExternalKeyActions);
}
/** @todo add oemSignOut */
if (action?.user || action.type === 'signOut') {
@@ -43,16 +44,21 @@ export const useCallbackActionsStore = defineStore(
}
// all actions have run
if (array.length === (index + 1)) {
console.debug('[actions] DONE');
callbackLoading.value = false;
// setTimeout(() => {
// callbackLoading.value = false;
// }, 1000);
callbackStatus.value = 'done';
// if (array.length > 1) {
// // if we have more than 1 action it means there was a key install and an account action so both need to be successful
// const allSuccess = accountStore.accountActionStatus === 'success' && installKeyStore.keyInstallStatus === 'success';
// callbackStatus.value = allSuccess ? 'success' : 'error';
// } else {
// // only 1 action needs to be successful
// const oneSuccess = accountStore.accountActionStatus === 'success' || installKeyStore.keyInstallStatus === 'success';
// callbackStatus.value = oneSuccess ? 'success' : 'error';
// }
}
});
};
const closeCallbackFeedback = () => callbackFeedbackVisible.value = false;
const setCallbackStatus = (status: CallbackStatus) => callbackStatus.value = status;
const preventClose = (e: { preventDefault: () => void; returnValue: string; }) => {
e.preventDefault();
@@ -62,18 +68,18 @@ export const useCallbackActionsStore = defineStore(
alert('Closing this pop-up window while actions are being preformed may lead to unintended errors.');
};
watch(callbackLoading, (newVal, _oldVal) => {
console.debug('[callbackLoading]', newVal);
if (newVal === true) {
console.debug('[callbackLoading] true', 'addEventListener');
watch(callbackStatus, (newVal, _oldVal) => {
console.debug('[callbackStatus]', newVal);
if (newVal === 'ready') {
console.debug('[callbackStatus]', newVal, 'addEventListener');
window.addEventListener('beforeunload', preventClose);
}
// removing query string once actions are done so users can't refresh the page and go through the same actions
if (newVal === false) {
console.debug('[callbackLoading] false', 'removeEventListener');
if (newVal !== 'ready') {
console.debug('[callbackStatus]', newVal, 'removeEventListener');
window.removeEventListener('beforeunload', preventClose);
console.debug('[callbackLoading] push history w/o query');
window.history.pushState(null, '', window.location.pathname);
console.debug('[callbackStatus] replace history w/o query');
window.history.replaceState(null, '', window.location.pathname);
}
});
@@ -81,8 +87,8 @@ export const useCallbackActionsStore = defineStore(
redirectToCallbackType,
callbackData,
callbackFeedbackVisible,
callbackLoading,
closeCallbackFeedback,
callbackStatus,
setCallbackStatus,
}
});

View File

@@ -13,11 +13,11 @@ setActivePinia(createPinia());
export const useInstallKeyStore = defineStore('installKey', () => {
const serverStore = useServerStore();
const keyActionType = ref<ServerStateDataKeyActions>();
const keyInstalling = ref<boolean | undefined>();
const keyUrl = ref<string>('');
const keySuccess = ref<boolean | undefined>();
const keyInstallStatus = ref<'failed' | 'installing' | 'ready' | 'success'>('ready');
const keyAction = ref<ExternalKeyActions>();
const keyActionType = computed(() => keyAction.value?.type);
const keyUrl = computed(() => keyAction.value?.keyUrl);
/**
* Extracts key type from key url. Works for both .key and .unkey.
*/
@@ -29,9 +29,8 @@ export const useInstallKeyStore = defineStore('installKey', () => {
const install = async (action: ExternalKeyActions) => {
console.debug('[install]');
keyInstalling.value = true;
keyActionType.value = action.type;
keyUrl.value = action.keyUrl;
keyInstallStatus.value = 'installing';
keyAction.value = action;
if (!keyUrl.value) return console.error('[install] no key to install');
@@ -41,7 +40,7 @@ export const useInstallKeyStore = defineStore('installKey', () => {
.get();
console.log('[install] WebguiInstallKey installResponse', installResponse);
keySuccess.value = true;
keyInstallStatus.value = 'success';
try {
const updateDnsResponse = await WebguiUpdateDns
@@ -56,20 +55,50 @@ export const useInstallKeyStore = defineStore('installKey', () => {
}
} catch (error) {
console.error('[install] WebguiInstallKey error', error);
keySuccess.value = false;
} finally {
keyInstalling.value = false;
keyInstallStatus.value = 'failed';
}
};
const keyInstallStatusCopy = computed((): { text: string; } => {
switch (keyInstallStatus.value) {
case 'ready':
return {
text: 'Ready to Install Key',
};
case 'installing':
let txt1 = 'Installing';
if (keyActionType.value === 'replace') txt1 = 'Installing Replaced';
if (keyActionType.value === 'recover') txt1 = 'Installing Recovered';
return {
text: `${txt1} ${keyType.value} Key...`,
};
case 'success':
let txt2 = 'Installed';
if (keyActionType.value === 'replace') txt2 = 'Replaced';
if (keyActionType.value === 'recover') txt2 = 'Recovered';
return {
text: `${keyType.value} Key ${txt2} Successfully`,
};
case 'failed':
let txt3 = 'Install';
if (keyActionType.value === 'replace') txt3 = 'Install Replaced';
if (keyActionType.value === 'recover') txt3 = 'Install Recoverd';
return {
text: `Failed to ${txt3} ${keyType.value} Key`,
};
}
});
watch(keyInstallStatus, (newV, oldV) => {
console.debug('[keyInstallStatus]', newV, oldV);
});
return {
// State
keyActionType,
keyInstalling,
keySuccess,
keyUrl,
keyInstallStatus,
// getters
keyType,
keyUrl,
keyInstallStatusCopy,
// Actions
install,
};

View File

@@ -4,14 +4,14 @@ import defaultTheme from 'tailwindcss/defaultTheme'
export default <Partial<Config>>{
safelist: [
'DropdownWrapper_blip',
'BrandLoading_1',
'BrandLoading_2',
'BrandLoading_3',
'BrandLoading_4',
'BrandLoading_6',
'BrandLoading_7',
'BrandLoading_8',
'BrandLoading_9',
'unraid_mark_1',
'unraid_mark_2',
'unraid_mark_3',
'unraid_mark_4',
'unraid_mark_6',
'unraid_mark_7',
'unraid_mark_8',
'unraid_mark_9',
],
theme: {
extend: {