diff --git a/_data/serverState.ts b/_data/serverState.ts
index 8e8e324b6..dee84c613 100644
--- a/_data/serverState.ts
+++ b/_data/serverState.ts
@@ -8,9 +8,9 @@ function makeid(length: number) {
return result;
}
-const registeredGuid = `1111-1111-${makeid(4)}-123412341234`; // this guid is registered in key server
+const randomGuid = `1111-1111-${makeid(4)}-123412341234`; // this guid is registered in key server
const newGuid = `1234-1234-${makeid(4)}-123412341234`; // this is a new USB, not registered
-const regWizTime = `1616711990500_${registeredGuid}`;
+const regWizTime = `1616711990500_${randomGuid}`;
const blacklistedGuid = '154B-00EE-0700-9B50CF819816';
// ENOKEYFILE
@@ -41,12 +41,12 @@ const serverState = {
avatar: 'https://source.unsplash.com/300x300/?portrait',
name: 'DevServer9000',
description: 'Fully automated media server',
- guid: '9292-1111-BITE-444444444444',
+ guid: randomGuid,
deviceCount: 8,
expireTime,
lanIp: '192.168.0.1',
locale: 'en',
- pluginInstalled: false,
+ pluginInstalled: true,
registered: true,
site: 'http://localhost:4321',
state,
diff --git a/_webGui/callbackTest.page b/_webGui/callbackTest.page
index 132c018ac..0f21a0810 100644
--- a/_webGui/callbackTest.page
+++ b/_webGui/callbackTest.page
@@ -16,48 +16,124 @@ $localSource = '/plugins/dynamix.my.servers/webComponents/' . $webComponentFile;
// add the web component source to the DOM
echo '';
+/**
+ * Build vars for user profile prop
+ */
+// add 'ipaddr' function for 6.9 backwards compatibility
+if (!function_exists('ipaddr')) {
+ function ipaddr($ethX='eth0', $prot=4) {
+ global $$ethX;
+ switch ($$ethX['PROTOCOL:0']) {
+ case 'ipv4':
+ return $$ethX['IPADDR:0'];
+ case 'ipv6':
+ return $$ethX['IPADDR6:0'];
+ case 'ipv4+ipv6':
+ switch ($prot) {
+ case 4: return $$ethX['IPADDR:0'];
+ case 6: return $$ethX['IPADDR6:0'];
+ default:return [$$ethX['IPADDR:0'],$$ethX['IPADDR6:0']];}
+ default:
+ return $$ethX['IPADDR:0'];
+ }
+ }
+}
+$configErrorEnum = [ // used to map $var['configValid'] value to mimic unraid-api's `configError` ENUM
+ "error" => 'UNKNOWN_ERROR',
+ "invalid" => 'INVALID',
+ "nokeyserver" => 'NO_KEY_SERVER',
+ "withdrawn" => 'WITHDRAWN',
+];
+// read flashbackup ini file
+$flashbackup_ini = '/var/local/emhttp/flashbackup.ini';
+$flashbackup_status = (file_exists($flashbackup_ini)) ? @parse_ini_file($flashbackup_ini) : [];
+
+$nginx = parse_ini_file('/var/local/emhttp/nginx.ini');
+
+$pluginInstalled = '';
+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
+} 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';
+ // 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';
+}
+
$serverData = [
- "avatar" => (!empty($myservers['remote']['avatar']) && $plgInstalled) ? $myservers['remote']['avatar'] : '',
+ "apiKey" => $myservers['upc']['apikey'] ?? '',
+ "apiVersion" => $myservers['api']['version'] ?? '',
+ "avatar" => (!empty($myservers['remote']['avatar']) && $pluginInstalled) ? $myservers['remote']['avatar'] : '',
+ "banner" => $display['banner'] ?? '',
+ "bannerGradient" => $display['showBannerGradient'] ?? 'yes',
+ "bgColor" => ($backgnd) ? '#'.$backgnd : '',
"config" => [
'valid' => $var['configValid'] === 'yes',
'error' => $var['configValid'] !== 'yes'
? (array_key_exists($var['configValid'], $configErrorEnum) ? $configErrorEnum[$var['configValid']] : 'UNKNOWN_ERROR')
: null,
],
+ "csrf" => $var['csrf_token'],
+ "description" => $var['COMMENT'],
+ "descriptionShow" => ($display['headerdescription']??''!='no') ? 'true' : '',
"deviceCount" => $var['deviceCount'],
"email" => $myservers['remote']['email'] ?? '',
+ "expireTime" => 1000*($var['regTy']=='Trial'||strstr($var['regTy'],'expired')?$var['regTm2']:0),
"extraOrigins" => explode(',', $myservers['api']['extraOrigins']??''),
- "flashproduct" => $var['flashProduct'],
- "flashvendor" => $var['flashVendor'],
+ "flashProduct" => $var['flashProduct'],
+ "flashVendor" => $var['flashVendor'],
"flashBackupActivated" => empty($flashbackup_status['activated']) ? '' : 'true',
"guid" => $var['flashGUID'],
"hasRemoteApikey" => !empty($myservers['remote']['apikey']),
- "internalip" => ipaddr(),
- "internalport" => $_SERVER['SERVER_PORT'],
+ "internalIp" => ipaddr(),
+ "internalPort" => $_SERVER['SERVER_PORT'],
"keyfile" => empty($var['regFILE'])? "" : str_replace(['+','/','='], ['-','_',''], trim(base64_encode(@file_get_contents($var['regFILE'])))),
- "locale" => 'en',
+ "locale" => ($_SESSION['locale']) ? $_SESSION['locale'] : 'en_US',
+ "metaColor" => ($display['headermetacolor']??'') ? '#'.$display['headermetacolor'] : '',
+ "model" => $var['SYS_MODEL'],
+ "name" => $var['NAME'],
"osVersion" => $var['version'],
"plgVersion" => $plgversion = file_exists('/var/log/plugins/dynamix.unraid.net.plg')
? trim(@exec('/usr/local/sbin/plugin version /var/log/plugins/dynamix.unraid.net.plg 2>/dev/null'))
: ( 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'] ),
- "plgInstalled" => $plgInstalled,
+ "pluginInstalled" => $pluginInstalled,
"protocol" => $_SERVER['REQUEST_SCHEME'],
- "reggen" => (int)$var['regGen'],
+ "regGen" => (int)$var['regGen'],
"regGuid" => $var['regGUID'],
- "registered" => (!empty($myservers['remote']['username']) && $plgInstalled),
- "name" => $var['NAME'],
+ "registered" => (!empty($myservers['remote']['username']) && $pluginInstalled),
+ "registeredTime" => $myservers['remote']['regWizTime'] ?? '',
"site" => $_SERVER['REQUEST_SCHEME']."://".$_SERVER['HTTP_HOST'],
"state" => strtoupper(empty($var['regCheck']) ? $var['regTy'] : $var['regCheck']),
+ "textColor" => ($header) ? '#'.$header : '',
+ "theme" => $display['theme'],
"ts" => time(),
- "username" => (!empty($myservers['remote']['username']) && $plgInstalled) ? $myservers['remote']['username'] : '',
+ "uptime" => 1000*(time() - round(strtok(exec("cat /proc/uptime"),' '))),
+ "username" => (!empty($myservers['remote']['username']) && $pluginInstalled) ? $myservers['remote']['username'] : '',
"wanFQDN" => $nginx['NGINX_WANFQDN'] ?? '',
];
-echo "";
+$themeBg = '#111';
+if ($display['theme'] === 'black' || $display['theme'] === 'azure') {
+ $themeBg = '#fff';
+}
?>
-
-
-
-
+
+
+ =""?>
+
+
+
+
+
+
+
+
+
+
diff --git a/app.vue b/app.vue
index c4016a264..0d07d7915 100644
--- a/app.vue
+++ b/app.vue
@@ -26,9 +26,6 @@ onBeforeMount(() => {
WanIpCheckCe
-
- CallbackHandlerCe
-
Web Components
@@ -46,9 +43,6 @@ onBeforeMount(() => {
WanIpCheckCe
-
- CallbackHandlerCe
-
diff --git a/components/CallbackHandler.ce.vue b/components/CallbackHandler.ce.vue
index 42edfd5e1..02ac28ff0 100644
--- a/components/CallbackHandler.ce.vue
+++ b/components/CallbackHandler.ce.vue
@@ -1,8 +1,9 @@
+
@@ -82,7 +92,7 @@ onBeforeMount(() => {
-
+
{{ description }}
•
@@ -99,12 +109,10 @@ onBeforeMount(() => {
-
-
-
-
-
-
+
+
+
+
diff --git a/components/UserProfile/CallbackFeedback.vue b/components/UserProfile/CallbackFeedback.vue
new file mode 100644
index 000000000..f819db774
--- /dev/null
+++ b/components/UserProfile/CallbackFeedback.vue
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
{{ JSON.stringify(decryptedData, null, 2) }}
+
+
+
+
+
+
+
+
+
diff --git a/components/UserProfile/DropdownError.vue b/components/UserProfile/DropdownError.vue
index 5bf631854..1c66d7111 100644
--- a/components/UserProfile/DropdownError.vue
+++ b/components/UserProfile/DropdownError.vue
@@ -22,10 +22,10 @@ const links = ref([
-
diff --git a/components/UserProfile/Promo.vue b/components/UserProfile/Promo.vue
index 1d36038e4..fd7964d91 100644
--- a/components/UserProfile/Promo.vue
+++ b/components/UserProfile/Promo.vue
@@ -88,7 +88,7 @@ const installButtonClasses = 'text-white text-14px text-center w-full flex flex-
-
+
Install Staging
diff --git a/components/UserProfile/ServerStateBuy.vue b/components/UserProfile/ServerStateBuy.vue
index d5f9117c9..1754bd557 100644
--- a/components/UserProfile/ServerStateBuy.vue
+++ b/components/UserProfile/ServerStateBuy.vue
@@ -1,6 +1,6 @@
-
diff --git a/nuxt.config.ts b/nuxt.config.ts
index d557db929..9abc4e160 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -29,10 +29,6 @@ export default defineNuxtConfig({
name: 'ConnectAuth',
path: '@/components/Auth.ce',
},
- {
- name: 'ConnectCallbackHandler',
- path: '@/components/CallbackHandler.ce',
- },
{
name: 'ConnectDownloadApiLogs',
path: '@/components/DownloadApiLogs.ce',
diff --git a/store/account.ts b/store/account.ts
new file mode 100644
index 000000000..7c0a01d72
--- /dev/null
+++ b/store/account.ts
@@ -0,0 +1,66 @@
+import { useToggle } from '@vueuse/core';
+import { defineStore, createPinia, setActivePinia } from "pinia";
+import { useCallbackStore } from './callback';
+import { useServerStore } from './server';
+
+/**
+ * @see https://stackoverflow.com/questions/73476371/using-pinia-with-vue-js-web-components
+ * @see https://github.com/vuejs/pinia/discussions/1085
+ */
+setActivePinia(createPinia());
+
+export const useAccountStore = defineStore('account', () => {
+ const callbackStore = useCallbackStore();
+ const serverStore = useServerStore();
+ // State
+ const accountVisible = ref(false);
+ // Actions
+ const recover = () => {
+ console.debug('[recover]');
+ callbackStore.send('https://account.unraid.net', {
+ ...serverStore.serverAccountPayload,
+ type: 'recover',
+ });
+ };
+ const replace = () => {
+ console.debug('[replace]');
+ callbackStore.send('https://account.unraid.net', {
+ ...serverStore.serverAccountPayload,
+ type: 'replace',
+ });
+ };
+ const signIn = () => {
+ console.debug('[signIn]');
+ callbackStore.send('https://account.unraid.net', {
+ ...serverStore.serverAccountPayload,
+ type: 'signIn',
+ });
+ };
+ const signOut = () => {
+ console.debug('[signOut]');
+ callbackStore.send('https://account.unraid.net', {
+ ...serverStore.serverAccountPayload,
+ type: 'signOut',
+ });
+ };
+ const accountHide = () => accountVisible.value = false;
+ const accountShow = () => accountVisible.value = true;
+ const accountToggle = useToggle(accountVisible);
+
+ watch(accountVisible, (newVal, _oldVal) => {
+ console.debug('[accountVisible]', newVal, _oldVal);
+ });
+
+ return {
+ // State
+ accountVisible,
+ accountHide,
+ accountShow,
+ // Actions
+ recover,
+ replace,
+ signIn,
+ signOut,
+ accountToggle,
+ };
+});
diff --git a/store/callback.ts b/store/callback.ts
index a6e7161cc..9401f3ed2 100644
--- a/store/callback.ts
+++ b/store/callback.ts
@@ -1,7 +1,11 @@
import AES from 'crypto-js/aes';
import Utf8 from 'crypto-js/enc-utf8';
import { defineStore, createPinia, setActivePinia } from "pinia";
-import { useServerStore } from './server';
+import type { CallbackSendPayload } from '~/types/callback';
+import type {
+ ServerAccountCallbackSendPayload,
+ ServerPurchaseCallbackSendPayload,
+} from '~/types/server';
/**
* @see https://stackoverflow.com/questions/73476371/using-pinia-with-vue-js-web-components
* @see https://github.com/vuejs/pinia/discussions/1085
@@ -9,22 +13,19 @@ import { useServerStore } from './server';
setActivePinia(createPinia());
export const useCallbackStore = defineStore('callback', () => {
- const serverStore = useServerStore();
// store helpers
// const config = useRuntimeConfig(); // results in a nuxt error after web components are built
// const encryptKey = config.public.callbackKey;
const encryptKey = 'Uyv2o8e*FiQe8VeLekTqyX6Z*8XonB';
- console.debug('[useCallbackStore]', { encryptKey });
// state
- const encryptedMessage = ref('');
+ const callbackFeedbackVisible = ref(false);
const decryptedData = ref();
- // getters
-
+ const encryptedMessage = ref('');
// actions
- const send = (url: string = 'https://unraid.ddev.site/init-purchase') => {
+ const send = (url: string = 'https://unraid.ddev.site/init-purchase', payload: CallbackSendPayload) => {
console.debug('[send]');
const stringifiedData = JSON.stringify({
- ...serverStore.server,
+ ...payload,
sender: window.location.href,
});
// @todo don't save to store
@@ -42,14 +43,16 @@ export const useCallbackStore = defineStore('callback', () => {
console.debug('[watcher]', currentUrl);
const callbackValue = currentUrl.searchParams.get('data');
if (!callbackValue) {
- console.debug('[watcher] no callback to handle');
- return;
+ return console.debug('[watcher] no callback to handle');
}
const decryptedMessage = AES.decrypt(callbackValue, encryptKey);
decryptedData.value = JSON.parse(decryptedMessage.toString(Utf8));
console.debug('[watcher]', decryptedMessage, decryptedData.value);
if (!decryptedData.value) return console.error('Callback Watcher: Data not present');
if (!decryptedData.value.action) return console.error('Callback Watcher: Required "action" not present');
+ // Display the feedback modal
+ show();
+ // Parse the data and perform actions
switch (decryptedData.value.action) {
case 'install':
console.debug(`Installing key ${decryptedData.value.keyUrl}\n\nOEM: ${decryptedData.value.oem}\n\nSender: ${decryptedData.value.sender}`);
@@ -63,5 +66,26 @@ export const useCallbackStore = defineStore('callback', () => {
}
};
- return { send, watcher, decryptedData };
+ const hide = () => callbackFeedbackVisible.value = false;
+ const show = () => callbackFeedbackVisible.value = true;
+ const toggle = useToggle(callbackFeedbackVisible);
+
+ /**
+ * @todo consider removing query string once actions are done
+ */
+ watch(callbackFeedbackVisible, (newVal, _oldVal) => {
+ console.debug('[callbackFeedbackVisible]', newVal, _oldVal);
+ });
+
+ return {
+ // state
+ decryptedData,
+ callbackFeedbackVisible,
+ // actions
+ send,
+ watcher,
+ hide,
+ show,
+ toggle,
+ };
});
\ No newline at end of file
diff --git a/store/purchase.ts b/store/purchase.ts
new file mode 100644
index 000000000..ddeb31acd
--- /dev/null
+++ b/store/purchase.ts
@@ -0,0 +1,58 @@
+import { useToggle } from '@vueuse/core';
+import { defineStore, createPinia, setActivePinia } from "pinia";
+import { useCallbackStore } from './callback';
+import { useServerStore } from './server';
+
+/**
+ * @see https://stackoverflow.com/questions/73476371/using-pinia-with-vue-js-web-components
+ * @see https://github.com/vuejs/pinia/discussions/1085
+ */
+setActivePinia(createPinia());
+
+export const usePurchaseStore = defineStore('purchase', () => {
+ const callbackStore = useCallbackStore();
+ const serverStore = useServerStore();
+ // State
+ const purchaseVisible = ref(false);
+ // Actions
+ const redeem = () => {
+ console.debug('[redeem]');
+ callbackStore.send('https://unraid.ddev.site/init-purchase', {
+ ...serverStore.serverPurchasePayload,
+ type: 'redeem',
+ });
+ };
+ const purchase = () => {
+ console.debug('[purchase]');
+ callbackStore.send('https://unraid.ddev.site/init-purchase', {
+ ...serverStore.serverPurchasePayload,
+ type: 'purchase',
+ });
+ };
+ const upgrade = () => {
+ console.debug('[upgrade]');
+ callbackStore.send('https://unraid.ddev.site/init-purchase', {
+ ...serverStore.serverPurchasePayload,
+ type: 'upgrade',
+ });
+ };
+ const purchaseHide = () => purchaseVisible.value = false;
+ const purchaseShow = () => purchaseVisible.value = true;
+ const purchaseToggle = useToggle(purchaseVisible);
+
+ watch(purchaseVisible, (newVal, _oldVal) => {
+ console.debug('[purchaseVisible]', newVal, _oldVal);
+ });
+
+ return {
+ // State
+ purchaseVisible,
+ purchaseHide,
+ purchaseShow,
+ // Actions
+ purchaseToggle,
+ redeem,
+ purchase,
+ upgrade,
+ };
+});
diff --git a/store/server.ts b/store/server.ts
index 1252b62a2..ef111679e 100644
--- a/store/server.ts
+++ b/store/server.ts
@@ -1,7 +1,13 @@
import { defineStore, createPinia, setActivePinia } from "pinia";
import { ArrowRightOnRectangleIcon, GlobeAltIcon, KeyIcon } from '@heroicons/vue/24/solid';
+
+import { useAccountStore } from './account';
+import { usePurchaseStore } from "./purchase";
+import { useTrialStore } from './trial';
import type {
Server,
+ ServerAccountCallbackSendPayload,
+ ServerPurchaseCallbackSendPayload,
ServerState,
ServerStateData,
ServerStateDataAction,
@@ -13,6 +19,9 @@ import type {
setActivePinia(createPinia());
export const useServerStore = defineStore('server', () => {
+ const accountStore = useAccountStore();
+ const purchaseStore = usePurchaseStore();
+ const trialStore = useTrialStore();
/**
* State
*/
@@ -80,64 +89,85 @@ export const useServerStore = defineStore('server', () => {
}
});
+ const serverPurchasePayload = computed((): ServerPurchaseCallbackSendPayload => {
+ return {
+ deviceCount: deviceCount.value,
+ guid: guid.value,
+ }
+ });
+
+ const serverAccountPayload = computed((): ServerAccountCallbackSendPayload => {
+ return {
+ description: description.value,
+ expireTime: expireTime.value,
+ flashProduct: flashProduct.value,
+ flashVendor: flashVendor.value,
+ guid: guid.value,
+ keyfile: keyfile.value,
+ name: name.value,
+ state: state.value,
+ wanFQDN: wanFQDN.value,
+ }
+ });
+
const purchaseAction: ServerStateDataAction = {
- click: () => { console.debug('purchase') },
+ click: () => { purchaseStore.purchase() },
external: true,
icon: KeyIcon,
name: 'purchase',
text: 'Purchase Key',
};
const upgradeAction: ServerStateDataAction = {
- click: () => { console.debug('upgrade') },
+ click: () => { purchaseStore.upgrade() },
external: true,
icon: KeyIcon,
name: 'upgrade',
text: 'Upgrade Key',
};
const recoverAction: ServerStateDataAction = {
- click: () => { console.debug('recover') },
+ click: () => { accountStore.recover() },
external: true,
icon: KeyIcon,
name: 'recover',
text: 'Recover Key',
};
const redeemAction: ServerStateDataAction = {
- click: () => { console.debug('redeem') },
+ click: () => { purchaseStore.redeem() },
external: true,
icon: KeyIcon,
name: 'redeem',
text: 'Redeem Activation Code',
};
const replaceAction: ServerStateDataAction = {
- click: () => { console.debug('replace') },
+ click: () => { accountStore.replace() },
external: true,
icon: KeyIcon,
name: 'replace',
text: 'Replace Key',
};
const signInAction: ServerStateDataAction = {
- click: () => { console.debug('signIn') },
+ click: () => { accountStore.signIn() },
external: true,
icon: GlobeAltIcon,
name: 'signIn',
text: 'Sign In with Unraid.net Account',
};
const signOutAction: ServerStateDataAction = {
- click: () => { console.debug('signOut') },
+ click: () => { accountStore.signOut() },
external: true,
icon: ArrowRightOnRectangleIcon,
name: 'signOut',
text: 'Sign Out of Unraid.net',
};
const trialExtendAction: ServerStateDataAction = {
- click: () => { console.debug('trialExtend') },
+ click: () => { trialStore.extend() },
external: true,
icon: ArrowRightOnRectangleIcon,
name: 'trialExtend',
text: 'Extend Trial',
};
const trialStartAction: ServerStateDataAction = {
- click: () => { console.debug('trialStart') },
+ click: () => { trialStore.start() },
external: true,
icon: ArrowRightOnRectangleIcon,
name: 'trialStart',
@@ -176,6 +206,7 @@ export const useServerStore = defineStore('server', () => {
...(trialExtensionEligible.value ? [trialExtendAction] : []),
...(registered.value ? [signOutAction] : []),
],
+ error: true,
humanReadable: 'Trial Expired',
heading: 'Your Trial has expired',
message: trialExtensionEligible.value
@@ -236,6 +267,7 @@ export const useServerStore = defineStore('server', () => {
...([purchaseAction, redeemAction, replaceAction]),
...(registered.value ? [signOutAction] : []),
],
+ error: true,
humanReadable: 'Flash GUID Error',
heading: 'Registration key / USB Flash GUID mismatch',
message: messageEGUID,
@@ -247,10 +279,11 @@ export const useServerStore = defineStore('server', () => {
...(registered.value ? [purchaseAction, redeemAction] : []),
...(registered.value ? [signOutAction] : []),
],
+ error: true,
humanReadable: 'Multiple License Keys Present',
heading: 'Multiple License Keys Present',
message: 'There are multiple license key files present on your USB flash device and none of them correspond to the USB Flash boot device. Please remove all key files, except the one you want to replace, from the /config directory on your USB Flash boot device. Alternately you may purchase a license key for this USB flash device. If you want to replace one of your license keys with a new key bound to this USB Flash device, please first remove all other key files first.',
- // signInToFix: true, // @todo
+ // signInToFix: true, // @todo is this needed?
};
case 'ENOKEYFILE2':
return {
@@ -259,6 +292,7 @@ export const useServerStore = defineStore('server', () => {
...([purchaseAction, redeemAction]),
...(registered.value ? [recoverAction, signOutAction] : []),
],
+ error: true,
humanReadable: 'Missing key file',
heading: 'Missing key file',
message: '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. If you do not have a backup copy of your license key file you may install the Connect (beta) plugin to attempt to recover your key. If this was an expired Trial installation, you may purchase a license key.',
@@ -270,6 +304,7 @@ export const useServerStore = defineStore('server', () => {
...([purchaseAction, redeemAction]),
...(registered.value ? [signOutAction] : []),
],
+ error: true,
humanReadable: 'Invalid installation',
heading: 'Invalid installation',
message: 'It is not possible to use a Trial key with an existing Unraid OS installation. You may purchase a license key corresponding to this USB Flash device to continue using this installation.',
@@ -281,6 +316,7 @@ export const useServerStore = defineStore('server', () => {
...([purchaseAction, redeemAction]),
...(registered.value ? [signOutAction] : []),
],
+ error: true,
humanReadable: 'No Keyfile',
heading: 'No USB flash configuration data',
message: 'There is a problem with your USB Flash device',
@@ -294,36 +330,42 @@ export const useServerStore = defineStore('server', () => {
case 'ENOFLASH6':
case 'ENOFLASH7':
return {
+ error: true,
humanReadable: 'No Flash',
heading: 'Cannot access your USB Flash boot device',
message: 'There is a physical problem accessing your USB Flash boot device',
};
case 'EBLACKLISTED':
return {
+ error: true,
humanReadable: 'BLACKLISTED',
heading: 'Blacklisted USB Flash GUID',
message: 'This USB Flash boot device has been blacklisted. This can occur as a result of transferring your license key to a replacement USB Flash device, and you are currently booted from your old USB Flash device. A USB Flash device may also be blacklisted if we discover the serial number is not unique – this is common with USB card readers.',
};
case 'EBLACKLISTED1':
return {
+ error: true,
humanReadable: 'BLACKLISTED',
heading: 'USB Flash device error',
message: 'This USB Flash device has an invalid GUID. Please try a different USB Flash device',
};
case 'EBLACKLISTED2':
return {
+ error: true,
humanReadable: 'BLACKLISTED',
heading: 'USB Flash has no serial number',
message: 'This USB Flash boot device has been blacklisted. This can occur as a result of transferring your license key to a replacement USB Flash device, and you are currently booted from your old USB Flash device. A USB Flash device may also be blacklisted if we discover the serial number is not unique – this is common with USB card readers.',
};
case 'ENOCONN':
return {
+ error: true,
humanReadable: 'Trial Requires Internet Connection',
heading: 'Cannot validate Unraid Trial key',
message: 'Your Trial key requires an internet connection. Please check Settings > Network',
};
default:
return {
+ error: true,
humanReadable: 'Stale',
heading: 'Stale Server',
message: 'Please refresh the page to ensure you load your latest configuration',
@@ -347,29 +389,30 @@ export const useServerStore = defineStore('server', () => {
* Actions
*/
const setServer = (data: Server) => {
- console.debug('[setServer]', data);
- if (data?.apiKey) apiKey.value = data.apiKey;
- if (data?.avatar) avatar.value = data.avatar;
- if (data?.description) description.value = data.description;
- if (data?.deviceCount) deviceCount.value = data.deviceCount;
- if (data?.expireTime) expireTime.value = data.expireTime;
- if (data?.flashProduct) flashProduct.value = data.flashProduct;
- if (data?.flashVendor) flashVendor.value = data.flashVendor;
- if (data?.guid) guid.value = data.guid;
- if (data?.keyfile) keyfile.value = data.keyfile;
- if (data?.lanIp) lanIp.value = data.lanIp;
- if (data?.license) license.value = data.license;
- if (data?.locale) locale.value = data.locale;
- if (data?.name) name.value = data.name;
- if (data?.pluginInstalled) pluginInstalled.value = data.pluginInstalled;
- if (data?.registered) registered.value = data.registered;
- if (data?.regGen) regGen.value = data.regGen;
- if (data?.regGuid) regGuid.value = data.regGuid;
- if (data?.site) site.value = data.site;
- if (data?.state) state.value = data.state;
- if (data?.uptime) uptime.value = data.uptime;
- if (data?.username) username.value = data.username;
- if (data?.wanFQDN) wanFQDN.value = data.wanFQDN;
+ 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?.description !== 'undefined') description.value = data.description;
+ if (typeof data?.deviceCount !== 'undefined') deviceCount.value = data.deviceCount;
+ if (typeof data?.expireTime !== 'undefined') expireTime.value = data.expireTime;
+ if (typeof data?.flashProduct !== 'undefined') flashProduct.value = data.flashProduct;
+ if (typeof data?.flashVendor !== 'undefined') flashVendor.value = data.flashVendor;
+ if (typeof data?.guid !== 'undefined') guid.value = data.guid;
+ if (typeof data?.keyfile !== 'undefined') keyfile.value = data.keyfile;
+ if (typeof data?.lanIp !== 'undefined') lanIp.value = data.lanIp;
+ 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?.registered !== 'undefined') registered.value = data.registered;
+ if (typeof data?.regGen !== 'undefined') regGen.value = data.regGen;
+ if (typeof data?.regGuid !== 'undefined') regGuid.value = data.regGuid;
+ if (typeof data?.site !== 'undefined') site.value = data.site;
+ if (typeof data?.state !== 'undefined') state.value = data.state;
+ if (typeof data?.uptime !== 'undefined') uptime.value = data.uptime;
+ if (typeof data?.username !== 'undefined') username.value = data.username;
+ if (typeof data?.wanFQDN !== 'undefined') wanFQDN.value = data.wanFQDN;
+ console.debug('[setServer] server.value', server.value);
};
return {
@@ -397,6 +440,8 @@ export const useServerStore = defineStore('server', () => {
keyActions,
pluginOutdated,
server,
+ serverAccountPayload,
+ serverPurchasePayload,
stateData,
// actions
setServer,
diff --git a/store/trial.ts b/store/trial.ts
new file mode 100644
index 000000000..58e776e03
--- /dev/null
+++ b/store/trial.ts
@@ -0,0 +1,37 @@
+import { useToggle } from '@vueuse/core';
+import { defineStore, createPinia, setActivePinia } from "pinia";
+import { useCallbackStore } from './callback';
+import { useServerStore } from './server';
+
+/**
+ * @see https://stackoverflow.com/questions/73476371/using-pinia-with-vue-js-web-components
+ * @see https://github.com/vuejs/pinia/discussions/1085
+ */
+setActivePinia(createPinia());
+
+export const useTrialStore = defineStore('trial', () => {
+ const callbackStore = useCallbackStore();
+ const serverStore = useServerStore();
+
+ const extend = () => {
+ console.debug('[extend]');
+ callbackStore.send('https://account.unraid.net', {
+ ...serverStore.serverAccountPayload,
+ type: 'trialExtend',
+ });
+ };
+ const start = () => {
+ console.debug('[start]');
+ callbackStore.send('https://account.unraid.net', {
+ ...serverStore.serverAccountPayload,
+ type: 'trialStart',
+ });
+ };
+
+ return {
+ // State
+ // Actions
+ extend,
+ start,
+ };
+});
diff --git a/types/callback.ts b/types/callback.ts
new file mode 100644
index 000000000..c7647fcfa
--- /dev/null
+++ b/types/callback.ts
@@ -0,0 +1,9 @@
+import type {
+ ServerAccountCallbackSendPayload,
+ ServerPurchaseCallbackSendPayload,
+ ServerStateDataActionType,
+} from '~/types/server';
+
+export interface CallbackSendPayload extends ServerAccountCallbackSendPayload, ServerPurchaseCallbackSendPayload {
+ type: ServerStateDataActionType;
+}
\ No newline at end of file
diff --git a/types/server.ts b/types/server.ts
index cd9102c83..6cd749990 100644
--- a/types/server.ts
+++ b/types/server.ts
@@ -46,7 +46,27 @@ export interface Server {
wanIp?: string;
}
-// @todo convert to object with text and click payload
+export interface ServerAccountCallbackSendPayload {
+ description?: string;
+ deviceCount?: number;
+ expireTime?: number;
+ flashProduct?: string;
+ flashVendor?: string;
+ guid?: string;
+ keyfile?: string;
+ locale?: string;
+ name?: string;
+ regGen?: number;
+ regGuid?: string;
+ state: string;
+ wanFQDN?: string;
+}
+
+export interface ServerPurchaseCallbackSendPayload {
+ deviceCount?: number;
+ guid?: string;
+}
+
export type ServerStateDataActionType = 'signIn'|'signOut'|'purchase'|'redeem'|'upgrade'|'recover'|'replace'|'trialExtend'|'trialStart';
export interface ServerStateDataAction extends UserProfileLink {
@@ -64,6 +84,6 @@ export interface ServerStateData {
humanReadable: string; // @todo create interface of ENUM to string mapping
heading?: string;
message?: string;
- error?: ServerStateDataError;
+ error?: ServerStateDataError | boolean;
withKey?: boolean; // @todo potentially remove
}
\ No newline at end of file