feat: contact support using webgui feedback modal

This commit is contained in:
Zack Spear
2023-07-12 17:42:55 -07:00
committed by Zack Spear
parent 1dd717be2e
commit 262a085d0c
17 changed files with 216 additions and 108 deletions

View File

@@ -29,7 +29,7 @@ const blacklistedGuid = '154B-00EE-0700-9B50CF819816';
// EBLACKLISTED1
// EBLACKLISTED2
// ENOCONN
const state: string = 'BASIC';
const state: string = 'EGUID';
const uptime = Date.now() - 60 * 60 * 1000; // 1 hour ago
let expireTime = 0;
@@ -55,7 +55,7 @@ const serverState = {
"license": "",
"locale": "en_US",
"name": "fuji",
"pluginInstalled": "dynamix.unraid.net.staging.plg",
"connectPluginInstalled": "dynamix.unraid.net.staging.plg",
"registered": false,
"regGen": 0,
// "regGuid": "0781-5583-8355-81071A2B0211",

View File

@@ -54,21 +54,21 @@ $flashbackup_status = (file_exists($flashbackup_ini)) ? @parse_ini_file($flashba
$nginx = parse_ini_file('/var/local/emhttp/nginx.ini');
$pluginInstalled = '';
$connectPluginInstalled = '';
if (!file_exists('/var/lib/pkgtools/packages/dynamix.unraid.net') && !file_exists('/var/lib/pkgtools/packages/dynamix.unraid.net.staging')) {
$pluginInstalled = ''; // base OS only, plugin not installed • show ad for plugin
$connectPluginInstalled = ''; // base OS only, plugin not installed • show ad for plugin
} else {
// plugin is installed but if the unraid-api file doesn't fully install it's a failed install
if (file_exists('/var/lib/pkgtools/packages/dynamix.unraid.net')) $pluginInstalled = 'dynamix.unraid.net.plg';
if (file_exists('/var/lib/pkgtools/packages/dynamix.unraid.net.staging')) $pluginInstalled = 'dynamix.unraid.net.staging.plg';
if (file_exists('/var/lib/pkgtools/packages/dynamix.unraid.net')) $connectPluginInstalled = 'dynamix.unraid.net.plg';
if (file_exists('/var/lib/pkgtools/packages/dynamix.unraid.net.staging')) $connectPluginInstalled = 'dynamix.unraid.net.staging.plg';
// plugin install failed • append failure detected so we can show warning about failed install via UPC
if (!file_exists('/usr/local/sbin/unraid-api')) $pluginInstalled = $pluginInstalled . '_installFailed';
if (!file_exists('/usr/local/sbin/unraid-api')) $connectPluginInstalled = $connectPluginInstalled . '_installFailed';
}
$serverData = [
"apiKey" => $myservers['upc']['apikey'] ?? '',
"apiVersion" => $myservers['api']['version'] ?? '',
"avatar" => (!empty($myservers['remote']['avatar']) && $pluginInstalled) ? $myservers['remote']['avatar'] : '',
"avatar" => (!empty($myservers['remote']['avatar']) && $connectPluginInstalled) ? $myservers['remote']['avatar'] : '',
"banner" => $display['banner'] ?? '',
"bannerGradient" => $display['showBannerGradient'] ?? 'yes',
"bgColor" => ($backgnd) ? '#'.$backgnd : '',
@@ -103,11 +103,11 @@ $serverData = [
: ( file_exists('/var/log/plugins/dynamix.unraid.net.staging.plg')
? trim(@exec('/usr/local/sbin/plugin version /var/log/plugins/dynamix.unraid.net.staging.plg 2>/dev/null'))
: 'base-'.$var['version'] ),
"pluginInstalled" => $pluginInstalled,
"connectPluginInstalled" => $connectPluginInstalled,
"protocol" => $_SERVER['REQUEST_SCHEME'],
"regGen" => (int)$var['regGen'],
"regGuid" => $var['regGUID'],
"registered" => (!empty($myservers['remote']['username']) && $pluginInstalled),
"registered" => (!empty($myservers['remote']['username']) && $connectPluginInstalled),
"registeredTime" => $myservers['remote']['regWizTime'] ?? '',
"site" => $_SERVER['REQUEST_SCHEME']."://".$_SERVER['HTTP_HOST'],
"state" => strtoupper(empty($var['regCheck']) ? $var['regTy'] : $var['regCheck']),
@@ -115,7 +115,7 @@ $serverData = [
"theme" => $display['theme'],
"ts" => time(),
"uptime" => 1000*(time() - round(strtok(exec("cat /proc/uptime"),' '))),
"username" => (!empty($myservers['remote']['username']) && $pluginInstalled) ? $myservers['remote']['username'] : '',
"username" => (!empty($myservers['remote']['username']) && $connectPluginInstalled) ? $myservers['remote']['username'] : '',
"wanFQDN" => $nginx['NGINX_WANFQDN'] ?? '',
];

View File

@@ -13,13 +13,13 @@ withDefaults(defineProps<Props>(), {
});
const serverStore = useServerStore();
const { avatar, pluginInstalled, registered, username } = storeToRefs(serverStore);
const { avatar, connectPluginInstalled, registered, username } = storeToRefs(serverStore);
</script>
<template>
<figure class="group relative z-0 flex items-center justify-center w-36px h-36px rounded-full bg-gradient-to-r from-unraid-red to-orange">
<img
v-if="avatar && pluginInstalled && registered"
v-if="avatar && connectPluginInstalled && registered"
:src="avatar"
:alt="username"
class="absolute z-10 inset-0 w-36px h-36px rounded-full overflow-hidden">

View File

@@ -3,6 +3,7 @@ import { XCircleIcon } from '@heroicons/vue/24/solid';
export interface ButtonProps {
btnStyle?: 'fill' | 'outline' | 'underline';
btnType?: 'button' | 'submit' | 'reset';
click?: () => void;
download?: boolean;
external?: boolean;
href?: string;
@@ -31,7 +32,7 @@ const classes = computed(() => {
<template>
<component
:is="href ? 'a' : 'button'"
@click="$emit('click')"
@click="click ?? $emit('click')"
:href="href"
:rel="external ? 'noopener noreferrer' : ''"
:target="external ? '_blank' : ''"

View File

@@ -43,7 +43,7 @@ const {
keyType,
} = storeToRefs(installKeyStore);
const {
pluginInstalled,
connectPluginInstalled,
registered,
authAction,
} = storeToRefs(serverStore);
@@ -62,8 +62,8 @@ const {
*/
const isSettingsPage = ref<boolean>(document.location.pathname === '/Settings/ManagementAccess');
const showPromoCta = computed(() => callbackStatus.value === 'success' && !pluginInstalled.value);
const showSignInCta = computed(() => pluginInstalled.value && !registered.value && authAction.value?.name === 'signIn' && accountActionType.value !== 'signIn');
const showPromoCta = computed(() => callbackStatus.value === 'success' && !connectPluginInstalled.value);
const showSignInCta = computed(() => connectPluginInstalled.value && !registered.value && authAction.value?.name === 'signIn' && accountActionType.value !== 'signIn');
const heading = computed(() => {
switch (callbackStatus.value) {
@@ -162,7 +162,7 @@ const { text, copy, copied, isSupported } = useClipboard({ source: keyUrl.value
<template #footer>
<div v-if="callbackStatus === 'success'" class="flex flex-row-reverse justify-center gap-16px">
<template v-if="pluginInstalled && accountActionType === 'signIn'">
<template v-if="connectPluginInstalled && accountActionType === 'signIn'">
<BrandButton
v-if="isSettingsPage"
@click="close"
@@ -192,7 +192,7 @@ const { text, copy, copied, isSupported } = useClipboard({ source: keyUrl.value
<BrandButton
@click="close"
btn-style="underline"
:text="!pluginInstalled ? 'No Thanks' : 'Close'" />
:text="!connectPluginInstalled ? 'No Thanks' : 'Close'" />
</div>
</template>
</Modal>

View File

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

View File

@@ -11,14 +11,14 @@ const myServersEnv = ref<string>('Staging');
const devEnv = ref<string>('development');
const promoStore = usePromoStore();
const { keyActions, pluginInstalled, registered, stateData } = storeToRefs(useServerStore());
const { keyActions, connectPluginInstalled, registered, stateData } = storeToRefs(useServerStore());
const signInAction = computed(() => stateData.value.actions?.filter((act: { name: string; }) => act.name === 'signIn') ?? []);
const signOutAction = computed(() => stateData.value.actions?.filter((act: { name: string; }) => act.name === 'signOut') ?? []);
const links = computed(():UserProfileLink[] => {
return [
...(registered.value && pluginInstalled.value
...(registered.value && connectPluginInstalled.value
? [
{
emphasize: true,
@@ -45,13 +45,13 @@ const links = computed(():UserProfileLink[] => {
]
: []
),
...(!registered.value && pluginInstalled.value
...(!registered.value && connectPluginInstalled.value
? [
...(signInAction.value),
]
: []
),
...(!pluginInstalled.value
...(!connectPluginInstalled.value
? [
{
click: () => {
@@ -70,7 +70,7 @@ const links = computed(():UserProfileLink[] => {
<template>
<div class="flex flex-col gap-y-8px min-w-300px max-w-350px">
<header v-if="pluginInstalled" class="flex flex-row items-center justify-between mt-8px mx-8px">
<header v-if="connectPluginInstalled" class="flex flex-row items-center justify-between mt-8px mx-8px">
<h2 class="text-18px leading-none flex flex-row gap-x-8px items-center justify-between">
<BrandLogoConnect gradient-start="currentcolor" gradient-stop="currentcolor" class="text-beta w-[120px]" />
<UpcBeta />
@@ -82,7 +82,7 @@ const links = computed(():UserProfileLink[] => {
</header>
<ul class="list-reset flex flex-col gap-y-4px p-0">
<UpcDropdownError />
<UpcDropdownConnectStatus v-if="!stateData.error && registered && pluginInstalled" class="mt-8px" />
<UpcDropdownConnectStatus v-if="!stateData.error && registered && connectPluginInstalled" class="mt-8px" />
<li v-if="keyActions || links.length" class="m-8px">
<UpcKeyline />

View File

@@ -4,9 +4,9 @@ import { useServerStore } from '~/store/server';
import 'tailwindcss/tailwind.css';
import '~/assets/main.css';
const { expireTime, pluginInstalled, registered, state, stateData } = storeToRefs(useServerStore());
const { expireTime, connectPluginInstalled, registered, state, stateData } = storeToRefs(useServerStore());
const showConnectCopy = computed(() => (pluginInstalled.value && !registered.value && !stateData.value?.error));
const showConnectCopy = computed(() => (connectPluginInstalled.value && !registered.value && !stateData.value?.error));
const heading = computed(() => {
if (showConnectCopy.value) return 'Thank you for installing Connect!';

View File

@@ -9,19 +9,19 @@ const dropdownStore = useDropdownStore();
const { dropdownVisible } = storeToRefs(dropdownStore);
const { errors } = storeToRefs(useErrorsStore());
const {
pluginInstalled,
connectPluginInstalled,
registered,
state,
stateData,
username,
} = storeToRefs(useServerStore());
const registeredAndPluginInstalled = computed(() => pluginInstalled.value && registered.value);
const registeredAndconnectPluginInstalled = computed(() => connectPluginInstalled.value && registered.value);
const showErrorIcon = computed(() => errors.value.length || stateData.value.error);
const text = computed((): string | undefined => {
if ((stateData.value.error) && state.value !== 'EEXPIRED') return 'Fix Error';
if (registeredAndPluginInstalled.value) return username.value;
if (registeredAndconnectPluginInstalled.value) return username.value;
return;
});

View File

@@ -1,18 +0,0 @@
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

@@ -15,18 +15,6 @@ export const startTrial = (payload: StartTrialPayload) => KeyServer
.formUrl(payload)
.post();
export interface KeyServerTroubleshootPayload {
email: string;
subject: string;
message: string;
guid?: string; // if passed it'll be appended to the email subject instead of date/time
comments?: string; // HONEYPOT FIELD. Passing a non-empty value for 'comments' will trigger the honeypot, thus not send an email but won't return any errors.
}
export const troubleshoot = (payload: KeyServerTroubleshootPayload) => KeyServer
.url('/ips/troubleshoot')
.formUrl(payload)
.post();
export const validateGuid = (payload: { guid: string }) => KeyServer
.url('/validate/guid')
.formUrl(payload)

7
helpers/functions.ts Normal file
View File

@@ -0,0 +1,7 @@
/**
* @name OBJ_TO_STR
* @param {object} obj
* @returns {String}
* @description Output key + value for each item in the object. Adds new line after each item.
*/
export const OBJ_TO_STR = obj => Object.entries(obj).reduce((str, [p, val]) => `${str}${p}: ${val}\n`, '');

View File

@@ -89,15 +89,6 @@ export const useAccountStore = defineStore('account', () => {
type: 'signOut',
}]);
};
const troubleshoot = () => {
console.debug('[accountStore.accountStore.troubleshoot]');
callbackStore.send(`${ACCOUNT}/connect`, [{
server: {
...serverStore.serverAccountPayload,
},
type: 'troubleshoot',
}]);
};
/**
* @description Update myservers.cfg for both Sign In & Sign Out
* @note unraid-api requires apikey & token realted keys to be lowercase
@@ -187,7 +178,6 @@ export const useAccountStore = defineStore('account', () => {
replace,
signIn,
signOut,
troubleshoot,
updatePluginConfig,
};
});

View File

@@ -28,7 +28,7 @@ export const useDropdownStore = defineStore('dropdown', () => {
dropdownShow();
}
// automatically open the launchpad dropdown after plugin install on first page load
if (serverStore.pluginInstalled && !serverStore.registered && sessionStorage.getItem(`${baseStorageName}clickedInstallPlugin`)) {
if (serverStore.connectPluginInstalled && !serverStore.registered && sessionStorage.getItem(`${baseStorageName}clickedInstallPlugin`)) {
sessionStorage.removeItem(`${baseStorageName}clickedInstallPlugin`);
dropdownShow();
}

View File

@@ -7,6 +7,13 @@ import { defineStore, createPinia, setActivePinia } from 'pinia';
// import { useServerStore } from '~/store/server';
import type { ButtonProps } from '~/components/Brand/Button.vue';
import { OBJ_TO_STR } from '~/helpers/functions';
import type {
Server,
ServerStateData,
} from '~/types/server';
/**
* @see https://stackoverflow.com/questions/73476371/using-pinia-with-vue-js-web-components
* @see https://github.com/vuejs/pinia/discussions/1085
@@ -16,6 +23,7 @@ setActivePinia(createPinia());
export type ErrorType = 'account' | 'callback' | 'installKey' | 'server' | 'serverState';
export interface Error {
actions?: ButtonProps[];
debugServer?: Server;
heading: string;
level: 'error' | 'info' | 'warning';
message: string;
@@ -51,11 +59,100 @@ export const useErrorsStore = defineStore('errors', () => {
errors.value.push(error);
};
interface BugReportPayload {
email: string;
includeUnraidApiLogs: boolean;
}
const openBugReport = async (payload: BugReportPayload) => {
console.debug('[openBugReport]', payload);
try {
// eslint-disable-next-line no-undef
// @ts-ignore `FeedbackButton` will be included in 6.10.4+ DefaultPageLayout
await FeedbackButton();
// once the modal is visible we need to select the radio to correctly show the bug report form
let $modal = document.querySelector('.sweet-alert.visible');
while (!$modal) {
console.debug('[openBugReport] getting $modal…');
await new Promise(resolve => setTimeout(resolve, 100));
$modal = document.querySelector('.sweet-alert.visible');
}
console.debug('[openBugReport] $modal', $modal);
// autofill errors into the bug report form
if (errors.value.length) {
let $textarea = $modal.querySelector('#bugreport_panel textarea');
while (!$textarea) {
console.debug('[openBugReport] getting $textarea…');
await new Promise(resolve => setTimeout(resolve, 100));
$textarea = $modal.querySelector('#bugreport_panel textarea');
}
console.debug('[openBugReport] $textarea', $textarea);
const errorMessages = errors.value.map((error, index) => {
const index1 = index + 1;
let message = `• Error ${index1}: ${error.heading}\n`;
message += `• Error ${index1} Message: ${error.message}\n`;
message += `• Error ${index1} Level: ${error.level}\n`;
message += `• Error ${index1} Type: ${error.type}\n`;
if (error.ref) message += `• Error ${index1} Ref: ${error.ref}\n`;
if (error.debugServer) message += `• Error ${index1} Debug Server: ${OBJ_TO_STR(error.debugServer)}\n`;
return message;
}).join('\n***************\n');
$textarea.value += `\n##########################\n`;
$textarea.value += `# Debug Details Component Errors ${errors.value.length} #\n`;
$textarea.value += `##########################\n`;
$textarea.value += errorMessages;
}
if (payload.email) {
// autofill emails
let $emailInputs = $modal.querySelectorAll('[type="email"]');
while (!$emailInputs) {
console.debug('[openBugReport] getting $emailInputs…');
await new Promise(resolve => setTimeout(resolve, 100));
$emailInputs = $modal.querySelectorAll('[type="email"]');
}
console.debug('[openBugReport] $emailInputs', $emailInputs);
$emailInputs.forEach($input => {
$input.value = payload.email;
});
} else {
// focus email input within bugreport_panel
let $emailInput = $modal.querySelector('#bugreport_panel [type="email"]');
while (!$emailInput) {
console.debug('[openBugReport] getting $emailInput…');
await new Promise(resolve => setTimeout(resolve, 100));
$emailInput = $modal.querySelector('#bugreport_panel [type="email"]');
}
console.debug('[openBugReport] $emailInput', $emailInput);
$emailInput.focus();
}
// select the radio to correctly show the bug report form
let $myRadio: HTMLInputElement | null = $modal.querySelector('#optBugReport');
while (!$myRadio) {
await new Promise(resolve => setTimeout(resolve, 100));
$myRadio = $modal.querySelector('#optBugReport');
}
$myRadio.checked = true;
// show the correct form in the modal
let $panels = $modal.querySelectorAll('.allpanels');
while (!$panels) {
await new Promise(resolve => setTimeout(resolve, 100));
$panels = $modal.querySelectorAll('.allpanels');
}
$panels.forEach($panel => {
if ($panel.id === 'bugreport_panel') $panel.style['display'] = 'block';
else $panel.style['display'] = 'none';
});
} catch (error) {
console.error('[openBugReport]', error);
}
}
return {
errors,
removeErrorByIndex,
removeErrorByRef,
resetErrors,
setError,
openBugReport,
};
});

View File

@@ -1,9 +1,11 @@
import { ServerStateData } from './../types/server';
import { defineStore, createPinia, setActivePinia } from 'pinia';
import {
ArrowRightOnRectangleIcon,
CogIcon,
GlobeAltIcon,
KeyIcon,
QuestionMarkCircleIcon
} from '@heroicons/vue/24/solid';
import { SETTINGS_MANAGMENT_ACCESS } from '~/helpers/urls';
@@ -21,7 +23,7 @@ import type {
ServerStateConfigStatus,
ServerStateData,
ServerStateDataAction,
ServerPluginInstalled,
ServerconnectPluginInstalled,
} from '~/types/server';
/**
* @see https://stackoverflow.com/questions/73476371/using-pinia-with-vue-js-web-components
@@ -58,7 +60,7 @@ export const useServerStore = defineStore('server', () => {
const license = ref<string>('');
const locale = ref<string>('');
const name = ref<string>('');
const pluginInstalled = ref<ServerPluginInstalled>('');
const connectPluginInstalled = ref<ServerconnectPluginInstalled>('');
const registered = ref<boolean>();
const regGen = ref<number>(0);
const regGuid = ref<string>('');
@@ -79,7 +81,7 @@ export const useServerStore = defineStore('server', () => {
return false;
});
const server = computed<Server>(():Server => {
const server = computed(():Server => {
return {
apiKey: apiKey.value,
avatar: avatar.value,
@@ -96,7 +98,7 @@ export const useServerStore = defineStore('server', () => {
license: license.value,
locale: locale.value,
name: name.value,
pluginInstalled: pluginInstalled.value,
connectPluginInstalled: connectPluginInstalled.value,
registered: registered.value,
regGen: regGen.value,
regGuid: regGuid.value,
@@ -134,7 +136,6 @@ export const useServerStore = defineStore('server', () => {
state: state.value,
site: site.value,
};
console.debug('[serverPurchasePayload] server', server);
return server;
});
@@ -157,6 +158,35 @@ export const useServerStore = defineStore('server', () => {
}
});
const serverDebugPayload = computed((): Server => {
const payload = {
apiKey: apiKey.value,
avatar: avatar.value,
description: description.value,
deviceCount: deviceCount.value,
email: email.value,
expireTime: expireTime.value,
flashProduct: flashProduct.value,
flashVendor: flashVendor.value,
guid: guid.value,
inIframe: inIframe.value,
lanIp: lanIp.value,
locale: locale.value,
name: name.value,
connectPluginInstalled: connectPluginInstalled.value,
registered: registered.value,
regGen: regGen.value,
regGuid: regGuid.value,
site: site.value,
state: state.value,
uptime: uptime.value,
username: username.value,
wanFQDN: wanFQDN.value,
};
// remove any empty values from object
return Object.fromEntries(Object.entries(payload).filter(([_, v]) => v !== null && v !== undefined && v !== ''));
});
const purchaseAction: ServerStateDataAction = {
click: () => { purchaseStore.purchase() },
external: true,
@@ -230,9 +260,9 @@ export const useServerStore = defineStore('server', () => {
case 'ENOKEYFILE':
return {
actions: [
...(!registered.value && pluginInstalled.value ? [signInAction] : []),
...(!registered.value && connectPluginInstalled.value ? [signInAction] : []),
...([purchaseAction, redeemAction, trialStartAction]),
...(registered.value && pluginInstalled.value ? [signOutAction] : []),
...(registered.value && connectPluginInstalled.value ? [signOutAction] : []),
],
humanReadable: 'No Keyfile',
heading: `Let's Unleash your Hardware!`,
@@ -241,9 +271,9 @@ export const useServerStore = defineStore('server', () => {
case 'TRIAL':
return {
actions: [
...(!registered.value && pluginInstalled.value ? [signInAction] : []),
...(!registered.value && connectPluginInstalled.value ? [signInAction] : []),
...([purchaseAction, redeemAction]),
...(registered.value && pluginInstalled.value ? [signOutAction] : []),
...(registered.value && connectPluginInstalled.value ? [signOutAction] : []),
],
humanReadable: 'Trial',
heading: 'Thank you for choosing Unraid OS!',
@@ -252,10 +282,10 @@ export const useServerStore = defineStore('server', () => {
case 'EEXPIRED':
return {
actions: [
...(!registered.value && pluginInstalled.value ? [signInAction] : []),
...(!registered.value && connectPluginInstalled.value ? [signInAction] : []),
...([purchaseAction, redeemAction]),
...(trialExtensionEligible.value ? [trialExtendAction] : []),
...(registered.value && pluginInstalled.value ? [signOutAction] : []),
...(registered.value && connectPluginInstalled.value ? [signOutAction] : []),
],
error: true,
humanReadable: 'Trial Expired',
@@ -267,9 +297,9 @@ export const useServerStore = defineStore('server', () => {
case 'BASIC':
return {
actions: [
...(!registered.value && pluginInstalled.value ? [signInAction] : []),
...(!registered.value && connectPluginInstalled.value ? [signInAction] : []),
...([upgradeAction]),
...(registered.value && pluginInstalled.value ? [signOutAction] : []),
...(registered.value && connectPluginInstalled.value ? [signOutAction] : []),
],
humanReadable: 'Basic',
heading: 'Thank you for choosing Unraid OS!',
@@ -282,9 +312,9 @@ export const useServerStore = defineStore('server', () => {
case 'PLUS':
return {
actions: [
...(!registered.value && pluginInstalled.value ? [signInAction] : []),
...(!registered.value && connectPluginInstalled.value ? [signInAction] : []),
...([upgradeAction]),
...(registered.value && pluginInstalled.value ? [signOutAction] : []),
...(registered.value && connectPluginInstalled.value ? [signOutAction] : []),
],
humanReadable: 'Plus',
heading: 'Thank you for choosing Unraid OS!',
@@ -297,8 +327,8 @@ export const useServerStore = defineStore('server', () => {
case 'PRO':
return {
actions: [
...(!registered.value && pluginInstalled.value ? [signInAction] : []),
...(registered.value && pluginInstalled.value ? [signOutAction] : []),
...(!registered.value && connectPluginInstalled.value ? [signInAction] : []),
...(registered.value && connectPluginInstalled.value ? [signOutAction] : []),
],
humanReadable: 'Pro',
heading: 'Thank you for choosing Unraid OS!',
@@ -310,12 +340,12 @@ export const useServerStore = defineStore('server', () => {
if (guidReplaceable.value) messageEGUID = '<p>Your Unraid registration key is ineligible for replacement as it has been replaced within the last 12 months.</p>';
else if (guidReplaceable.value === false && guidBlacklisted.value) messageEGUID = `<p>The license key file does not correspond to the USB Flash boot device. Please copy the correct key file to the /config directory on your USB Flash boot device or choose Purchase Key.</p><p>Your Unraid registration key is ineligible for replacement as it is blacklisted.</p>`;
else if (guidReplaceable.value === false && !guidBlacklisted.value) messageEGUID = `<p>The license key file does not correspond to the USB Flash boot device. Please copy the correct key file to the /config directory on your USB Flash boot device or choose Purchase Key.</p><p>Your Unraid registration key is ineligible for replacement as it has been replaced within the last 12 months.</p>`;
else messageEGUID = '<p>The license key file does not correspond to the USB Flash boot device. Please copy the correct key file to the /config directory on your USB Flash boot device</p><p>You may also attempt to Purchase or Replace your key.</p>'; // basically guidReplaceable.value === null
else messageEGUID = '<p>The license key file does not correspond to the USB Flash boot device. Please copy the correct key file to the /config directory on your USB Flash boot device.</p><p>You may also attempt to Purchase or Replace your key.</p>'; // basically guidReplaceable.value === null
return {
actions: [
...(!registered.value && pluginInstalled.value ? [signInAction] : []),
...(!registered.value && connectPluginInstalled.value ? [signInAction] : []),
...([purchaseAction, redeemAction, replaceAction]),
...(registered.value && pluginInstalled.value ? [signOutAction] : []),
...(registered.value && connectPluginInstalled.value ? [signOutAction] : []),
],
error: true,
humanReadable: 'Flash GUID Error',
@@ -325,9 +355,9 @@ export const useServerStore = defineStore('server', () => {
case 'EGUID1':
return {
actions: [
...(!registered.value && pluginInstalled.value ? [signInAction] : []),
...(!registered.value && connectPluginInstalled.value ? [signInAction] : []),
...([purchaseAction, redeemAction]),
...(registered.value && pluginInstalled.value ? [signOutAction] : []),
...(registered.value && connectPluginInstalled.value ? [signOutAction] : []),
],
error: true,
humanReadable: 'Multiple License Keys Present',
@@ -338,24 +368,24 @@ export const useServerStore = defineStore('server', () => {
case 'ENOKEYFILE2':
return {
actions: [
...(!registered.value && pluginInstalled.value ? [signInAction] : []),
...(!registered.value && connectPluginInstalled.value ? [signInAction] : []),
...([purchaseAction, redeemAction]),
...(pluginInstalled.value ? [recoverAction] : []),
...(connectPluginInstalled.value ? [recoverAction] : []),
...(registered.value ? [signOutAction] : []),
],
error: true,
humanReadable: 'Missing key file',
heading: 'Missing key file',
message: pluginInstalled.value
message: connectPluginInstalled.value
? '<p>It appears that your license key file is corrupted or missing. The key file should be located in the /config directory on your USB Flash boot device.</p><p>With Unraid Connect (beta) installed you may attempt to recover your key with your Unraid.net account.</p><p>If this was an expired Trial installation, you may purchase a license key.</p>'
: '<p>It appears that your license key file is corrupted or missing. The key file should be located in the /config directory on your USB Flash boot device.</p><p>If you do not have a backup copy of your license key file you may install the Unraid Connect (beta) plugin to attempt to recover your key.</p><p>If this was an expired Trial installation, you may purchase a license key.</p>',
};
case 'ETRIAL':
return {
actions: [
...(!registered.value && pluginInstalled.value ? [signInAction] : []),
...(!registered.value && connectPluginInstalled.value ? [signInAction] : []),
...([purchaseAction, redeemAction]),
...(registered.value && pluginInstalled.value ? [signOutAction] : []),
...(registered.value && connectPluginInstalled.value ? [signOutAction] : []),
],
error: true,
humanReadable: 'Invalid installation',
@@ -365,9 +395,9 @@ export const useServerStore = defineStore('server', () => {
case 'ENOKEYFILE1':
return {
actions: [
...(!registered.value && pluginInstalled.value ? [signInAction] : []),
...(!registered.value && connectPluginInstalled.value ? [signInAction] : []),
...([purchaseAction, redeemAction]),
...(registered.value && pluginInstalled.value ? [signOutAction] : []),
...(registered.value && connectPluginInstalled.value ? [signOutAction] : []),
],
error: true,
humanReadable: 'No Keyfile',
@@ -429,10 +459,23 @@ export const useServerStore = defineStore('server', () => {
const stateDataError = computed(() => {
if (!stateData.value?.error) return undefined;
return {
actions: [
{
click: () => {
errorsStore.openBugReport({
email: email.value,
includeUnraidApiLogs: !!connectPluginInstalled.value,
});
},
icon: QuestionMarkCircleIcon,
text: 'Contact Support',
},
],
debugServer: serverDebugPayload.value,
heading: stateData.value?.heading,
level: 'error',
message: stateData.value?.message,
ref: 'stateDataError',
ref: `stateDataError__${state.value}`,
type: 'serverState',
};
});
@@ -494,7 +537,7 @@ export const useServerStore = defineStore('server', () => {
});
const pluginInstallFailed = computed((): Error | undefined => {
if (pluginInstalled.value && pluginInstalled.value.includes('_installFailed')) {
if (connectPluginInstalled.value && connectPluginInstalled.value.includes('_installFailed')) {
return {
heading: 'Unraid Connect Install Failed',
level: 'error',
@@ -559,7 +602,7 @@ export const useServerStore = defineStore('server', () => {
if (typeof data?.license !== 'undefined') license.value = data.license;
if (typeof data?.locale !== 'undefined') locale.value = data.locale;
if (typeof data?.name !== 'undefined') name.value = data.name;
if (typeof data?.pluginInstalled !== 'undefined') pluginInstalled.value = data.pluginInstalled;
if (typeof data?.connectPluginInstalled !== 'undefined') connectPluginInstalled.value = data.connectPluginInstalled;
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;
@@ -610,7 +653,7 @@ export const useServerStore = defineStore('server', () => {
locale,
lanIp,
name,
pluginInstalled,
connectPluginInstalled,
registered,
regGen,
regGuid,

View File

@@ -48,7 +48,7 @@ export interface Server {
license?: string;
locale?: string;
name?: string;
pluginInstalled?: ServerPluginInstalled;
connectPluginInstalled?: ServerconnectPluginInstalled;
registered?: boolean;
regGen?: number;
regGuid?: string;
@@ -120,4 +120,4 @@ export interface ServerStateData {
withKey?: boolean; // @todo potentially remove
}
export type ServerPluginInstalled = 'dynamix.unraid.net.plg' | 'dynamix.unraid.net.staging.plg' | 'dynamix.unraid.net.plg_installFailed' | 'dynamix.unraid.net.staging.plg_installFailed' | '';
export type ServerconnectPluginInstalled = 'dynamix.unraid.net.plg' | 'dynamix.unraid.net.staging.plg' | 'dynamix.unraid.net.plg_installFailed' | 'dynamix.unraid.net.staging.plg_installFailed' | '';