mirror of
https://github.com/unraid/api.git
synced 2025-12-31 13:39:52 -06:00
feat(web): activation modal steps, updated copy (#1079)
* feat(stepper): add shadcn stepper components * chore(serverState): remove partnerLogo property from server state configuration * refactor(web): modal add subfooter slot - adds ability to display content below the modal's content box * feat(modal): add ActivationSteps component to subFooter slot in WelcomeModal and ActivationModal * refactor: improve activation modal buttons responsiveness * refactor: update activation flow messaging and UI * feat: web/deploy-dev.sh add dynamic web component JS file whitelisting in auth-request.php * fix: remove test UTM parameters from Unraid docs links in activation modal * refactor: improve konami code handling and add type safety to activation steps * chore: remove extra semicolon in serverState.ts * Apply suggestions from code review Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
@@ -60,7 +60,7 @@ fi
|
||||
|
||||
if [ "$deploy" = "yes" ]; then
|
||||
cd web || exit
|
||||
npm run deploy-wc:dev
|
||||
npm run deploy-to-unraid:dev
|
||||
elif [ "$deploy" = "build" ]; then
|
||||
cd web || exit
|
||||
npm run build:dev
|
||||
|
||||
@@ -80,6 +80,7 @@ class WebComponentTranslations
|
||||
'<p>Your Unraid registration key is ineligible for replacement as it has been replaced within the last 12 months.</p>' => '<p>' . _('Your Unraid registration key is ineligible for replacement as it has been replaced within the last 12 months.') . '</p>',
|
||||
'A Trial key provides all the functionality of an Unleashed Registration key' => _('A Trial key provides all the functionality of an Unleashed Registration key'),
|
||||
'Acklowledge that you have made a Flash Backup to enable this action' => _('Acklowledge that you have made a Flash Backup to enable this action'),
|
||||
'Activate License' => _('Activate License'),
|
||||
'Activate Now' => _('Activate Now'),
|
||||
'ago' => _('ago'),
|
||||
'All you need is an active internet connection, an Unraid.net account, and the Connect plugin. Get started by installing the plugin.' => _('All you need is an active internet connection, an Unraid.net account, and the Connect plugin.') . ' ' . _('Get started by installing the plugin.'),
|
||||
@@ -119,11 +120,14 @@ class WebComponentTranslations
|
||||
'Copy your Key URL: {0}' => sprintf(_('Copy your Key URL: %s'), '{0}'),
|
||||
'Create Flash Backup' => _('Create Flash Backup'),
|
||||
'Create a password' => _('Create a password'),
|
||||
'Create an Unraid.net account and activate your key' => _('Create an Unraid.net account and activate your key'),
|
||||
'Create Device Password' => _('Create Device Password'),
|
||||
'Current Version {0}' => sprintf(_('Current Version %s'), '{0}'),
|
||||
'Current Version: Unraid {0}' => sprintf(_('Current Version: Unraid %s'), '{0}'),
|
||||
'Customizable Dashboard Tiles' => _('Customizable Dashboard Tiles'),
|
||||
'day' => sprintf(_('%s day'), '{n}') . ' | ' . sprintf(_('%s days'), '{n}'),
|
||||
'Deep Linking' => _('Deep Linking'),
|
||||
'Device is ready to configure' => _('Device is ready to configure'),
|
||||
'DNS issue, unable to resolve wanip4.unraid.net' => _('DNS issue, unable to resolve wanip4.unraid.net'),
|
||||
'Downgrade Unraid OS to {0}' => sprintf(_('Downgrade Unraid OS to %s'), '{0}'),
|
||||
'Downgrade Unraid OS' => _('Downgrade Unraid OS'),
|
||||
@@ -206,7 +210,7 @@ class WebComponentTranslations
|
||||
'Learn more and link your key to your account' => _('Learn more and link your key to your account'),
|
||||
'Learn More' => _('Learn More'),
|
||||
'Learn more' => _('Learn more'),
|
||||
'Let\'s activate your Unraid license!' => _('Let\'s activate your Unraid license!'),
|
||||
'Let\'s activate your Unraid OS License' => _('Let\'s activate your Unraid OS License'),
|
||||
'Let\'s Unleash your Hardware!' => _('Let\'s Unleash your Hardware!'),
|
||||
'License key actions' => _('License key actions'),
|
||||
'License key type' => _('License key type'),
|
||||
@@ -286,6 +290,7 @@ class WebComponentTranslations
|
||||
'Requires the local unraid-api to be running successfully' => _('Requires the local unraid-api to be running successfully'),
|
||||
'Restarting unraid-api…' => _('Restarting unraid-api…'),
|
||||
'second' => sprintf(_('%s second'), '{n}') . ' | ' . sprintf(_('%s seconds'), '{n}'),
|
||||
'Secure your device' => _('Secure your device'),
|
||||
'Server Up Since {0}' => sprintf(_('Server Up Since %s'), '{0}'),
|
||||
'Servers equipped with a myunraid.net certificate can be managed directly from within the Connect web UI. Manage multiple servers from your phone, tablet, laptop, or PC in the same browser window.' => _('Servers equipped with a myunraid.net certificate can be managed directly from within the Connect web UI.') . ' ' . _('Manage multiple servers from your phone, tablet, laptop, or PC in the same browser window.'),
|
||||
'Set custom server tiles how you like and automatically display your server\'s banner image on your Connect Dashboard.' => _('Set custom server tiles how you like and automatically display your server\'s banner image on your Connect Dashboard.'),
|
||||
@@ -307,7 +312,7 @@ class WebComponentTranslations
|
||||
'SSL certificates for unraid.net deprecated' => _('SSL certificates for unraid.net deprecated'),
|
||||
'Stale Server' => _('Stale Server'),
|
||||
'Stale' => _('Stale'),
|
||||
'Start by creating an Unraid.net account — this will let you manage your license and access support. Once that\'s done, we\'ll guide you through a quick checkout process to register your license and install your key.' => _('Start by creating an Unraid.net account — this will let you manage your license and access support.') . ' ' . _('Once that\'s done, we\'ll guide you through a quick checkout process to register your license and install your key.'),
|
||||
'On the following screen, your license will be activated. You\'ll then create an Unraid.net Account to manage your license going forward.' => _('On the following screen, your license will be activated.') . ' ' . _('You\'ll then create an Unraid.net Account to manage your license going forward.'),
|
||||
'Start Free 30 Day Trial' => _('Start Free 30 Day Trial'),
|
||||
'Starting your free 30 day trial' => _('Starting your free 30 day trial'),
|
||||
'Success!' => _('Success!'),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
;
|
||||
// import dayjs, { extend } from 'dayjs';
|
||||
// import customParseFormat from 'dayjs/plugin/customParseFormat';
|
||||
// import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
@@ -11,7 +10,6 @@ import type { Server, ServerState
|
||||
// ServerUpdateOsResponse,
|
||||
} from '~/types/server';
|
||||
|
||||
|
||||
// dayjs plugins
|
||||
// extend(customParseFormat);
|
||||
// extend(relativeTime);
|
||||
|
||||
@@ -19,10 +19,10 @@ const activationCodeStore = useActivationCodeStore();
|
||||
const { partnerLogo, showActivationModal } = storeToRefs(activationCodeStore);
|
||||
const purchaseStore = usePurchaseStore();
|
||||
|
||||
const title = computed<string>(() => props.t("Let's activate your Unraid license!"));
|
||||
const title = computed<string>(() => props.t("Let's activate your Unraid OS License"));
|
||||
const description = computed<string>(() =>
|
||||
props.t(
|
||||
`Start by creating an Unraid.net account — this will let you manage your license and access support. Once that's done, we'll guide you through a quick checkout process to register your license and install your key.`
|
||||
`On the following screen, your license will be activated. You’ll then create an Unraid.net Account to manage your license going forward.`
|
||||
)
|
||||
);
|
||||
const docsButtons = computed<ButtonProps[]>(() => {
|
||||
@@ -47,40 +47,41 @@ const docsButtons = computed<ButtonProps[]>(() => {
|
||||
});
|
||||
|
||||
/**
|
||||
* Listen for a key sequence to close the modal
|
||||
* @todo - temporary solution until we have a better way to handle this
|
||||
* Listen for konami code sequence to close the modal
|
||||
*/
|
||||
const keySequence = [
|
||||
"ArrowUp",
|
||||
"ArrowUp",
|
||||
"ArrowDown",
|
||||
"ArrowDown",
|
||||
"ArrowLeft",
|
||||
"ArrowRight",
|
||||
"ArrowLeft",
|
||||
"ArrowRight",
|
||||
"b",
|
||||
"a",
|
||||
];
|
||||
let sequenceIndex = 0;
|
||||
|
||||
const handleKeydown = (event: KeyboardEvent) => {
|
||||
if (event.key === keySequence[sequenceIndex]) {
|
||||
sequenceIndex++;
|
||||
} else {
|
||||
sequenceIndex = 0;
|
||||
}
|
||||
|
||||
if (sequenceIndex === keySequence.length) {
|
||||
activationCodeStore.setActivationModalHidden(true);
|
||||
window.location.href = "/Tools/Registration";
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
const keySequence = [
|
||||
"ArrowUp",
|
||||
"ArrowUp",
|
||||
"ArrowDown",
|
||||
"ArrowDown",
|
||||
"ArrowLeft",
|
||||
"ArrowRight",
|
||||
"ArrowLeft",
|
||||
"ArrowRight",
|
||||
"b",
|
||||
"a",
|
||||
];
|
||||
let sequenceIndex = 0;
|
||||
|
||||
window.addEventListener("keydown", (event) => {
|
||||
if (event.key === keySequence[sequenceIndex]) {
|
||||
sequenceIndex++;
|
||||
} else {
|
||||
sequenceIndex = 0;
|
||||
}
|
||||
|
||||
if (sequenceIndex === keySequence.length) {
|
||||
activationCodeStore.setActivationModalHidden(true);
|
||||
window.location.href = "/Tools/Registration";
|
||||
}
|
||||
});
|
||||
window.addEventListener("keydown", handleKeydown);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener("keydown", () => {});
|
||||
window.removeEventListener("keydown", handleKeydown);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -103,11 +104,6 @@ onUnmounted(() => {
|
||||
<ActivationPartnerLogo />
|
||||
</template>
|
||||
|
||||
<template #main>
|
||||
<div class="flex justify-center gap-4 mx-auto w-full">
|
||||
<BrandButton v-for="button in docsButtons" :key="button.text" v-bind="button" />
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div class="w-full flex gap-8px justify-center mx-auto">
|
||||
<BrandButton
|
||||
@@ -117,5 +113,15 @@ onUnmounted(() => {
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #subFooter>
|
||||
<div class="flex flex-col gap-6">
|
||||
<ActivationSteps :active-step="2" class="hidden sm:flex mt-6" />
|
||||
|
||||
<div class="flex flex-col sm:flex-row justify-center gap-4 mx-auto w-full">
|
||||
<BrandButton v-for="button in docsButtons" :key="button.text" v-bind="button" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
119
web/components/Activation/Steps.vue
Normal file
119
web/components/Activation/Steps.vue
Normal file
@@ -0,0 +1,119 @@
|
||||
<script setup lang="ts">
|
||||
import { Button } from "@/components/shadcn/button";
|
||||
|
||||
import {
|
||||
Stepper,
|
||||
StepperDescription,
|
||||
StepperItem,
|
||||
StepperSeparator,
|
||||
StepperTitle,
|
||||
StepperTrigger,
|
||||
} from "@/components/shadcn/stepper";
|
||||
import { CheckIcon, KeyIcon, ServerStackIcon } from "@heroicons/vue/24/outline";
|
||||
import {
|
||||
KeyIcon as KeyIconSolid,
|
||||
LockClosedIcon,
|
||||
ServerStackIcon as ServerStackIconSolid,
|
||||
} from "@heroicons/vue/24/solid";
|
||||
|
||||
import type { Component } from 'vue'
|
||||
|
||||
type StepState = "inactive" | "active" | "completed";
|
||||
|
||||
withDefaults(defineProps<{
|
||||
activeStep: number
|
||||
}>(), {
|
||||
activeStep: 1
|
||||
})
|
||||
|
||||
interface Step {
|
||||
step: number;
|
||||
title: string;
|
||||
description: string;
|
||||
icon: {
|
||||
inactive: Component;
|
||||
active: Component;
|
||||
completed: Component;
|
||||
};
|
||||
};
|
||||
const steps: readonly Step[] = [
|
||||
{
|
||||
step: 1,
|
||||
title: "Create Device Password",
|
||||
description: "Secure your device",
|
||||
icon: {
|
||||
inactive: LockClosedIcon,
|
||||
active: LockClosedIcon,
|
||||
completed: CheckIcon,
|
||||
},
|
||||
},
|
||||
{
|
||||
step: 2,
|
||||
title: "Activate License",
|
||||
description: "Create an Unraid.net account and activate your key",
|
||||
icon: {
|
||||
inactive: KeyIcon,
|
||||
active: KeyIconSolid,
|
||||
completed: CheckIcon,
|
||||
},
|
||||
},
|
||||
{
|
||||
step: 3,
|
||||
title: "Unleash Your Hardware",
|
||||
description: "Device is ready to configure",
|
||||
icon: {
|
||||
inactive: ServerStackIcon,
|
||||
active: ServerStackIconSolid,
|
||||
completed: CheckIcon,
|
||||
},
|
||||
},
|
||||
] as const;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Stepper
|
||||
:default-value="activeStep"
|
||||
class="text-foreground flex w-full items-start gap-2 text-16px"
|
||||
>
|
||||
<StepperItem
|
||||
v-for="step in steps"
|
||||
:key="step.step"
|
||||
v-slot="{ state }: { state: StepState }"
|
||||
class="relative flex w-full flex-col items-center justify-center"
|
||||
:step="step.step"
|
||||
:disabled="true"
|
||||
>
|
||||
<StepperSeparator
|
||||
v-if="step.step !== steps[steps.length - 1].step"
|
||||
class="absolute left-[calc(50%+20px)] right-[calc(-50%+10px)] top-[1.5rem] &block h-0.5 shrink-0 rounded-full bg-muted group-data-[state=completed]:bg-primary"
|
||||
/>
|
||||
|
||||
<StepperTrigger as-child>
|
||||
<Button
|
||||
:variant="state === 'completed' || state === 'active' ? 'default' : 'outline'"
|
||||
size="default"
|
||||
class="z-10 rounded-full shrink-0 opacity-100"
|
||||
:class="[
|
||||
state !== 'inactive' &&
|
||||
'ring-2 ring-primary ring-offset-2 ring-offset-background *:cursor-default',
|
||||
]"
|
||||
:disabled="state === 'inactive'"
|
||||
>
|
||||
<component :is="step.icon[state]" class="size-4" />
|
||||
</Button>
|
||||
</StepperTrigger>
|
||||
|
||||
<div class="mt-2 flex flex-col items-center text-center">
|
||||
<StepperTitle
|
||||
:class="[state === 'active' && 'text-primary']"
|
||||
class="text-2xs font-semibold transition"
|
||||
>
|
||||
{{ step.title }}
|
||||
</StepperTitle>
|
||||
<StepperDescription class="text-2xs font-normal">
|
||||
{{ step.description }}
|
||||
</StepperDescription>
|
||||
</div>
|
||||
</StepperItem>
|
||||
</Stepper>
|
||||
</template>
|
||||
@@ -58,12 +58,12 @@ const closeModal = () => {
|
||||
const ariaLablledById = computed<string|undefined>(() => props.title ? `ModalTitle-${Math.random()}`.replace('0.', '') : undefined);
|
||||
const computedVerticalCenter = computed<string>(() => {
|
||||
if (props.tallContent) {
|
||||
return 'items-start sm:items-center';
|
||||
return 'justify-start sm:justify-center';
|
||||
}
|
||||
if (typeof props.modalVerticalCenter === 'string') {
|
||||
return props.modalVerticalCenter;
|
||||
}
|
||||
return props.modalVerticalCenter ? 'items-center' : 'items-start';
|
||||
return props.modalVerticalCenter ? 'justify-center' : 'justify-start';
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -78,7 +78,7 @@ const computedVerticalCenter = computed<string>(() => {
|
||||
@keyup.esc="closeModal"
|
||||
>
|
||||
<div
|
||||
class="fixed inset-0 flex min-h-screen w-screen justify-center p-8px sm:p-16px overflow-y-auto"
|
||||
class="fixed inset-0 flex flex-col min-h-screen w-screen items-center p-8px sm:p-16px overflow-y-auto"
|
||||
:class="computedVerticalCenter"
|
||||
>
|
||||
<TransitionChild
|
||||
@@ -115,7 +115,7 @@ const computedVerticalCenter = computed<string>(() => {
|
||||
success ? 'shadow-green-600/30 border-green-600/10' : '',
|
||||
!error && !success && !disableShadow ? 'shadow-orange/10 border-white/10' : '',
|
||||
]"
|
||||
class="text-16px text-foreground bg-background text-left relative z-10 flex flex-col justify-around border-2 border-solid transform overflow-hidden rounded-lg transition-all sm:w-full"
|
||||
class="text-16px text-foreground bg-background text-left relative z-10 mx-auto flex flex-col justify-around border-2 border-solid transform overflow-hidden rounded-lg transition-all sm:w-full"
|
||||
>
|
||||
<div v-if="showCloseX" class="absolute z-20 right-0 top-0 pt-4px pr-4px hidden sm:block">
|
||||
<button
|
||||
@@ -174,6 +174,21 @@ const computedVerticalCenter = computed<string>(() => {
|
||||
</footer>
|
||||
</div>
|
||||
</TransitionChild>
|
||||
|
||||
<TransitionChild
|
||||
appear
|
||||
as="template"
|
||||
enter="duration-300 ease-out"
|
||||
enter-from="opacity-0"
|
||||
enter-to="opacity-100"
|
||||
leave="duration-200 ease-in"
|
||||
leave-from="opacity-100"
|
||||
leave-to="opacity-0"
|
||||
>
|
||||
<div v-if="$slots['subFooter']" class="mt-4 flex justify-center mx-auto" :class="[maxWidth]">
|
||||
<slot name="subFooter" />
|
||||
</div>
|
||||
</TransitionChild>
|
||||
</div>
|
||||
</div>
|
||||
</TransitionRoot>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useI18n } from 'vue-i18n';
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import '~/assets/main.css';
|
||||
|
||||
import ActivationSteps from '~/components/Activation/Steps.vue';
|
||||
import { useActivationCodeStore } from '~/store/activationCode';
|
||||
import { useServerStore } from '~/store/server';
|
||||
import type { Server } from '~/types/server';
|
||||
@@ -28,7 +29,7 @@ const title = computed<string>(() =>
|
||||
);
|
||||
|
||||
const description = computed<string>(() =>
|
||||
t(`You're about to create a password to secure access to your system. This password is essential for managing and configuring your server. You’ll use this password every time you access the Unraid web interface.`)
|
||||
t(`First, you’ll create your device’s login credentials, then you’ll activate your Unraid license—your device’s operating system (OS).`)
|
||||
);
|
||||
|
||||
const showModal = ref<boolean>(true);
|
||||
@@ -98,6 +99,10 @@ onBeforeMount(() => {
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #subFooter>
|
||||
<ActivationSteps :active-step="1" class="hidden sm:flex mt-6" />
|
||||
</template>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
28
web/components/shadcn/stepper/Stepper.vue
Normal file
28
web/components/shadcn/stepper/Stepper.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<script lang="ts" setup>
|
||||
import type { StepperRootEmits, StepperRootProps } from 'radix-vue'
|
||||
import { cn } from '@/components/shadcn/utils'
|
||||
import { StepperRoot, useForwardPropsEmits } from 'radix-vue'
|
||||
|
||||
import { computed, type HTMLAttributes } from 'vue'
|
||||
|
||||
const props = defineProps<StepperRootProps & { class?: HTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<StepperRootEmits>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<StepperRoot
|
||||
v-slot="slotProps"
|
||||
:class="cn('flex gap-2', props.class)"
|
||||
v-bind="forwarded"
|
||||
>
|
||||
<slot v-bind="slotProps" />
|
||||
</StepperRoot>
|
||||
</template>
|
||||
27
web/components/shadcn/stepper/StepperDescription.vue
Normal file
27
web/components/shadcn/stepper/StepperDescription.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<script lang="ts" setup>
|
||||
import type { StepperDescriptionProps } from 'radix-vue'
|
||||
import { cn } from '@/components/shadcn/utils'
|
||||
import { StepperDescription, useForwardProps } from 'radix-vue'
|
||||
|
||||
import { computed, type HTMLAttributes } from 'vue'
|
||||
|
||||
const props = defineProps<StepperDescriptionProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwarded = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<StepperDescription
|
||||
v-slot="slotProps"
|
||||
v-bind="forwarded"
|
||||
:class="cn('text-xs text-muted-foreground', props.class)"
|
||||
>
|
||||
<slot v-bind="slotProps" />
|
||||
</StepperDescription>
|
||||
</template>
|
||||
37
web/components/shadcn/stepper/StepperIndicator.vue
Normal file
37
web/components/shadcn/stepper/StepperIndicator.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<script lang="ts" setup>
|
||||
import type { StepperIndicatorProps } from 'radix-vue'
|
||||
import { cn } from '@/components/shadcn/utils'
|
||||
import { StepperIndicator, useForwardProps } from 'radix-vue'
|
||||
|
||||
import { computed, type HTMLAttributes } from 'vue'
|
||||
|
||||
const props = defineProps<StepperIndicatorProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwarded = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<StepperIndicator
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'inline-flex items-center justify-center rounded-full text-muted-foreground/50 w-10 h-10',
|
||||
// Disabled
|
||||
'group-data-[disabled]:text-muted-foreground group-data-[disabled]:opacity-50',
|
||||
// Active
|
||||
'group-data-[state=active]:bg-primary group-data-[state=active]:text-primary-foreground',
|
||||
// Completed
|
||||
'group-data-[state=completed]:bg-accent group-data-[state=completed]:text-accent-foreground',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</StepperIndicator>
|
||||
</template>
|
||||
29
web/components/shadcn/stepper/StepperItem.vue
Normal file
29
web/components/shadcn/stepper/StepperItem.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<script lang="ts" setup>
|
||||
import type { StepperItemProps } from 'radix-vue'
|
||||
import { cn } from '@/components/shadcn/utils'
|
||||
import { StepperItem, useForwardProps } from 'radix-vue'
|
||||
|
||||
import { computed, type HTMLAttributes } from 'vue'
|
||||
|
||||
const props = defineProps<StepperItemProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwarded = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<StepperItem
|
||||
v-slot="slotProps"
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn('flex items-center gap-2 group data-[disabled]:pointer-events-none', props.class)
|
||||
"
|
||||
>
|
||||
<slot v-bind="slotProps" />
|
||||
</StepperItem>
|
||||
</template>
|
||||
33
web/components/shadcn/stepper/StepperSeparator.vue
Normal file
33
web/components/shadcn/stepper/StepperSeparator.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<script lang="ts" setup>
|
||||
import type { StepperSeparatorProps } from 'radix-vue'
|
||||
import { cn } from '@/components/shadcn/utils'
|
||||
import { StepperSeparator, useForwardProps } from 'radix-vue'
|
||||
|
||||
import { computed, type HTMLAttributes } from 'vue'
|
||||
|
||||
const props = defineProps<StepperSeparatorProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwarded = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<StepperSeparator
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'bg-muted',
|
||||
// Disabled
|
||||
'group-data-[disabled]:bg-muted group-data-[disabled]:opacity-75',
|
||||
// Completed
|
||||
'group-data-[state=completed]:bg-accent-foreground',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
26
web/components/shadcn/stepper/StepperTitle.vue
Normal file
26
web/components/shadcn/stepper/StepperTitle.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<script lang="ts" setup>
|
||||
import type { StepperTitleProps } from 'radix-vue'
|
||||
import { cn } from '@/components/shadcn/utils'
|
||||
import { StepperTitle, useForwardProps } from 'radix-vue'
|
||||
|
||||
import { computed, type HTMLAttributes } from 'vue'
|
||||
|
||||
const props = defineProps<StepperTitleProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwarded = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<StepperTitle
|
||||
v-bind="forwarded"
|
||||
:class="cn('text-md font-semibold whitespace-nowrap', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</StepperTitle>
|
||||
</template>
|
||||
28
web/components/shadcn/stepper/StepperTrigger.vue
Normal file
28
web/components/shadcn/stepper/StepperTrigger.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<script lang="ts" setup>
|
||||
import type { StepperTriggerProps } from 'radix-vue'
|
||||
import { cn } from '@/components/shadcn/utils'
|
||||
import { StepperTrigger, useForwardProps } from 'radix-vue'
|
||||
|
||||
import { computed, type HTMLAttributes } from 'vue'
|
||||
|
||||
const props = defineProps<StepperTriggerProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwarded = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<StepperTrigger
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn('p-2 flex flex-col items-center text-center gap-2 rounded-md', props.class)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</StepperTrigger>
|
||||
</template>
|
||||
7
web/components/shadcn/stepper/index.ts
Normal file
7
web/components/shadcn/stepper/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export { default as Stepper } from './Stepper.vue'
|
||||
export { default as StepperDescription } from './StepperDescription.vue'
|
||||
export { default as StepperIndicator } from './StepperIndicator.vue'
|
||||
export { default as StepperItem } from './StepperItem.vue'
|
||||
export { default as StepperSeparator } from './StepperSeparator.vue'
|
||||
export { default as StepperTitle } from './StepperTitle.vue'
|
||||
export { default as StepperTrigger } from './StepperTrigger.vue'
|
||||
@@ -35,6 +35,7 @@
|
||||
"A valid OS version is required to check for OS updates.": "A valid OS version is required to check for OS updates.",
|
||||
"Acklowledge that you have made a Flash Backup to enable this action": "Acklowledge that you have made a Flash Backup to enable this action",
|
||||
"Activate Now": "Activate Now",
|
||||
"Activate License": "Activate License",
|
||||
"ago": "ago",
|
||||
"All you need is an active internet connection, an Unraid.net account, and the Connect plugin. Get started by installing the plugin.": "All you need is an active internet connection, an Unraid.net account, and the Connect plugin. Get started by installing the plugin.",
|
||||
"Attached Storage Devices": "Attached Storage Devices",
|
||||
@@ -75,6 +76,7 @@
|
||||
"Copy your Key URL: {0}": "Copy your Key URL: {0}",
|
||||
"Create Flash Backup": "Create Flash Backup",
|
||||
"Create a password": "Create a password",
|
||||
"Create Device Password": "Create Device Password",
|
||||
"Current Version {0}": "Current Version {0}",
|
||||
"Current Version: Unraid {0}": "Current Version: Unraid {0}",
|
||||
"Customizable Dashboard Tiles": "Customizable Dashboard Tiles",
|
||||
@@ -175,7 +177,7 @@
|
||||
"Learn more and link your key to your account": "Learn more and link your key to your account",
|
||||
"Learn more": "Learn more",
|
||||
"Learn More": "Learn More",
|
||||
"Let's activate your Unraid license!": "Let's activate your Unraid license!",
|
||||
"Let's activate your Unraid OS License": "Let's activate your Unraid OS License",
|
||||
"Let's Unleash your Hardware!": "Let's Unleash your Hardware!",
|
||||
"License key actions": "License key actions",
|
||||
"License key type": "License key type",
|
||||
@@ -284,7 +286,7 @@
|
||||
"SSL certificates for unraid.net deprecated": "SSL certificates for unraid.net deprecated",
|
||||
"Stale Server": "Stale Server",
|
||||
"Stale": "Stale",
|
||||
"Start by creating an Unraid.net account — this will let you manage your license and access support. Once that's done, we'll guide you through a quick checkout process to register your license and install your key.": "Start by creating an Unraid.net account — this will let you manage your license and access support. Once that's done, we'll guide you through a quick checkout process to register your license and install your key.",
|
||||
"On the following screen, your license will be activated. You'll then create an Unraid.net Account to manage your license going forward.": "On the following screen, your license will be activated. You'll then create an Unraid.net Account to manage your license going forward.",
|
||||
"Start Free 30 Day Trial": "Start Free 30 Day Trial",
|
||||
"Starter": "Starter",
|
||||
"Starting your free 30 day trial": "Starting your free 30 day trial",
|
||||
@@ -317,6 +319,7 @@
|
||||
"Unable to open release notes": "Unable to open release notes",
|
||||
"Unknown error": "Unknown error",
|
||||
"Unknown": "Unknown",
|
||||
"Unleash Your Hardware": "Unleash Your Hardware",
|
||||
"Unleashed": "Unleashed",
|
||||
"unlimited": "unlimited",
|
||||
"Unraid {0} Available": "Unraid {0} Available",
|
||||
@@ -368,7 +371,7 @@
|
||||
"You have exceeded the number of devices allowed for your license. Please remove a device before adding another.": "You have exceeded the number of devices allowed for your license. Please remove a device before adding another.",
|
||||
"You have not activated the Flash Backup feature via the Unraid Connect plugin.": "You have not activated the Flash Backup feature via the Unraid Connect plugin.",
|
||||
"You may still update to releases dated prior to your update expiration date.": "You may still update to releases dated prior to your update expiration date.",
|
||||
"You're about to create a password to secure access to your system. This password is essential for managing and configuring your server. You’ll use this password every time you access the Unraid web interface.": "You're about to create a password to secure access to your system. This password is essential for managing and configuring your server. You’ll use this password every time you access the Unraid web interface.",
|
||||
"You're about to create a password to secure access to your system. This password is essential for managing and configuring your server. You'll use this password every time you access the Unraid web interface.": "You're about to create a password to secure access to your system. This password is essential for managing and configuring your server. You'll use this password every time you access the Unraid web interface.",
|
||||
"You're one step closer to enhancing your Unraid experience": "You're one step closer to enhancing your Unraid experience",
|
||||
"Your {0} Key has been replaced!": "Your {0} Key has been replaced!",
|
||||
"Your {0} license included one year of free updates at the time of purchase. You are now eligible to extend your license and access the latest OS updates. You are still eligible to access OS updates that were published on or before {1}.": "Your {0} license included one year of free updates at the time of purchase. You are now eligible to extend your license and access the latest OS updates. You are still eligible to access OS updates that were published on or before {1}.",
|
||||
@@ -377,5 +380,8 @@
|
||||
"Your license key is not eligible for Unraid OS {0}": "Your license key is not eligible for Unraid OS {0}",
|
||||
"Your license key's OS update eligibility has expired. Please renew your license key to enable updates released after your expiration date.": "Your license key's OS update eligibility has expired. Please renew your license key to enable updates released after your expiration date.",
|
||||
"Your Trial has expired": "Your Trial has expired",
|
||||
"Your Trial key has been extended!": "Your Trial key has been extended!"
|
||||
"Your Trial key has been extended!": "Your Trial key has been extended!",
|
||||
"Create an Unraid.net account and activate your key": "Create an Unraid.net account and activate your key",
|
||||
"Device is ready to configure": "Device is ready to configure",
|
||||
"Secure your device": "Secure your device"
|
||||
}
|
||||
|
||||
@@ -40,6 +40,57 @@ echo "$rsync_command"
|
||||
eval "$rsync_command"
|
||||
exit_code=$?
|
||||
|
||||
# Update the auth-request.php file to include the new web component JS
|
||||
update_auth_request() {
|
||||
local server_name="$1"
|
||||
# SSH into server and update auth-request.php
|
||||
ssh "root@${server_name}" "
|
||||
AUTH_REQUEST_FILE='/usr/local/emhttp/auth-request.php'
|
||||
WEB_COMPS_DIR='/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components/_nuxt/'
|
||||
|
||||
# Find JS files and modify paths
|
||||
mapfile -t JS_FILES < <(find \"\$WEB_COMPS_DIR\" -type f -name \"*.js\" | sed 's|/usr/local/emhttp||' | sort -u)
|
||||
|
||||
FILES_TO_ADD+=(\"\${JS_FILES[@]}\")
|
||||
|
||||
if grep -q '\$arrWhitelist' \"\$AUTH_REQUEST_FILE\"; then
|
||||
awk '
|
||||
BEGIN { in_array = 0 }
|
||||
/\\\$arrWhitelist\s*=\s*\[/ {
|
||||
in_array = 1
|
||||
print \$0
|
||||
next
|
||||
}
|
||||
in_array && /^\s*\]/ {
|
||||
in_array = 0
|
||||
print \$0
|
||||
next
|
||||
}
|
||||
!in_array || !/\/plugins\/dynamix\.my\.servers\/unraid-components\/_nuxt\/unraid-components\.client-/ {
|
||||
print \$0
|
||||
}
|
||||
' \"\$AUTH_REQUEST_FILE\" > \"\${AUTH_REQUEST_FILE}.tmp\"
|
||||
|
||||
# Now add new entries right after the opening bracket
|
||||
awk -v files_to_add=\"\$(printf '%s\n' \"\${FILES_TO_ADD[@]}\" | sort -u | awk '{printf \" \\\x27%s\\\x27,\n\", \$0}')\" '
|
||||
/\\\$arrWhitelist\s*=\s*\[/ {
|
||||
print \$0
|
||||
print files_to_add
|
||||
next
|
||||
}
|
||||
{ print }
|
||||
' \"\${AUTH_REQUEST_FILE}.tmp\" > \"\${AUTH_REQUEST_FILE}\"
|
||||
|
||||
rm \"\${AUTH_REQUEST_FILE}.tmp\"
|
||||
echo \"Updated \$AUTH_REQUEST_FILE with new web component JS files\"
|
||||
else
|
||||
echo \"\\\$arrWhitelist array not found in \$AUTH_REQUEST_FILE\"
|
||||
fi
|
||||
"
|
||||
}
|
||||
|
||||
update_auth_request "$server_name"
|
||||
|
||||
# Play built-in sound based on the operating system
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
# macOS
|
||||
|
||||
Reference in New Issue
Block a user