refactor: errors for server

This commit is contained in:
Zack Spear
2023-07-12 13:54:22 -07:00
committed by Zack Spear
parent a46f5a3cb4
commit 1dd717be2e
13 changed files with 100 additions and 56 deletions

View File

@@ -39,6 +39,10 @@ if (state === 'EEXPIRED') expireTime = uptime; // 1 hour ago
const serverState = {
"apiKey": "XXXunupc_12312313123",
"avatar": "https://source.unsplash.com/300x300/?portrait",
"config": {
// error: 'INVALID',
valid: true,
},
"description": "DevServer9000",
"deviceCount": "3",
expireTime,
@@ -63,7 +67,7 @@ const serverState = {
"bgColor": "",
"descriptionShow": true,
"metaColor": "",
"name": "white",
"name": "black",
"textColor": ""
},
uptime,

View File

@@ -7,10 +7,10 @@ import { useServerStore } from '~/store/server';
const dropdownStore = useDropdownStore()
const { dropdownVisible } = storeToRefs(dropdownStore);
const { pluginInstalled, registered, state } = storeToRefs(useServerStore());
const { pluginInstalled, registered, state, stateDataError, serverErrors } = storeToRefs(useServerStore());
const showDefaultContent = computed(() => !showLaunchpad.value);
const showLaunchpad = computed(() => state.value === 'ENOKEYFILE' || (pluginInstalled.value && !registered.value));
const showLaunchpad = computed(() => state.value === 'ENOKEYFILE' || (pluginInstalled.value && !registered.value) && !stateDataError.value);
</script>
<template>

View File

@@ -84,7 +84,7 @@ const links = computed(():UserProfileLink[] => {
<UpcDropdownError />
<UpcDropdownConnectStatus v-if="!stateData.error && registered && pluginInstalled" class="mt-8px" />
<li class="m-8px">
<li v-if="keyActions || links.length" class="m-8px">
<UpcKeyline />
</li>
@@ -94,7 +94,7 @@ const links = computed(():UserProfileLink[] => {
</li>
</template>
<template v-if="links">
<template v-if="links.length">
<li v-for="(link, index) in links" :key="`link_${index}`">
<UpcDropdownItem :item="link" />
</li>

View File

@@ -1,35 +1,15 @@
<script setup lang="ts">
import { storeToRefs } from 'pinia';
// import {
// ExclamationCircleIcon,
// ExclamationTriangleIcon,
// ShieldExclamationIcon,
// } from '@heroicons/vue/24/solid';
import { useErrorsStore, type Error } from '~/store/errors';
import { useServerStore } from '~/store/server';
import { useErrorsStore } from '~/store/errors';
const errorsStore = useErrorsStore();
const { errors } = storeToRefs(errorsStore);
const { stateData } = storeToRefs(useServerStore());
const computedErrors = computed(() => {
if (stateData.value?.error) {
return [
{
heading: stateData.value.heading,
level: 'error',
message: stateData.value.message,
},
];
};
return errors.value;
});
</script>
<template>
<ul v-if="computedErrors" class="text-white bg-unraid-red/90 font-semibold list-reset flex flex-col gap-y-8px mb-4px py-12px px-16px rounded">
<li v-for="(error, index) in computedErrors" :key="index" class="flex flex-col gap-8px">
<ul v-if="errors.length" class="text-white bg-unraid-red/90 font-semibold list-reset flex flex-col gap-y-8px mb-4px py-12px px-16px rounded">
<li v-for="(error, index) in errors" :key="index" class="flex flex-col gap-8px">
<h3 class="text-18px">
<span>{{ error.heading }}</span>
</h3>

View File

@@ -24,7 +24,7 @@ const showExpireTime = computed(() => {
</script>
<template>
<div class="flex flex-col gap-y-24px w-full min-w-300px md:min-w-[500px] max-w-4xl p-16px">
<div class="flex flex-col gap-y-24px w-full min-w-300px md:min-w-[500px] max-w-xl p-16px">
<header :class="{ 'text-center': showConnectCopy }">
<h2 class="text-24px text-center font-semibold" v-html="heading" />
<div v-html="subheading" class="flex flex-col gap-y-8px" />

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { storeToRefs } from 'pinia';
import { InformationCircleIcon, ExclamationTriangleIcon } from '@heroicons/vue/24/solid';
import { InformationCircleIcon, ExclamationTriangleIcon, ShieldExclamationIcon } from '@heroicons/vue/24/solid';
import { useDropdownStore } from '~/store/dropdown';
import { useErrorsStore } from '~/store/errors';
import { useServerStore } from '~/store/server';
@@ -10,7 +10,6 @@ const { dropdownVisible } = storeToRefs(dropdownStore);
const { errors } = storeToRefs(useErrorsStore());
const {
pluginInstalled,
pluginOutdated,
registered,
state,
stateData,
@@ -40,8 +39,11 @@ const title = computed((): string => {
class="group text-18px hover:text-alpha focus:text-alpha border border-transparent relative flex flex-row justify-end items-center h-full gap-x-8px outline-none focus:outline-none"
:title="title"
>
<InformationCircleIcon v-if="pluginOutdated" class="text-unraid-red fill-current relative w-24px h-24px" />
<ExclamationTriangleIcon v-else-if="showErrorIcon" class="text-unraid-red fill-current relative w-24px h-24px" />
<template v-if="errors.length && errors[0].level">
<InformationCircleIcon v-if="errors[0].level === 'info'" class="text-unraid-red fill-current relative w-24px h-24px" />
<ExclamationTriangleIcon v-if="errors[0].level === 'warning'" class="text-unraid-red fill-current relative w-24px h-24px" />
<ShieldExclamationIcon v-if="errors[0].level === 'error'" class="text-unraid-red fill-current relative w-24px h-24px" />
</template>
<span v-if="text" class="relative leading-none">
<span>{{ text }}</span>
<span class="absolute bottom-[-3px] inset-x-0 h-2px w-full bg-gradient-to-r from-unraid-red to-orange rounded opacity-0 group-hover:opacity-100 group-focus:opacity-100 transition-opacity"></span>

View File

@@ -0,0 +1,18 @@
const useOpenFeedback = () => {
const open = (includeUnraidApiLogs: boolean) => {
console.debug('[useOpenFeedback.open]', { includeUnraidApiLogs });
try {
// eslint-disable-next-line no-undef
// @ts-ignore `FeedbackButton` will be included in 6.10.4+ DefaultPageLayout
FeedbackButton();
} catch (error) {
console.error('[useOpenFeedback.open]', error);
}
}
return {
open,
};
};
export default useOpenFeedback;

View File

@@ -152,13 +152,25 @@ export const useAccountStore = defineStore('account', () => {
.catch(err => {
console.debug('[accountStore.updatePluginConfig] WebguiUpdate err', err);
accountActionStatus.value = 'failed';
errorsStore.setError(err);
errorsStore.setError({
heading: 'Failed to update Connect account configuration',
message: err.message,
level: 'error',
ref: 'updatePluginConfig',
type: 'account',
});
});
return response;
} catch(err) {
console.debug('[accountStore.updatePluginConfig] WebguiUpdate catch err', err);
accountActionStatus.value = 'failed';
errorsStore.setError(err);
errorsStore.setError({
heading: 'Failed to update Connect account configuration',
message: err.message,
level: 'error',
ref: 'updatePluginConfig',
type: 'account',
});
}
};

View File

@@ -33,11 +33,13 @@ export const useErrorsStore = defineStore('errors', () => {
const errors = ref<Error[]>([]);
const displayedErrors = computed(() => errors.value.filter(error => error.type === 'server' || error.type === 'serverState'));
const removeErrorByIndex = (index: number) => {
errors.value = errors.value.filter((_error, i) => i !== index);
};
const removeErrorByRef = (ref: ErrorType) => {
const removeErrorByRef = (ref: string) => {
errors.value = errors.value.filter(error => error?.ref !== ref);
};
@@ -45,7 +47,7 @@ export const useErrorsStore = defineStore('errors', () => {
errors.value = [];
};
const setError = (error: any) => {
const setError = (error: Error) => {
errors.value.push(error);
};

View File

@@ -57,7 +57,14 @@ export const useInstallKeyStore = defineStore('installKey', () => {
} catch (error) {
console.error('[install] WebguiInstallKey error', error);
keyInstallStatus.value = 'failed';
errorsStore.setError(error);
errorsStore.setError({
heading: 'Failed to install key',
message: error.message,
level: 'error',
ref: 'installKey',
type: 'installKey',
});
}
};

View File

@@ -425,6 +425,18 @@ export const useServerStore = defineStore('server', () => {
};
}
});
const stateDataError = computed(() => {
if (!stateData.value?.error) return undefined;
return {
heading: stateData.value?.heading,
level: 'error',
message: stateData.value?.message,
ref: 'stateDataError',
type: 'serverState',
};
});
const authActionsNames = ['signIn', 'signOut'];
// Extract sign in / out from actions so we can display seperately as needed
const authAction = computed((): ServerStateDataAction | undefined => {
@@ -478,6 +490,7 @@ export const useServerStore = defineStore('server', () => {
type: 'server',
};
}
return undefined;
});
const pluginInstallFailed = computed((): Error | undefined => {
@@ -490,6 +503,7 @@ export const useServerStore = defineStore('server', () => {
type: 'server',
};
}
return undefined;
});
/**
@@ -505,13 +519,24 @@ export const useServerStore = defineStore('server', () => {
text: 'Go to Management Access Now',
},
],
heading: 'Unraid.net SSL Certificate Deprecation',
level: 'warning',
message: 'Unraid.net SSL certificates will be deprecated on January 1st, 2023. You MUST provision a new SSL certificate to use our new myunraid.net domain. You can do this on the Settings > Management Access page.',
heading: 'SSL certificates for unraid.net deprecated',
level: 'error',
message: 'On January 1st, 2023 SSL certificates for unraid.net were deprecated. You MUST provision a new SSL certificate to use our new myunraid.net domain. You can do this on the Settings > Management Access page.',
ref: 'deprecatedUnraidSSL',
type: 'server',
};
}
return undefined;
});
const serverErrors = computed(() => {
return [
stateDataError.value,
invalidApiKey.value,
tooManyDevices.value,
pluginInstallFailed.value,
deprecatedUnraidSSL.value,
].filter(Boolean);
});
/**
* Actions
@@ -520,6 +545,7 @@ export const useServerStore = defineStore('server', () => {
console.debug('[setServer] data', data);
if (typeof data?.apiKey !== 'undefined') apiKey.value = data.apiKey;
if (typeof data?.avatar !== 'undefined') avatar.value = data.avatar;
if (typeof data?.config !== 'undefined') config.value = data.config;
if (typeof data?.csrf !== 'undefined') csrf.value = data.csrf;
if (typeof data?.description !== 'undefined') description.value = data.description;
if (typeof data?.deviceCount !== 'undefined') deviceCount.value = data.deviceCount;
@@ -550,18 +576,9 @@ export const useServerStore = defineStore('server', () => {
if (newVal) themeStore.setTheme(newVal);
});
watch(stateData, (newVal, oldVal) => {
if (oldVal.error) {
errorsStore.removeErrorByRef('serverState');
}
if (newVal.error && !oldVal.error) {
const stateDataError = {
heading: newVal.heading,
message: newVal.message,
type: 'serverState',
};
errorsStore.setError(stateDataError);
}
watch(stateDataError, (newVal, oldVal) => {
if (oldVal && oldVal.ref) errorsStore.removeErrorByRef(oldVal.ref);
if (newVal) errorsStore.setError(newVal);
});
watch(invalidApiKey, (newVal, oldVal) => {
if (oldVal && oldVal.ref) errorsStore.removeErrorByRef(oldVal.ref);
@@ -611,6 +628,8 @@ export const useServerStore = defineStore('server', () => {
serverAccountPayload,
serverPurchasePayload,
stateData,
stateDataError,
serverErrors,
// actions
setServer,
};

View File

@@ -19,7 +19,7 @@ export interface Theme {
export const useThemeStore = defineStore('theme', () => {
// State
const theme = ref<Theme|undefined>();
const theme = ref<Theme | undefined>();
// Getters
const darkMode = computed(() => (theme.value?.name === 'black' || theme.value?.name === 'azure') ?? false);
const bannerGradient = computed(() => {

View File

@@ -3,7 +3,7 @@ import { Theme } from '~/store/theme';
import { UserProfileLink } from '~/types/userProfile';
export interface ServerStateConfigStatus {
error: 'INVALID' | 'NO_KEY_SERVER' | 'UNKNOWN_ERROR' | 'WITHDRAWN';
error?: 'INVALID' | 'NO_KEY_SERVER' | 'UNKNOWN_ERROR' | 'WITHDRAWN';
valid: boolean;
}
export type ServerState = 'BASIC'
@@ -54,7 +54,7 @@ export interface Server {
regGuid?: string;
site?: string;
state?: ServerState;
theme: Theme;
theme: Theme | undefined;
uptime?: number;
username?: string;
wanFQDN?: string;