refactor: callback finished refreshServerState

This commit is contained in:
Zack Spear
2023-07-20 16:29:45 -07:00
parent 775b7fcd1e
commit 472e3fdba2
15 changed files with 278 additions and 249 deletions

View File

@@ -24,70 +24,5 @@ body {
--ring-shadow: 0 0 --var(--color-beta);
}
.DropdownWrapper_blip {
box-shadow: var(--ring-offset-shadow), var(--ring-shadow), var(--shadow-beta);
&::before {
@apply absolute z-20 block;
content: '';
width: 0;
height: 0;
top: -10px;
right: 42px;
border-right: 11px solid transparent;
border-bottom: 11px solid var(--color-alpha);
border-left: 11px solid transparent;
}
}
.unraid_mark_2,
.unraid_mark_4 {
animation: mark_2 1.5s ease infinite;
}
.unraid_mark_3 {
animation: mark_3 1.5s ease infinite;
}
.unraid_mark_6,
.unraid_mark_8 {
animation: mark_6 1.5s ease infinite;
}
.unraid_mark_7 {
animation: mark_7 1.5s ease infinite;
}
@keyframes mark_2 {
50% {
transform: translateY(-40px);
}
100% {
transform: translateY(0);
}
}
@keyframes mark_3 {
50% {
transform: translateY(-62px);
}
100% {
transform: translateY(0);
}
}
@keyframes mark_6 {
50% {
transform: translateY(40px);
}
100% {
transform: translateY(0);
}
}
@keyframes mark_7 {
50% {
transform: translateY(62px);
}
100% {
transform: translateY(0);
}
}
/* Ensure this is always at the bottom @see https://tailwindcss.com/docs/content-configuration#working-with-third-party-libraries */
@tailwind utilities;

View File

@@ -5,14 +5,12 @@ import '~/assets/main.css';
export interface Props {
gradientStart?: string;
gradientStop?: string;
height?: number,
title?: string,
}
withDefaults(defineProps<Props>(), {
gradientStart: '#e32929',
gradientStop: '#ff8d30',
height: 64,
title: 'Loading',
});
</script>
@@ -22,7 +20,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="`unraid_mark h-[${height}px]`"
:class="`unraid_mark`"
role="img"
>
<title>{{ title }}</title>

View File

@@ -24,4 +24,69 @@ const { trialModalVisible } = storeToRefs(useTrialStore());
@tailwind base;
@tailwind components;
@tailwind utilities;
.DropdownWrapper_blip {
box-shadow: var(--ring-offset-shadow), var(--ring-shadow), var(--shadow-beta);
&::before {
@apply absolute z-20 block;
content: '';
width: 0;
height: 0;
top: -10px;
right: 42px;
border-right: 11px solid transparent;
border-bottom: 11px solid var(--color-alpha);
border-left: 11px solid transparent;
}
}
.unraid_mark_2,
.unraid_mark_4 {
animation: mark_2 1.5s ease infinite;
}
.unraid_mark_3 {
animation: mark_3 1.5s ease infinite;
}
.unraid_mark_6,
.unraid_mark_8 {
animation: mark_6 1.5s ease infinite;
}
.unraid_mark_7 {
animation: mark_7 1.5s ease infinite;
}
@keyframes mark_2 {
50% {
transform: translateY(-40px);
}
100% {
transform: translateY(0);
}
}
@keyframes mark_3 {
50% {
transform: translateY(-62px);
}
100% {
transform: translateY(0);
}
}
@keyframes mark_6 {
50% {
transform: translateY(40px);
}
100% {
transform: translateY(0);
}
}
@keyframes mark_7 {
50% {
transform: translateY(62px);
}
100% {
transform: translateY(0);
}
}
</style>

View File

@@ -75,6 +75,8 @@ onBeforeMount(() => {
*/
callbackStore.watcher();
});
watch(description, (value) => console.debug('[watch:description]', value));
</script>
<template>
@@ -117,4 +119,69 @@ onBeforeMount(() => {
@tailwind base;
@tailwind components;
@tailwind utilities;
.DropdownWrapper_blip {
box-shadow: var(--ring-offset-shadow), var(--ring-shadow), var(--shadow-beta);
&::before {
@apply absolute z-20 block;
content: '';
width: 0;
height: 0;
top: -10px;
right: 42px;
border-right: 11px solid transparent;
border-bottom: 11px solid var(--color-alpha);
border-left: 11px solid transparent;
}
}
.unraid_mark_2,
.unraid_mark_4 {
animation: mark_2 1.5s ease infinite;
}
.unraid_mark_3 {
animation: mark_3 1.5s ease infinite;
}
.unraid_mark_6,
.unraid_mark_8 {
animation: mark_6 1.5s ease infinite;
}
.unraid_mark_7 {
animation: mark_7 1.5s ease infinite;
}
@keyframes mark_2 {
50% {
transform: translateY(-40px);
}
100% {
transform: translateY(0);
}
}
@keyframes mark_3 {
50% {
transform: translateY(-62px);
}
100% {
transform: translateY(0);
}
}
@keyframes mark_6 {
50% {
transform: translateY(40px);
}
100% {
transform: translateY(0);
}
}
@keyframes mark_7 {
50% {
transform: translateY(62px);
}
100% {
transform: translateY(0);
}
}
</style>

View File

@@ -26,7 +26,6 @@ const promoStore = usePromoStore();
const serverStore = useServerStore();
const {
accountAction,
accountActionHide,
accountActionStatus,
accountActionStatusCopy,
@@ -46,12 +45,9 @@ const {
connectPluginInstalled,
registered,
authAction,
refreshedServerState,
} = storeToRefs(serverStore);
/** @todo if post purchase/upgrade thank user for their purchase and support */
/** @todo if post purchase/upgrade and no Connect, show CTA to Connect promo */
/** @todo if signing in show CTA to head to Connect settings to enable features */
/**
* Post sign in success state:
* If we're on the Connect settings page in the webGUI
@@ -92,10 +88,15 @@ const subheading = computed(() => {
return '';
});
const closeText = computed(() => {
const txt = !connectPluginInstalled.value ? 'No Thanks' : 'Close'
return refreshedServerState.value ? txt : 'Reload';
});
const close = () => {
if (callbackStatus.value === 'loading') return console.debug('[close] not allowed');
window.location.reload();
// callbackActionsStore.setCallbackStatus('ready');
return refreshedServerState.value
? callbackActionsStore.setCallbackStatus('ready')
: window.location.reload();
};
const promoClick = () => {
@@ -122,6 +123,8 @@ const { text, copy, copied, isSupported } = useClipboard({ source: keyUrl.value
v-if="keyInstallStatus !== 'ready' || accountActionStatus !== 'ready'"
class="text-center relative w-full flex flex-col justify-center gap-y-16px py-24px sm:py-32px"
>
<BrandLoading v-if="callbackStatus === 'loading'" class="w-[110px] mx-auto" />
<UpcCallbackFeedbackStatus
v-if="keyInstallStatus !== 'ready'"
:success="keyInstallStatus === 'success'"
@@ -192,7 +195,7 @@ const { text, copy, copied, isSupported } = useClipboard({ source: keyUrl.value
<BrandButton
@click="close"
btn-style="underline"
:text="!connectPluginInstalled ? 'No Thanks' : 'Close'" />
:text="closeText" />
</div>
</template>
</Modal>

View File

@@ -5,28 +5,28 @@ type ApiOnlineStatus = 'online'|'offline';
const onlineStatus = ref<ApiOnlineStatus>('online');
const apiLoading = ref(false);
import { useQuery } from '@vue/apollo-composable';
// import { useQuery } from '@vue/apollo-composable';
import {
TEST_FRAGMENT,
TEST_QUERY,
} from './DropdownConnectStatus.fragment';
import { useFragment } from '@/composables/gql/fragment-masking';
// import {
// TEST_FRAGMENT,
// TEST_QUERY,
// } from './DropdownConnectStatus.fragment';
// import { useFragment } from '@/composables/gql/fragment-masking';
const { result: newResult } = useQuery(
TEST_QUERY,
);
const result = computed(() => useFragment(TEST_FRAGMENT, newResult.value?.cloud));
// const { result: newResult } = useQuery(
// TEST_QUERY,
// );
// const result = computed(() => useFragment(TEST_FRAGMENT, newResult.value?.cloud));
watch(result, (newVal, oldVal) => {
console.log('result', newVal, oldVal);
});
// watch(result, (newVal, oldVal) => {
// console.log('result', newVal, oldVal);
// });
</script>
<template>
<li class="px-8px flex flex-col items-center">
<template v-if="apiLoading">
<BrandLoading class="w-36px mx-auto" :height="21" />
<BrandLoading class="w-36px mx-auto" />
<span class="text-12px italic opacity-80">{{ 'Loading Connect status…' }}</span>
</template>
<span

View File

@@ -20,10 +20,6 @@ const documents = {
"\n fragment FragmentRegistration on Registration {\n state\n expiration\n keyFile {\n contents\n }\n }\n": types.FragmentRegistrationFragmentDoc,
"\n fragment FragmentVars on Vars {\n regGen\n regState\n configError\n configValid\n }\n": types.FragmentVarsFragmentDoc,
"\n query serverState {\n owner {\n ...FragmentOwner\n }\n info {\n os {\n hostname\n }\n }\n registration {\n ...FragmentRegistration\n }\n crashReportingEnabled\n vars {\n ...FragmentVars\n }\n config {\n ...FragmentConfig\n }\n cloud {\n error\n apiKey {\n valid\n error\n }\n relay {\n status\n error\n }\n cloud {\n status\n error\n }\n }\n }\n": types.serverStateDocument,
"\n subscription Config {\n config {\n ...FragmentConfig\n }\n }\n": types.ConfigDocument,
"\n subscription Owner {\n owner {\n ...FragmentOwner\n }\n }\n": types.OwnerDocument,
"\n subscription Registration {\n registration {\n ...FragmentRegistration\n }\n }\n": types.RegistrationDocument,
"\n subscription Vars {\n vars {\n ...FragmentVars\n }\n }\n": types.VarsDocument,
};
/**
@@ -68,22 +64,6 @@ export function graphql(source: "\n fragment FragmentVars on Vars {\n regGen
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query serverState {\n owner {\n ...FragmentOwner\n }\n info {\n os {\n hostname\n }\n }\n registration {\n ...FragmentRegistration\n }\n crashReportingEnabled\n vars {\n ...FragmentVars\n }\n config {\n ...FragmentConfig\n }\n cloud {\n error\n apiKey {\n valid\n error\n }\n relay {\n status\n error\n }\n cloud {\n status\n error\n }\n }\n }\n"): (typeof documents)["\n query serverState {\n owner {\n ...FragmentOwner\n }\n info {\n os {\n hostname\n }\n }\n registration {\n ...FragmentRegistration\n }\n crashReportingEnabled\n vars {\n ...FragmentVars\n }\n config {\n ...FragmentConfig\n }\n cloud {\n error\n apiKey {\n valid\n error\n }\n relay {\n status\n error\n }\n cloud {\n status\n error\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n subscription Config {\n config {\n ...FragmentConfig\n }\n }\n"): (typeof documents)["\n subscription Config {\n config {\n ...FragmentConfig\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n subscription Owner {\n owner {\n ...FragmentOwner\n }\n }\n"): (typeof documents)["\n subscription Owner {\n owner {\n ...FragmentOwner\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n subscription Registration {\n registration {\n ...FragmentRegistration\n }\n }\n"): (typeof documents)["\n subscription Registration {\n registration {\n ...FragmentRegistration\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n subscription Vars {\n vars {\n ...FragmentVars\n }\n }\n"): (typeof documents)["\n subscription Vars {\n vars {\n ...FragmentVars\n }\n }\n"];
export function graphql(source: string) {
return (documents as any)[source] ?? {};

View File

@@ -1621,46 +1621,10 @@ export type serverStateQuery = { __typename?: 'Query', crashReportingEnabled?: b
& { ' $fragmentRefs'?: { 'FragmentConfigFragment': FragmentConfigFragment } }
), cloud?: { __typename?: 'Cloud', error?: string | null, apiKey: { __typename?: 'ApiKeyResponse', valid: boolean, error?: string | null }, relay?: { __typename?: 'RelayResponse', status: string, error?: string | null } | null, cloud: { __typename?: 'CloudResponse', status: string, error?: string | null } } | null };
export type ConfigSubscriptionVariables = Exact<{ [key: string]: never; }>;
export type ConfigSubscription = { __typename?: 'Subscription', config: (
{ __typename?: 'Config' }
& { ' $fragmentRefs'?: { 'FragmentConfigFragment': FragmentConfigFragment } }
) };
export type OwnerSubscriptionVariables = Exact<{ [key: string]: never; }>;
export type OwnerSubscription = { __typename?: 'Subscription', owner: (
{ __typename?: 'Owner' }
& { ' $fragmentRefs'?: { 'FragmentOwnerFragment': FragmentOwnerFragment } }
) };
export type RegistrationSubscriptionVariables = Exact<{ [key: string]: never; }>;
export type RegistrationSubscription = { __typename?: 'Subscription', registration: (
{ __typename?: 'Registration' }
& { ' $fragmentRefs'?: { 'FragmentRegistrationFragment': FragmentRegistrationFragment } }
) };
export type VarsSubscriptionVariables = Exact<{ [key: string]: never; }>;
export type VarsSubscription = { __typename?: 'Subscription', vars: (
{ __typename?: 'Vars' }
& { ' $fragmentRefs'?: { 'FragmentVarsFragment': FragmentVarsFragment } }
) };
export const TestFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Cloud"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}}]}}]} as unknown as DocumentNode<TestFragmentFragment, unknown>;
export const FragmentConfigFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FragmentConfig"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Config"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"valid"}}]}}]} as unknown as DocumentNode<FragmentConfigFragment, unknown>;
export const FragmentOwnerFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FragmentOwner"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Owner"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"username"}}]}}]} as unknown as DocumentNode<FragmentOwnerFragment, unknown>;
export const FragmentRegistrationFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FragmentRegistration"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Registration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"state"}},{"kind":"Field","name":{"kind":"Name","value":"expiration"}},{"kind":"Field","name":{"kind":"Name","value":"keyFile"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"contents"}}]}}]}}]} as unknown as DocumentNode<FragmentRegistrationFragment, unknown>;
export const FragmentVarsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FragmentVars"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Vars"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"regGen"}},{"kind":"Field","name":{"kind":"Name","value":"regState"}},{"kind":"Field","name":{"kind":"Name","value":"configError"}},{"kind":"Field","name":{"kind":"Name","value":"configValid"}}]}}]} as unknown as DocumentNode<FragmentVarsFragment, unknown>;
export const cloudErrorDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"cloudError"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cloud"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TestFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Cloud"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}}]}}]} as unknown as DocumentNode<cloudErrorQuery, cloudErrorQueryVariables>;
export const serverStateDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"serverState"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"owner"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FragmentOwner"}}]}},{"kind":"Field","name":{"kind":"Name","value":"info"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"os"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hostname"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"registration"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FragmentRegistration"}}]}},{"kind":"Field","name":{"kind":"Name","value":"crashReportingEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"vars"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FragmentVars"}}]}},{"kind":"Field","name":{"kind":"Name","value":"config"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FragmentConfig"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cloud"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"apiKey"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"valid"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}},{"kind":"Field","name":{"kind":"Name","value":"relay"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cloud"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FragmentOwner"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Owner"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"username"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FragmentRegistration"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Registration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"state"}},{"kind":"Field","name":{"kind":"Name","value":"expiration"}},{"kind":"Field","name":{"kind":"Name","value":"keyFile"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"contents"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FragmentVars"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Vars"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"regGen"}},{"kind":"Field","name":{"kind":"Name","value":"regState"}},{"kind":"Field","name":{"kind":"Name","value":"configError"}},{"kind":"Field","name":{"kind":"Name","value":"configValid"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FragmentConfig"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Config"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"valid"}}]}}]} as unknown as DocumentNode<serverStateQuery, serverStateQueryVariables>;
export const ConfigDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"subscription","name":{"kind":"Name","value":"Config"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"config"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FragmentConfig"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FragmentConfig"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Config"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"valid"}}]}}]} as unknown as DocumentNode<ConfigSubscription, ConfigSubscriptionVariables>;
export const OwnerDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"subscription","name":{"kind":"Name","value":"Owner"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"owner"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FragmentOwner"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FragmentOwner"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Owner"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"username"}}]}}]} as unknown as DocumentNode<OwnerSubscription, OwnerSubscriptionVariables>;
export const RegistrationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"subscription","name":{"kind":"Name","value":"Registration"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"registration"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FragmentRegistration"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FragmentRegistration"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Registration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"state"}},{"kind":"Field","name":{"kind":"Name","value":"expiration"}},{"kind":"Field","name":{"kind":"Name","value":"keyFile"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"contents"}}]}}]}}]} as unknown as DocumentNode<RegistrationSubscription, RegistrationSubscriptionVariables>;
export const VarsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"subscription","name":{"kind":"Name","value":"Vars"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"vars"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FragmentVars"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FragmentVars"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Vars"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"regGen"}},{"kind":"Field","name":{"kind":"Name","value":"regState"}},{"kind":"Field","name":{"kind":"Name","value":"configError"}},{"kind":"Field","name":{"kind":"Name","value":"configValid"}}]}}]} as unknown as DocumentNode<VarsSubscription, VarsSubscriptionVariables>;
export const serverStateDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"serverState"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"owner"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FragmentOwner"}}]}},{"kind":"Field","name":{"kind":"Name","value":"info"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"os"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hostname"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"registration"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FragmentRegistration"}}]}},{"kind":"Field","name":{"kind":"Name","value":"crashReportingEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"vars"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FragmentVars"}}]}},{"kind":"Field","name":{"kind":"Name","value":"config"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FragmentConfig"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cloud"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"apiKey"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"valid"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}},{"kind":"Field","name":{"kind":"Name","value":"relay"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cloud"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FragmentOwner"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Owner"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"username"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FragmentRegistration"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Registration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"state"}},{"kind":"Field","name":{"kind":"Name","value":"expiration"}},{"kind":"Field","name":{"kind":"Name","value":"keyFile"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"contents"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FragmentVars"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Vars"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"regGen"}},{"kind":"Field","name":{"kind":"Name","value":"regState"}},{"kind":"Field","name":{"kind":"Name","value":"configError"}},{"kind":"Field","name":{"kind":"Name","value":"configValid"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FragmentConfig"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Config"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"valid"}}]}}]} as unknown as DocumentNode<serverStateQuery, serverStateQueryVariables>;

View File

@@ -35,3 +35,9 @@ export const WebguiUpdateDns = request.url('/webGui/include/UpdateDNS.php');
* @type POST
*/
export const WebguiUnraidApiCommand = request.url('/plugins/dynamix.my.servers/include/unraid-api.php');
/**
* @name WebguiState
* @description used to get current state of server via PHP rather than unraid-api
* @type GET
*/
export const WebguiState = request.url('/plugins/dynamix.my.servers/data/server-state.php');

4
package-lock.json generated
View File

@@ -20,6 +20,7 @@
"graphql-tag": "^2.12.6",
"graphql-ws": "^5.14.0",
"hex-to-rgba": "^2.0.1",
"lodash": "^4.17.21",
"wretch": "^2.6.0"
},
"devDependencies": {
@@ -11784,8 +11785,7 @@
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash-es": {
"version": "4.17.21",

View File

@@ -43,6 +43,7 @@
"graphql-tag": "^2.12.6",
"graphql-ws": "^5.14.0",
"hex-to-rgba": "^2.0.1",
"lodash": "^4.17.21",
"wretch": "^2.6.0"
},
"overrides": {

View File

@@ -3,6 +3,7 @@ import { defineStore } from 'pinia';
import { addPreventClose, removePreventClose } from '~/composables/preventClose';
import { useAccountStore } from '~/store/account';
import { useInstallKeyStore } from '~/store/installKey';
import { useServerStore } from '~/store/server';
import { useCallbackStoreGeneric, type ExternalPayload, type ExternalKeyActions, type QueryPayloads } from '~/store/callback';
export const useCallbackActionsStore = defineStore(
@@ -10,6 +11,7 @@ export const useCallbackActionsStore = defineStore(
() => {
const accountStore = useAccountStore();
const installKeyStore = useInstallKeyStore();
const serverStore = useServerStore();
type CallbackStatus = 'error' | 'loading' | 'ready' | 'success';
const callbackStatus = ref<CallbackStatus>('ready');
@@ -43,6 +45,8 @@ export const useCallbackActionsStore = defineStore(
}
// all actions have run
if (array.length === (index + 1)) {
/** @todo refresh server state until we have new data */
await serverStore.refreshServerState();
// 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

View File

@@ -70,35 +70,3 @@ export const SERVER_STATE_QUERY = graphql(/* GraphQL */`
}
}
`);
export const SERVER_CONFIG_SUBSCRIPTION = graphql(/* GraphQL */`
subscription Config {
config {
...FragmentConfig
}
}
`);
export const SERVER_OWNER_SUBSCRIPTION = graphql(/* GraphQL */`
subscription Owner {
owner {
...FragmentOwner
}
}
`);
export const SERVER_REGISTRATION_SUBSCRIPTION = graphql(/* GraphQL */`
subscription Registration {
registration {
...FragmentRegistration
}
}
`);
export const SERVER_VARS_SUBSCRIPTION = graphql(/* GraphQL */`
subscription Vars {
vars {
...FragmentVars
}
}
`);

View File

@@ -2,6 +2,7 @@
* @todo Check OS and Connect Plugin versions against latest via API every session
*/
import { defineStore, createPinia, setActivePinia } from 'pinia';
import isEqual from 'lodash/isEqual';
import {
ArrowRightOnRectangleIcon,
CogIcon,
@@ -10,8 +11,10 @@ import {
KeyIcon,
QuestionMarkCircleIcon
} from '@heroicons/vue/24/solid';
import { useQuery, useSubscription } from '@vue/apollo-composable';
import { useQuery } from '@vue/apollo-composable';
import { useTimeoutPoll } from '@vueuse/core';
import { WebguiState } from '~/composables/services/webgui';
import { SETTINGS_MANAGMENT_ACCESS } from '~/helpers/urls';
import { useAccountStore } from '~/store/account';
import { useErrorsStore, type Error } from '~/store/errors';
@@ -31,18 +34,7 @@ import type {
ServerconnectPluginInstalled,
} from '~/types/server';
import {
SERVER_STATE_QUERY,
SERVER_VARS_FRAGMENT,
SERVER_VARS_SUBSCRIPTION,
SERVER_OWNER_FRAGMENT,
SERVER_OWNER_SUBSCRIPTION,
SERVER_CONFIG_FRAGMENT,
SERVER_CONFIG_SUBSCRIPTION,
SERVER_REGISTRATION_FRAGMENT,
SERVER_REGISTRATION_SUBSCRIPTION,
} from './server.fragment';
import { useFragment } from '~/composables/gql/fragment-masking';
import { SERVER_STATE_QUERY } from './server.fragment';
/**
* @see https://stackoverflow.com/questions/73476371/using-pinia-with-vue-js-web-components
* @see https://github.com/vuejs/pinia/discussions/1085
@@ -93,6 +85,7 @@ export const useServerStore = defineStore('server', () => {
const username = ref<string>(''); // @todo potentially move to a user store
const wanFQDN = ref<string>('');
const apiServerStateRefresh = ref<any>(null);
/**
* Getters
*/
@@ -693,89 +686,98 @@ export const useServerStore = defineStore('server', () => {
};
const fetchServerFromApi = () => {
const { result: resultServerState } = useQuery(SERVER_STATE_QUERY);
const { result: resultServerState, refetch: refetchServerState } = useQuery(SERVER_STATE_QUERY, null, {
pollInterval: 2500,
fetchPolicy: 'no-cache',
});
const serverState = computed(() => resultServerState.value ?? null);
apiServerStateRefresh.value = refetchServerState;
watch(serverState, value => {
console.debug('[watch.serverState]', value);
console.debug('[watch:serverState]', value);
if (value) {
const mutatedServerStateResult = mutateServerStateFromApi(value);
setServer(mutatedServerStateResult);
}
});
const { result: resultServerVars } = useSubscription(SERVER_VARS_SUBSCRIPTION);
const serverVars = computed(() => useFragment(SERVER_VARS_FRAGMENT, resultServerVars.value));
watch(serverVars, value => {
console.debug('[watch.serverVars]', value);
// if (value) {
// }
});
// const { result: resultServerOwner } = useSubscription(SERVER_OWNER_SUBSCRIPTION);
// const serverOwner = computed(() => resultServerOwner.value ?? null);
// watch(serverOwner, value => {
// console.debug('[watch.resultServerOwner]', value);
// // if (value) {
// // }
// });
// const { result: resultServerConfig } = useSubscription(SERVER_CONFIG_SUBSCRIPTION);
// const serverConfig = computed(() => resultServerConfig.value ?? null);
// watch(serverConfig, value => {
// console.debug('[watch.resultServerConfig]', value);
// // if (value) {
// // }
// });
// const { result: resultServerRegistration } = useSubscription(SERVER_REGISTRATION_SUBSCRIPTION);
// const serverRegistration = computed(() => resultServerRegistration.value ?? null);
// watch(serverRegistration, value => {
// console.debug('[watch.resultServerRegistration]', value);
// // if (value) {
// // }
// });
};
watch(apiKey, (newVal, oldVal) => {
console.debug('[watch.apiKey]', newVal, oldVal);
if (oldVal) {
// stop old client
console.debug('[watch.apiKey] no apiKey, stop old client');
const phpServerStateRefresh = async () => {
console.debug('[phpServerStateRefresh] start');
try {
const stateResponse: Server = await WebguiState
.get()
.json();
console.debug('[phpServerStateRefresh] stateResponse', stateResponse);
setServer(stateResponse);
return stateResponse;
} catch (error) {
console.error('[phpServerStateRefresh] error', error);
}
/** @todo abstract validation checks */
if (newVal && newVal.length === 64 && newVal.startsWith('unupc_')) {
// start new client
console.debug('[watch.apiKey] start new client');
unraidApiStore.createApolloClient(newVal);
};
let refreshCount = 0;
const refreshLimit = 10;
const refreshedServerState = ref(false);
const refreshServerState = async () => {
refreshCount++;
console.debug('[refreshServerState] start', { refreshCount });
const registeredBeforeRefresh = registered.value;
const stateBeforeRefresh = state.value;
const responseNewState = apiServerStateRefresh.value
? await apiServerStateRefresh.value()
: await phpServerStateRefresh();
console.debug(`[refreshServerState] responseNewState`, responseNewState);
const newState = apiServerStateRefresh.value && responseNewState?.data ? responseNewState.data : responseNewState;
console.debug(`[refreshServerState] newState`, newState);
const registrationStatusChanged = registeredBeforeRefresh !== newState.registered;
const stateChanged = stateBeforeRefresh !== newState.state;
if (registrationStatusChanged || stateChanged) {
console.debug('[refreshServerState] change detected, stop refreshing', { registrationStatusChanged, stateChanged });
refreshedServerState.value = true;
return true;
}
});
if (refreshCount >= refreshLimit) {
console.debug('[refreshServerState] refresh limit reached, stop refreshing');
return false;
}
console.debug('[refreshServerState] no change, fetch again in 250ms…', { registrationStatusChanged, stateChanged });
setTimeout(() => {
return refreshServerState();
}, 250);
};
watch(theme, (newVal) => {
if (newVal) themeStore.setTheme(newVal);
});
watch(stateDataError, (newVal, oldVal) => {
console.debug('[watch.stateDataError]', newVal, oldVal);
console.debug('[watch:stateDataError]', newVal, oldVal);
if (oldVal && oldVal.ref) errorsStore.removeErrorByRef(oldVal.ref);
if (newVal) errorsStore.setError(newVal);
});
watch(invalidApiKey, (newVal, oldVal) => {
console.debug('[watch.invalidApiKey]', newVal, oldVal);
console.debug('[watch:invalidApiKey]', newVal, oldVal);
if (oldVal && oldVal.ref) errorsStore.removeErrorByRef(oldVal.ref);
if (newVal) errorsStore.setError(newVal);
});
watch(tooManyDevices, (newVal, oldVal) => {
console.debug('[watch.tooManyDevices]', newVal, oldVal);
console.debug('[watch:tooManyDevices]', newVal, oldVal);
if (oldVal && oldVal.ref) errorsStore.removeErrorByRef(oldVal.ref);
if (newVal) errorsStore.setError(newVal);
});
watch(pluginInstallFailed, (newVal, oldVal) => {
console.debug('[watch.pluginInstallFailed]', newVal, oldVal);
console.debug('[watch:pluginInstallFailed]', newVal, oldVal);
if (oldVal && oldVal.ref) errorsStore.removeErrorByRef(oldVal.ref);
if (newVal) errorsStore.setError(newVal);
});
watch(deprecatedUnraidSSL, (newVal, oldVal) => {
console.debug('[watch.deprecatedUnraidSSL]', newVal, oldVal);
console.debug('[watch:deprecatedUnraidSSL]', newVal, oldVal);
if (oldVal && oldVal.ref) errorsStore.removeErrorByRef(oldVal.ref);
if (newVal) errorsStore.setError(newVal);
});
@@ -802,6 +804,7 @@ export const useServerStore = defineStore('server', () => {
theme,
uptime,
username,
refreshedServerState,
// getters
authAction,
deprecatedUnraidSSL,
@@ -820,5 +823,6 @@ export const useServerStore = defineStore('server', () => {
// actions
setServer,
fetchServerFromApi,
refreshServerState,
};
});

View File

@@ -7,6 +7,7 @@ import { logErrorMessages } from '@vue/apollo-util'
import { createClient } from 'graphql-ws';
import { defineStore, createPinia, setActivePinia } from 'pinia';
import { useAccountStore } from '~/store/account';
import { useErrorsStore } from '~/store/errors';
import { useServerStore } from '~/store/server';
/**
@@ -28,18 +29,26 @@ console.debug('[useUnraidApiStore] wsEndpoint', wsEndpoint.toString());
export const useUnraidApiStore = defineStore('unraidApi', () => {
console.debug('[useUnraidApiStore]');
const accountStore = useAccountStore();
const errorsStore = useErrorsStore();
const serverStore = useServerStore();
const unraidApiClient = ref<ApolloClient<any>>();
const registeredWithValidApiKey = computed(() => {
const { registered, invalidApiKey } = serverStore;
return registered && !invalidApiKey;
});
/**
* Automatically called when an apiKey is set in the serverStore
*/
const createApolloClient = (apiKey: string) => {
console.debug('[useUnraidApiStore.createApolloClient]');
const headers = { 'x-api-key': apiKey };
const createApolloClient = () => {
console.debug('[useUnraidApiStore.createApolloClient]', serverStore.apiKey);
if (accountStore.accountActionType === 'signOut') {
return console.debug('[useUnraidApiStore.createApolloClient] sign out imminent, skipping createApolloClient');
}
const headers = { 'x-api-key': serverStore.apiKey };
const httpLink = new createHttpLink({
uri: httpEndpoint.toString(),
@@ -103,8 +112,32 @@ export const useUnraidApiStore = defineStore('unraidApi', () => {
console.debug('[useUnraidApiStore.createApolloClient] 🏁 CREATED');
};
const closeUnraidApiClient = async () => {
console.debug('[useUnraidApiStore.closeUnraidApiClient] STARTED');
if (!unraidApiClient.value) return console.debug('[useUnraidApiStore.closeUnraidApiClient] unraidApiClient not set');
if (unraidApiClient.value) {
await unraidApiClient.value.clearStore();
unraidApiClient.value.stop();
// (wsLink.value as any).subscriptionClient.close(); // needed if we start using subscriptions
}
unraidApiClient.value = undefined;
console.debug('[useUnraidApiStore.closeUnraidApiClient] DONE');
};
watch(registeredWithValidApiKey, (newVal, oldVal) => {
console.debug('[watch:registeredWithValidApiKey]', newVal, oldVal);
if (oldVal) {
console.debug('[watch:registeredWithValidApiKey] no apiKey, stop unraid-api client');
closeUnraidApiClient();
}
if (newVal) {
console.debug('[watch:registeredWithValidApiKey] new apiKey, start unraid-api client');
createApolloClient();
}
});
watch(unraidApiClient, (newVal, oldVal) => {
console.debug('[watch.unraidApiStore.unraidApiClient]', { newVal, oldVal });
console.debug('[watch:unraidApiStore.unraidApiClient]', { newVal, oldVal });
if (newVal && !oldVal) { // first time
serverStore.fetchServerFromApi();
}
@@ -113,5 +146,6 @@ export const useUnraidApiStore = defineStore('unraidApi', () => {
return {
unraidApiClient,
createApolloClient,
closeUnraidApiClient,
};
});