mirror of
https://github.com/unraid/api.git
synced 2026-02-18 05:58:28 -06:00
refactor: callback feedback status
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
25
components/UserProfile/CallbackFeedbackStatus.vue
Normal file
25
components/UserProfile/CallbackFeedbackStatus.vue
Normal 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>
|
||||
@@ -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">
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user