Files
api/web/components/UserProfile/DropdownContent.vue
Eli Bosley 345e83bfb0 feat: upgrade nuxt-custom-elements (#1461)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added new modal dialogs and UI components, including activation steps,
OS update feedback, and expanded notification management.
* Introduced a plugin to configure internationalization, state
management, and Apollo client support in web components.
* Added a new Log Viewer page with a streamlined interface for viewing
logs.

* **Improvements**
* Centralized Pinia state management by consolidating all stores to use
a shared global Pinia instance.
* Simplified component templates by removing redundant
internationalization host wrappers.
* Enhanced ESLint configuration with stricter rules and global variable
declarations.
* Refined custom element build process to prevent jQuery conflicts and
optimize minification.
* Updated component imports and templates for consistent structure and
maintainability.
* Streamlined log viewer dropdowns using simplified select components
with improved formatting.
* Improved notification sidebar with filtering by importance and modular
components.
* Replaced legacy notification popups with new UI components and added
automatic root session creation for localhost requests.
* Updated OS version display and user profile UI with refined styling
and component usage.

* **Bug Fixes**
* Fixed component tag capitalization and improved type annotations
across components.

* **Chores**
* Updated development dependencies including ESLint plugins and build
tools.
* Removed deprecated log viewer patch class and cleaned up related test
fixtures.
  * Removed unused imports and simplified Apollo client setup.
* Cleaned up test mocks and removed obsolete i18n host component tests.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210730229632804

---------

Co-authored-by: Pujit Mehrotra <pujit@lime-technology.com>
Co-authored-by: Zack Spear <zackspear@users.noreply.github.com>
2025-07-08 10:05:39 -04:00

247 lines
7.1 KiB
Vue

<script setup lang="ts">
import { computed } from 'vue';
import { storeToRefs } from 'pinia';
import {
ArrowPathIcon,
ArrowTopRightOnSquareIcon,
BellAlertIcon,
CogIcon,
ExclamationTriangleIcon,
KeyIcon,
UserIcon,
} from '@heroicons/vue/24/solid';
import { BrandLogoConnect } from '@unraid/ui';
import {
CONNECT_DASHBOARD,
WEBGUI_CONNECT_SETTINGS,
WEBGUI_TOOLS_DOWNGRADE,
WEBGUI_TOOLS_REGISTRATION,
WEBGUI_TOOLS_UPDATE,
} from '~/helpers/urls';
import type { UserProfileLink } from '~/types/userProfile';
import type { ComposerTranslation } from 'vue-i18n';
import { useAccountStore } from '~/store/account';
import { useErrorsStore } from '~/store/errors';
import { useServerStore } from '~/store/server';
import { useUpdateOsStore } from '~/store/updateOs';
import Beta from './Beta.vue';
import DropdownConnectStatus from './DropdownConnectStatus.vue';
import DropdownError from './DropdownError.vue';
import DropdownItem from './DropdownItem.vue';
import Keyline from './Keyline.vue';
const props = defineProps<{ t: ComposerTranslation }>();
const accountStore = useAccountStore();
const errorsStore = useErrorsStore();
const updateOsStore = useUpdateOsStore();
const { errors } = storeToRefs(errorsStore);
const {
keyActions,
connectPluginInstalled,
rebootType,
registered,
regUpdatesExpired,
stateData,
stateDataError,
} = storeToRefs(useServerStore());
const { available: osUpdateAvailable, availableWithRenewal: osUpdateAvailableWithRenewal } =
storeToRefs(updateOsStore);
const signInAction = computed(
() => stateData.value.actions?.filter((act: { name: string }) => act.name === 'signIn') ?? []
);
const signOutAction = computed(
() => stateData.value.actions?.filter((act: { name: string }) => act.name === 'signOut') ?? []
);
/**
* Filter out the renew action from the key actions so we can display it separately and link to the Tools > Registration page
*/
const filteredKeyActions = computed(() =>
keyActions.value?.filter((action) => !['renew'].includes(action.name))
);
const manageUnraidNetAccount = computed((): UserProfileLink => {
return {
external: true,
click: () => {
accountStore.manage();
},
icon: UserIcon,
text: props.t('Manage Unraid.net Account'),
title: props.t('Manage Unraid.net Account in new tab'),
};
});
const updateOsCheckForUpdatesButton = computed((): UserProfileLink => {
return {
click: () => {
updateOsStore.localCheckForUpdate();
},
icon: ArrowPathIcon,
text: props.t('Check for Update'),
};
});
const updateOsResponseModalOpenButton = computed((): UserProfileLink => {
return {
click: () => {
updateOsStore.setModalOpen(true);
},
emphasize: true,
icon: BellAlertIcon,
text: osUpdateAvailableWithRenewal.value
? props.t('Unraid OS {0} Released', [osUpdateAvailableWithRenewal.value])
: props.t('Unraid OS {0} Update Available', [osUpdateAvailable.value]),
};
});
const rebootDetectedButton = computed((): UserProfileLink => {
return {
href:
rebootType.value === 'downgrade'
? WEBGUI_TOOLS_DOWNGRADE.toString()
: WEBGUI_TOOLS_UPDATE.toString(),
icon: ExclamationTriangleIcon,
text:
rebootType.value === 'downgrade'
? props.t('Reboot Required for Downgrade')
: props.t('Reboot Required for Update'),
};
});
const updateOsButton = computed((): UserProfileLink[] => {
const btns = [];
if (rebootType.value === 'downgrade' || rebootType.value === 'update') {
btns.push(rebootDetectedButton.value);
return btns;
}
if (osUpdateAvailable.value) {
btns.push(updateOsResponseModalOpenButton.value);
} else {
btns.push(updateOsCheckForUpdatesButton.value);
}
return btns;
});
const links = computed((): UserProfileLink[] => {
return [
...(regUpdatesExpired.value
? [
{
href: WEBGUI_TOOLS_REGISTRATION.toString(),
icon: KeyIcon,
text: props.t('OS Update Eligibility Expired'),
title: props.t('Go to Tools > Registration to Learn More'),
},
]
: []),
// ensure we only show the update button when we don't have an error
...(!stateDataError.value ? [...updateOsButton.value] : []),
// connect plugin links
...(registered.value && connectPluginInstalled.value
? [
{
emphasize: !osUpdateAvailable.value, // only emphasize when we don't have an update available
external: true,
href: CONNECT_DASHBOARD.toString(),
icon: ArrowTopRightOnSquareIcon,
text: props.t('Go to Connect'),
title: props.t('Opens Connect in new tab'),
},
...[manageUnraidNetAccount.value],
...signOutAction.value,
]
: [...[manageUnraidNetAccount.value]]),
{
href: WEBGUI_CONNECT_SETTINGS.toString(),
icon: CogIcon,
text: props.t('Settings'),
title: props.t('Go to API Settings'),
},
];
});
const showErrors = computed(() => errors.value.length);
const showConnectStatus = computed(
() => !showErrors.value && !stateData.value.error && registered.value && connectPluginInstalled.value
);
const showKeyline = computed(
() =>
(showConnectStatus.value && (keyActions.value?.length || links.value.length)) ||
unraidConnectWelcome.value
);
const unraidConnectWelcome = computed(() => {
if (
connectPluginInstalled.value &&
!registered.value &&
!errors.value.length &&
!stateDataError.value
) {
return {
heading: props.t('Thank you for installing Connect!'),
message: props.t('Sign In to your Unraid.net account to get started'),
};
}
return undefined;
});
</script>
<template>
<div class="flex flex-col grow gap-y-8px">
<header
v-if="connectPluginInstalled"
class="flex flex-col items-start justify-between mt-8px mx-8px"
>
<h2 class="text-18px leading-none flex flex-row gap-x-4px items-center justify-between">
<BrandLogoConnect
gradient-start="currentcolor"
gradient-stop="currentcolor"
class="text-foreground w-[120px]"
/>
<Beta />
</h2>
<template v-if="unraidConnectWelcome">
<h3 class="text-16px font-semibold mt-2">
{{ unraidConnectWelcome.heading }}
</h3>
<p class="text-14px">
{{ unraidConnectWelcome.message }}
</p>
</template>
</header>
<ul class="list-reset flex flex-col gap-y-4px p-0">
<DropdownConnectStatus v-if="showConnectStatus" :t="t" />
<DropdownError v-if="showErrors" :t="t" />
<li v-if="showKeyline" class="my-8px">
<Keyline />
</li>
<li v-if="!registered && connectPluginInstalled">
<DropdownItem :item="signInAction[0]" :t="t" />
</li>
<template v-if="filteredKeyActions">
<li v-for="action in filteredKeyActions" :key="action.name">
<DropdownItem :item="action" :t="t" />
</li>
</template>
<template v-if="links.length">
<li v-for="(link, index) in links" :key="`link_${index}`">
<DropdownItem :item="link" :t="t" />
</li>
</template>
</ul>
</div>
</template>