mirror of
https://github.com/unraid/api.git
synced 2026-01-02 22:50:02 -06:00
refactor: errors for server
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
18
composables/openFeedback.ts
Normal file
18
composables/openFeedback.ts
Normal 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;
|
||||
@@ -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',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user