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>
This commit is contained in:
Eli Bosley
2025-07-08 10:05:39 -04:00
committed by GitHub
parent 7be8bc84d3
commit 345e83bfb0
109 changed files with 955 additions and 746 deletions

View File

@@ -1,3 +1,9 @@
{
"permissions": {
"allow": [
"Bash(rg:*)",
"Bash(find:*)"
]
},
"enableAllProjectMcpServers": false
}

View File

@@ -706,6 +706,7 @@ $.ajaxPrefilter(function(s, orig, xhr){
<?include "$docroot/plugins/dynamix.my.servers/include/myservers1.php"?>
</head>
<body>
<unraid-modals></unraid-modals>
<div id="displaybox">
<div class="upgrade_notice" style="display:none"></div>
<div id="header" class="<?=$display['banner']?>">

View File

@@ -72,6 +72,9 @@ if (is_localhost() && !is_good_session()) {
);
}
private addModalsWebComponent(source: string): string {
return source.replace('<body>', '<body>\n<unraid-modals></unraid-modals>');
}
private applyToSource(fileContent: string): string {
const transformers = [
this.removeNotificationBell.bind(this),
@@ -79,6 +82,7 @@ if (is_localhost() && !is_good_session()) {
this.addToaster.bind(this),
this.patchGuiBootAuth.bind(this),
this.hideHeaderLogo.bind(this),
this.addModalsWebComponent.bind(this),
];
return transformers.reduce((content, transformer) => transformer(content), fileContent);

View File

@@ -1,67 +0,0 @@
import { Logger } from '@nestjs/common';
import { readFile, rm, writeFile } from 'node:fs/promises';
import { fileExists } from '@app/core/utils/files/file-exists.js';
import {
FileModification,
ShouldApplyWithReason,
} from '@app/unraid-api/unraid-file-modifier/file-modification.js';
export default class LogViewerModification extends FileModification {
id: string = 'log-viewer';
public readonly filePath: string =
'/usr/local/emhttp/plugins/dynamix.my.servers/LogViewer.page' as const;
private readonly logViewerConfig: string = `
Menu="UNRAID-OS"
Title="Log Viewer (new)"
Icon="icon-log"
Tag="list"
---
<unraid-i18n-host>
<unraid-log-viewer></unraid-log-viewer>
</unraid-i18n-host>
`.trimStart();
constructor(logger: Logger) {
super(logger);
}
protected async generatePatch(overridePath?: string): Promise<string> {
const currentContent = (await fileExists(this.filePath))
? await readFile(this.filePath, 'utf8')
: '';
return this.createPatchWithDiff(
overridePath ?? this.filePath,
currentContent,
this.logViewerConfig
);
}
async shouldApply(): Promise<ShouldApplyWithReason> {
if (await this.isUnraidVersionGreaterThanOrEqualTo('7.2.0')) {
return {
shouldApply: false,
reason: 'Skipping for Unraid 7.2 or later, where the Unraid API is integrated.',
};
}
const alreadyConfigured = await fileExists(this.filePath);
if (alreadyConfigured) {
return { shouldApply: false, reason: 'LogViewer configuration already exists' };
}
return { shouldApply: true, reason: 'No LogViewer config for the API configured yet' };
}
async apply(): Promise<string> {
await this.rollback();
await writeFile(this.filePath, this.logViewerConfig, { mode: 0o644 });
return this.logViewerConfig;
}
async rollback(): Promise<void> {
await rm(this.getPathToAppliedPatch(), { force: true });
await rm(this.filePath, { force: true });
}
}

View File

@@ -53,8 +53,13 @@ Index: /usr/local/emhttp/plugins/dynamix/include/DefaultPageLayout.php
}
function closeNotifier() {
@@ -699,11 +708,11 @@
@@ -695,15 +704,16 @@
});
</script>
<?include "$docroot/plugins/dynamix.my.servers/include/myservers1.php"?>
</head>
<body>
+<unraid-modals></unraid-modals>
<div id="displaybox">
<div class="upgrade_notice" style="display:none"></div>
<div id="header" class="<?=$display['banner']?>">
@@ -66,7 +71,7 @@ Index: /usr/local/emhttp/plugins/dynamix/include/DefaultPageLayout.php
<?include "$docroot/plugins/dynamix.my.servers/include/myservers2.php"?>
</div>
<a href="#" class="move_to_end" title="<?=_('Move To End')?>"><i class="fa fa-arrow-circle-down"></i></a>
@@ -748,12 +757,12 @@
@@ -748,12 +758,12 @@
}
// create list of nchan scripts to be started
if (isset($button['Nchan'])) nchan_merge($button['root'], $button['Nchan']);
@@ -80,7 +85,7 @@ Index: /usr/local/emhttp/plugins/dynamix/include/DefaultPageLayout.php
foreach ($buttons as $button) {
annotate($button['file']);
// include page specific stylesheets (if existing)
@@ -960,26 +969,18 @@
@@ -960,26 +970,18 @@
case 'warning': bell2++; break;
case 'normal' : bell3++; break;
}
@@ -112,7 +117,7 @@ Index: /usr/local/emhttp/plugins/dynamix/include/DefaultPageLayout.php
});
<?if ($wlan0):?>
@@ -1363,7 +1364,8 @@
@@ -1363,7 +1365,8 @@
}
}
}

View File

@@ -3,6 +3,4 @@ Title="API Keys"
Icon="icon-u-key"
Tag="key"
---
<unraid-i18n-host>
<unraid-api-key-manager />
</unraid-i18n-host>
<unraid-api-key-manager />

View File

@@ -577,5 +577,5 @@ _(GraphQL API Developer Sandbox)_:
</div>
<!-- start unraid-api section -->
<unraid-i18n-host><unraid-connect-settings></unraid-connect-settings></unraid-i18n-host>
<unraid-connect-settings></unraid-connect-settings>
<!-- end unraid-api section -->

View File

@@ -0,0 +1,6 @@
Menu="UNRAID-OS"
Title="Log Viewer (new)"
Icon="icon-log"
Tag="list"
---
<unraid-log-viewer></unraid-log-viewer>

View File

@@ -18,6 +18,4 @@ require_once "$docroot/plugins/dynamix/include/ReplaceKey.php";
$replaceKey = new ReplaceKey();
$replaceKey->check(true);
?>
<unraid-i18n-host>
<unraid-registration></unraid-registration>
</unraid-i18n-host>
<unraid-registration></unraid-registration>

View File

@@ -17,20 +17,5 @@ $wCTranslations = new WebComponentTranslations();
?>
<script>
window.LOCALE_DATA = '<?= $wCTranslations->getTranslationsJson(true) ?>';
/**
* So we're not needing to modify DefaultLayout with an additional include, we'll add the Modals web component to the bottom of the body.
*/
const i18nHostWebComponent = 'unraid-i18n-host';
const modalsWebComponent = 'unraid-modals';
if (!document.getElementsByTagName(modalsWebComponent).length) {
const $body = document.getElementsByTagName('body')[0];
const $i18nHost = document.createElement(i18nHostWebComponent);
const $modals = document.createElement(modalsWebComponent);
$body.appendChild($i18nHost);
$i18nHost.appendChild($modals);
}
</script>
<unraid-i18n-host>
<unraid-user-profile server="<?= $serverState->getServerStateJsonForHtmlAttr() ?>"></unraid-user-profile>
</unraid-i18n-host>
<unraid-user-profile server="<?= $serverState->getServerStateJsonForHtmlAttr() ?>"></unraid-user-profile>

View File

@@ -17,6 +17,4 @@ $wcExtractor = new WebComponentsExtractor();
echo $wcExtractor->getScriptTagHtml();
?>
<unraid-i18n-host>
<unraid-sso-button ssoenabled="<?= $serverState->ssoEnabled ? "true" : "false" ?>"></unraid-sso-button>
</unraid-i18n-host>
<unraid-sso-button ssoenabled="<?= $serverState->ssoEnabled ? "true" : "false" ?>"></unraid-sso-button>

View File

@@ -5,6 +5,7 @@ class WebComponentsExtractor
{
private const PREFIXED_PATH = '/plugins/dynamix.my.servers/unraid-components/';
private const RICH_COMPONENTS_ENTRY = 'unraid-components.client.mjs';
private const RICH_COMPONENTS_ENTRY_JS = 'unraid-components.client.js';
private const UI_ENTRY = 'src/register.ts';
private const UI_STYLES_ENTRY = 'style.css';
@@ -47,7 +48,16 @@ class WebComponentsExtractor
$subfolder = $this->getRelativePath($manifestPath);
foreach ($manifest as $key => $value) {
if (strpos($key, self::RICH_COMPONENTS_ENTRY) !== false && isset($value["file"])) {
// Skip timestamp entries
if ($key === 'ts' || !is_array($value)) {
continue;
}
// Check for both old format (direct key match) and new format (path-based key)
$matchesMjs = strpos($key, self::RICH_COMPONENTS_ENTRY) !== false;
$matchesJs = strpos($key, self::RICH_COMPONENTS_ENTRY_JS) !== false;
if (($matchesMjs || $matchesJs) && isset($value["file"])) {
return ($subfolder ? $subfolder . '/' : '') . $value["file"];
}
}
@@ -59,7 +69,7 @@ class WebComponentsExtractor
{
$jsFile = $this->getRichComponentsFile();
if (empty($jsFile)) {
return '<script>console.error("%cNo matching key containing \'' . self::RICH_COMPONENTS_ENTRY . '\' found.", "font-weight: bold; color: white; background-color: red");</script>';
return '<script>console.error("%cNo matching key containing \'' . self::RICH_COMPONENTS_ENTRY . '\' or \'' . self::RICH_COMPONENTS_ENTRY_JS . '\' found.", "font-weight: bold; color: white; background-color: red");</script>';
}
return '<script src="' . $this->getAssetPath($jsFile) . '"></script>';
}

View File

@@ -14,7 +14,5 @@ $wcExtractor = new WebComponentsExtractor();
echo $wcExtractor->getScriptTagHtml();
?>
<unraid-i18n-host>
<unraid-welcome-modal></unraid-welcome-modal>
</unraid-i18n-host>
<unraid-welcome-modal></unraid-welcome-modal>

View File

@@ -146,9 +146,7 @@ function confirmDowngrade() {
}
</script>
<unraid-i18n-host>
<unraid-downgrade-os
<unraid-downgrade-os
reboot-version="<?= $rebootDetails->rebootVersion ?>"
restore-version="<?= $rebootDetails->previousVersion ?>"
restore-release-date="<?= $rebootDetails->previousReleaseDate ?>"></unraid-downgrade-os>
</unraid-i18n-host>

View File

@@ -49,6 +49,4 @@ function flashBackup() {
}
</script>
<unraid-i18n-host>
<unraid-update-os reboot-version="<?= $rebootDetails->rebootVersion ?>"></unraid-update-os>
</unraid-i18n-host>
<unraid-update-os reboot-version="<?= $rebootDetails->rebootVersion ?>"></unraid-update-os>

333
pnpm-lock.yaml generated
View File

@@ -432,7 +432,7 @@ importers:
version: 9.29.0(jiti@2.4.2)
eslint-plugin-import:
specifier: ^2.31.0
version: 2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))
version: 2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.29.0(jiti@2.4.2))
eslint-plugin-n:
specifier: ^17.0.0
version: 17.20.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)
@@ -922,7 +922,7 @@ importers:
version: 10.1.5(eslint@9.29.0(jiti@2.4.2))
eslint-plugin-import:
specifier: ^2.31.0
version: 2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))
version: 2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.29.0(jiti@2.4.2))
eslint-plugin-no-relative-import-paths:
specifier: ^1.6.1
version: 1.6.1
@@ -1098,6 +1098,9 @@ importers:
vue-i18n:
specifier: ^11.0.0
version: 11.1.6(vue@3.5.17(typescript@5.8.3))
vue-web-component-wrapper:
specifier: ^1.7.7
version: 1.7.7
vuetify:
specifier: ^3.7.14
version: 3.8.10(typescript@5.8.3)(vite-plugin-vuetify@2.1.0)(vue@3.5.17(typescript@5.8.3))
@@ -1156,6 +1159,9 @@ importers:
'@types/semver':
specifier: ^7.5.8
version: 7.7.0
'@typescript-eslint/eslint-plugin':
specifier: ^8.34.1
version: 8.34.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)
'@unraid/tailwind-rem-to-rem':
specifier: ^1.1.0
version: 1.1.0(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.12.4)(@types/node@22.15.32)(typescript@5.8.3)))
@@ -1183,6 +1189,12 @@ importers:
eslint-config-prettier:
specifier: ^10.0.0
version: 10.1.5(eslint@9.29.0(jiti@2.4.2))
eslint-import-resolver-typescript:
specifier: ^4.4.4
version: 4.4.4(eslint-plugin-import-x@4.15.2(@typescript-eslint/utils@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.29.0(jiti@2.4.2)))(eslint-plugin-import@2.31.0)(eslint@9.29.0(jiti@2.4.2))
eslint-plugin-import:
specifier: ^2.31.0
version: 2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.29.0(jiti@2.4.2))
happy-dom:
specifier: ^18.0.0
version: 18.0.0
@@ -1193,8 +1205,8 @@ importers:
specifier: ^3.14.1592
version: 3.17.5(@parcel/watcher@2.5.1)(@types/node@22.15.32)(db0@0.3.2)(eslint@9.29.0(jiti@2.4.2))(ioredis@5.6.1)(magicast@0.3.5)(meow@13.2.0)(optionator@0.9.4)(rollup@4.44.0)(stylus@0.57.0)(terser@5.43.1)(tsx@4.20.3)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.32)(jiti@2.4.2)(stylus@0.57.0)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0))(vue-tsc@2.2.10(typescript@5.8.3))(xml2js@0.6.2)(yaml@2.8.0)
nuxt-custom-elements:
specifier: 2.0.0-beta.18
version: 2.0.0-beta.18(webpack@5.98.0(@swc/core@1.12.4)(esbuild@0.23.1))
specifier: 2.0.0-beta.32
version: 2.0.0-beta.32(webpack@5.99.9(@swc/core@1.12.4)(esbuild@0.23.1))
prettier:
specifier: 3.5.3
version: 3.5.3
@@ -1216,6 +1228,9 @@ importers:
typescript:
specifier: ^5.7.3
version: 5.8.3
vite:
specifier: ^6.3.5
version: 6.3.5(@types/node@22.15.32)(jiti@2.4.2)(stylus@0.57.0)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)
vite-plugin-remove-console:
specifier: ^2.2.0
version: 2.2.0
@@ -3117,9 +3132,15 @@ packages:
'@jridgewell/sourcemap-codec@1.5.0':
resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
'@jridgewell/sourcemap-codec@1.5.4':
resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==}
'@jridgewell/trace-mapping@0.3.25':
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
'@jridgewell/trace-mapping@0.3.29':
resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==}
'@jridgewell/trace-mapping@0.3.9':
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
@@ -4668,6 +4689,9 @@ packages:
'@types/node@22.15.32':
resolution: {integrity: sha512-3jigKqgSjsH6gYZv2nEsqdXfZqIFGAV36XYYjf9KGZ3PSG+IhLecqPnI310RvjutyMwifE2hhhNEklOUrvx/wA==}
'@types/node@22.16.0':
resolution: {integrity: sha512-B2egV9wALML1JCpv3VQoQ+yesQKAmNMBIAY7OteVrikcOcAkWm+dGL6qpeCktPjAv6N1JLnhbNiqS35UpFyBsQ==}
'@types/normalize-package-data@2.4.4':
resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==}
@@ -5873,6 +5897,11 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
browserslist@4.25.1:
resolution: {integrity: sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
bser@2.1.1:
resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==}
@@ -6030,6 +6059,9 @@ packages:
caniuse-lite@1.0.30001724:
resolution: {integrity: sha512-WqJo7p0TbHDOythNTqYujmaJTvtYRZrjpP8TCvH6Vb9CYJerJNKamKzIWOM4BkQatWj9H2lYulpdAQNBe7QhNA==}
caniuse-lite@1.0.30001727:
resolution: {integrity: sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==}
capital-case@1.0.4:
resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==}
@@ -6937,9 +6969,6 @@ packages:
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
engines: {node: '>= 0.4'}
defu@6.1.2:
resolution: {integrity: sha512-+uO4+qr7msjNNWKYPHqN/3+Dx3NFkmIzayk2L1MyZQlvgZb/J1A0fo410dpKrN2SnqFjt8n4JL8fDJE0wIgjFQ==}
defu@6.1.4:
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
@@ -7184,6 +7213,9 @@ packages:
electron-to-chromium@1.5.171:
resolution: {integrity: sha512-scWpzXEJEMrGJa4Y6m/tVotb0WuvNmasv3wWVzUAeCgKU0ToFOhUW6Z+xWnRQANMYGxN4ngJXIThgBJOqzVPCQ==}
electron-to-chromium@1.5.180:
resolution: {integrity: sha512-ED+GEyEh3kYMwt2faNmgMB0b8O5qtATGgR4RmRsIp4T6p7B8vdMbIedYndnvZfsaXvSzegtpfqRMDNCjjiSduA==}
emoji-regex@10.4.0:
resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==}
@@ -7217,6 +7249,10 @@ packages:
resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==}
engines: {node: '>=10.13.0'}
enhanced-resolve@5.18.2:
resolution: {integrity: sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==}
engines: {node: '>=10.13.0'}
enquirer@2.3.6:
resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==}
engines: {node: '>=8.6'}
@@ -7520,6 +7556,19 @@ packages:
eslint-import-resolver-node@0.3.9:
resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==}
eslint-import-resolver-typescript@4.4.4:
resolution: {integrity: sha512-1iM2zeBvrYmUNTj2vSC/90JTHDth+dfOfiNKkxApWRsTJYNrc8rOdxxIf5vazX+BiAXTeOT0UvWpGI/7qIWQOw==}
engines: {node: ^16.17.0 || >=18.6.0}
peerDependencies:
eslint: '*'
eslint-plugin-import: '*'
eslint-plugin-import-x: '*'
peerDependenciesMeta:
eslint-plugin-import:
optional: true
eslint-plugin-import-x:
optional: true
eslint-merge-processors@2.0.0:
resolution: {integrity: sha512-sUuhSf3IrJdGooquEUB5TNpGNpBoQccbnaLHsb1XkBLUPPqCNivCpY05ZcpCOiV9uHwO2yxXEWVczVclzMxYlA==}
peerDependencies:
@@ -8559,11 +8608,17 @@ packages:
html-sloppy-escaper@0.1.0:
resolution: {integrity: sha512-ONSUC5HwiImkny/29ApddyM+BxpqjgTZ+pOag6y39Q5FQgJuWypPLl7cGDpPYp1RtC5+6Wi5yQld3zAXhlO3xg==}
html-webpack-plugin@5.5.3:
resolution: {integrity: sha512-6YrDKTuqaP/TquFH7h4srYWsZx+x6k6+FbsTm0ziCwGHDP78Unr1r9F/H4+sGmMbX08GQcJ+K64x55b+7VM/jg==}
html-webpack-plugin@5.6.3:
resolution: {integrity: sha512-QSf1yjtSAsmf7rYBV7XX86uua4W/vkhIt0xNXKbsi2foEeW7vjJQz4bhnpL3xH+l1ryl1680uNv968Z+X6jSYg==}
engines: {node: '>=10.13.0'}
peerDependencies:
'@rspack/core': 0.x || 1.x
webpack: ^5.20.0
peerDependenciesMeta:
'@rspack/core':
optional: true
webpack:
optional: true
htmlparser2@6.1.0:
resolution: {integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==}
@@ -8821,6 +8876,9 @@ packages:
resolution: {integrity: sha512-f4RqJKBUe5rQkJ2eJEJBXSticB3hGbN9j0yxxMQFqIW89Jp9WYFtzfTcRlstDKVUTRzSOTLKRfO9vIztenwtxA==}
engines: {node: '>=18.20'}
is-bun-module@2.0.0:
resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==}
is-callable@1.2.7:
resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
engines: {node: '>= 0.4'}
@@ -9868,10 +9926,6 @@ packages:
module-details-from-path@1.0.3:
resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==}
mrmime@1.0.1:
resolution: {integrity: sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==}
engines: {node: '>=10'}
mrmime@2.0.1:
resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
engines: {node: '>=10'}
@@ -10123,8 +10177,8 @@ packages:
nullthrows@1.1.1:
resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==}
nuxt-custom-elements@2.0.0-beta.18:
resolution: {integrity: sha512-9FLjCgxHl9xcfQlRexX1TXk6vQC10j2zafhHXCPYC2lQ3xJ7r1sRC9r2XJy7B7tyOJCLPsEEtlD2EWXECeBzdA==}
nuxt-custom-elements@2.0.0-beta.32:
resolution: {integrity: sha512-uT8v+3f6N68/r0wOgcxyb2h9nxiDm8yeEQK8Ura4jr5ifMGsZebFZ272XgeNM2DH39vZ3RLeP1g3MVQQY4+nFg==}
nuxt@3.17.5:
resolution: {integrity: sha512-HWTWpM1/RDcCt9DlnzrPcNvUmGqc62IhlZJvr7COSfnJq2lKYiBKIIesEaOF+57Qjw7TfLPc1DQVBNtxfKBxEw==}
@@ -11758,8 +11812,8 @@ packages:
resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==}
engines: {node: '>=10'}
sirv@1.0.19:
resolution: {integrity: sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==}
sirv@2.0.4:
resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==}
engines: {node: '>= 10'}
sirv@3.0.1:
@@ -11890,6 +11944,10 @@ packages:
resolution: {integrity: sha512-l0x1D6vhnsNUGPFVDx45eif0y6eedVC8nm5uACTrVFJFtl2mLRW17aWtVyxFCpn5t94VUPkjU8vSLwIuwwqtJQ==}
engines: {node: '>=12.0.0'}
stable-hash-x@0.2.0:
resolution: {integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==}
engines: {node: '>=12.0.0'}
stack-trace@0.0.10:
resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==}
@@ -12344,10 +12402,6 @@ packages:
toml@3.0.0:
resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==}
totalist@1.1.0:
resolution: {integrity: sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==}
engines: {node: '>=6'}
totalist@3.0.1:
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
engines: {node: '>=6'}
@@ -13151,6 +13205,12 @@ packages:
peerDependencies:
typescript: '>=5.0.0'
vue-web-component-wrapper@1.6.9:
resolution: {integrity: sha512-65Ybkjg2Y+MAMsx62AgEL2n5RCfzSvqbzJLEy6WvPalLATGEUhfT88Lbx5EYC6yzvZArfLKHq7Zl75Dg29ssNQ==}
vue-web-component-wrapper@1.7.7:
resolution: {integrity: sha512-2uy6VdN8AwSzCeqc9tV4ZK2HKtgZ/NWL1rvdgOsddF1UFtszBZHKyQT9sDBUc4BpyXmP7f8tmI1rI0n/A6Qptw==}
vue@3.5.17:
resolution: {integrity: sha512-LbHV3xPN9BeljML+Xctq4lbz2lVHCR6DtbpTf5XIO6gugpXUN49j2QQPcMj086r9+AkJ0FfUT8xjulKKBkkr9g==}
peerDependencies:
@@ -13200,8 +13260,8 @@ packages:
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
engines: {node: '>=12'}
webpack-bundle-analyzer@4.9.0:
resolution: {integrity: sha512-+bXGmO1LyiNx0i9enBu3H8mv42sj/BJWhZNFwjz92tVnBa9J3JMGo2an2IXlEleoDOPn/Hofl5hr/xCpObUDtw==}
webpack-bundle-analyzer@4.10.2:
resolution: {integrity: sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==}
engines: {node: '>= 10.13.0'}
hasBin: true
@@ -13212,8 +13272,8 @@ packages:
webpack-virtual-modules@0.6.2:
resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
webpack@5.98.0:
resolution: {integrity: sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==}
webpack@5.99.9:
resolution: {integrity: sha512-brOPwM3JnmOa+7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/s/+MNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg==}
engines: {node: '>=10.13.0'}
hasBin: true
peerDependencies:
@@ -15339,7 +15399,7 @@ snapshots:
'@graphql-tools/optimize@2.0.0(graphql@16.11.0)':
dependencies:
graphql: 16.11.0
tslib: 2.6.3
tslib: 2.8.1
'@graphql-tools/prisma-loader@8.0.17(@types/node@22.15.32)(crossws@0.3.5)(graphql@16.11.0)':
dependencies:
@@ -15385,7 +15445,7 @@ snapshots:
'@ardatan/relay-compiler': 12.0.2(graphql@16.11.0)
'@graphql-tools/utils': 10.8.6(graphql@16.11.0)
graphql: 16.11.0
tslib: 2.6.3
tslib: 2.8.1
transitivePeerDependencies:
- encoding
@@ -15394,7 +15454,7 @@ snapshots:
'@ardatan/relay-compiler': 12.0.3(graphql@16.11.0)
'@graphql-tools/utils': 10.8.6(graphql@16.11.0)
graphql: 16.11.0
tslib: 2.6.3
tslib: 2.8.1
transitivePeerDependencies:
- encoding
@@ -15689,15 +15749,24 @@ snapshots:
'@jridgewell/sourcemap-codec@1.5.0': {}
'@jridgewell/sourcemap-codec@1.5.4':
optional: true
'@jridgewell/trace-mapping@0.3.25':
dependencies:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.0
'@jridgewell/trace-mapping@0.3.29':
dependencies:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.4
optional: true
'@jridgewell/trace-mapping@0.3.9':
dependencies:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.0
'@jridgewell/sourcemap-codec': 1.5.4
optional: true
'@js-sdsl/ordered-map@4.4.2': {}
@@ -16688,7 +16757,7 @@ snapshots:
'@pm2/pm2-version-check@1.0.4':
dependencies:
debug: 4.4.1
debug: 4.4.0(supports-color@5.5.0)
transitivePeerDependencies:
- supports-color
@@ -17475,11 +17544,13 @@ snapshots:
dependencies:
'@types/eslint': 9.6.1
'@types/estree': 1.0.8
optional: true
'@types/eslint@9.6.1':
dependencies:
'@types/estree': 1.0.8
'@types/json-schema': 7.0.15
optional: true
'@types/estree@1.0.6': {}
@@ -17582,6 +17653,11 @@ snapshots:
dependencies:
undici-types: 6.21.0
'@types/node@22.16.0':
dependencies:
undici-types: 6.21.0
optional: true
'@types/normalize-package-data@2.4.4': {}
'@types/parse-path@7.0.3': {}
@@ -18342,20 +18418,26 @@ snapshots:
dependencies:
'@webassemblyjs/helper-numbers': 1.13.2
'@webassemblyjs/helper-wasm-bytecode': 1.13.2
optional: true
'@webassemblyjs/floating-point-hex-parser@1.13.2': {}
'@webassemblyjs/floating-point-hex-parser@1.13.2':
optional: true
'@webassemblyjs/helper-api-error@1.13.2': {}
'@webassemblyjs/helper-api-error@1.13.2':
optional: true
'@webassemblyjs/helper-buffer@1.14.1': {}
'@webassemblyjs/helper-buffer@1.14.1':
optional: true
'@webassemblyjs/helper-numbers@1.13.2':
dependencies:
'@webassemblyjs/floating-point-hex-parser': 1.13.2
'@webassemblyjs/helper-api-error': 1.13.2
'@xtuc/long': 4.2.2
optional: true
'@webassemblyjs/helper-wasm-bytecode@1.13.2': {}
'@webassemblyjs/helper-wasm-bytecode@1.13.2':
optional: true
'@webassemblyjs/helper-wasm-section@1.14.1':
dependencies:
@@ -18363,16 +18445,20 @@ snapshots:
'@webassemblyjs/helper-buffer': 1.14.1
'@webassemblyjs/helper-wasm-bytecode': 1.13.2
'@webassemblyjs/wasm-gen': 1.14.1
optional: true
'@webassemblyjs/ieee754@1.13.2':
dependencies:
'@xtuc/ieee754': 1.2.0
optional: true
'@webassemblyjs/leb128@1.13.2':
dependencies:
'@xtuc/long': 4.2.2
optional: true
'@webassemblyjs/utf8@1.13.2': {}
'@webassemblyjs/utf8@1.13.2':
optional: true
'@webassemblyjs/wasm-edit@1.14.1':
dependencies:
@@ -18384,6 +18470,7 @@ snapshots:
'@webassemblyjs/wasm-opt': 1.14.1
'@webassemblyjs/wasm-parser': 1.14.1
'@webassemblyjs/wast-printer': 1.14.1
optional: true
'@webassemblyjs/wasm-gen@1.14.1':
dependencies:
@@ -18392,6 +18479,7 @@ snapshots:
'@webassemblyjs/ieee754': 1.13.2
'@webassemblyjs/leb128': 1.13.2
'@webassemblyjs/utf8': 1.13.2
optional: true
'@webassemblyjs/wasm-opt@1.14.1':
dependencies:
@@ -18399,6 +18487,7 @@ snapshots:
'@webassemblyjs/helper-buffer': 1.14.1
'@webassemblyjs/wasm-gen': 1.14.1
'@webassemblyjs/wasm-parser': 1.14.1
optional: true
'@webassemblyjs/wasm-parser@1.14.1':
dependencies:
@@ -18408,11 +18497,13 @@ snapshots:
'@webassemblyjs/ieee754': 1.13.2
'@webassemblyjs/leb128': 1.13.2
'@webassemblyjs/utf8': 1.13.2
optional: true
'@webassemblyjs/wast-printer@1.14.1':
dependencies:
'@webassemblyjs/ast': 1.14.1
'@xtuc/long': 4.2.2
optional: true
'@whatwg-node/disposablestack@0.0.5':
dependencies:
@@ -18477,9 +18568,11 @@ snapshots:
dependencies:
tslib: 2.8.1
'@xtuc/ieee754@1.2.0': {}
'@xtuc/ieee754@1.2.0':
optional: true
'@xtuc/long@4.2.2': {}
'@xtuc/long@4.2.2':
optional: true
JSONStream@1.3.5:
dependencies:
@@ -18515,7 +18608,7 @@ snapshots:
acorn-walk@8.3.4:
dependencies:
acorn: 8.14.1
acorn: 8.15.0
acorn@7.4.1: {}
@@ -18544,6 +18637,7 @@ snapshots:
dependencies:
ajv: 8.17.1
fast-deep-equal: 3.1.3
optional: true
ajv@6.12.6:
dependencies:
@@ -18983,6 +19077,14 @@ snapshots:
node-releases: 2.0.19
update-browserslist-db: 1.1.3(browserslist@4.25.0)
browserslist@4.25.1:
dependencies:
caniuse-lite: 1.0.30001727
electron-to-chromium: 1.5.180
node-releases: 2.0.19
update-browserslist-db: 1.1.3(browserslist@4.25.1)
optional: true
bser@2.1.1:
dependencies:
node-int64: 0.4.0
@@ -19173,6 +19275,9 @@ snapshots:
caniuse-lite@1.0.30001724: {}
caniuse-lite@1.0.30001727:
optional: true
capital-case@1.0.4:
dependencies:
no-case: 3.0.4
@@ -19277,7 +19382,8 @@ snapshots:
chownr@3.0.0: {}
chrome-trace-event@1.0.4: {}
chrome-trace-event@1.0.4:
optional: true
ci-info@4.2.0: {}
@@ -20172,8 +20278,6 @@ snapshots:
has-property-descriptors: 1.0.2
object-keys: 1.1.1
defu@6.1.2: {}
defu@6.1.4: {}
degenerator@5.0.1:
@@ -20416,6 +20520,9 @@ snapshots:
electron-to-chromium@1.5.171: {}
electron-to-chromium@1.5.180:
optional: true
emoji-regex@10.4.0: {}
emoji-regex@7.0.3: {}
@@ -20443,6 +20550,12 @@ snapshots:
graceful-fs: 4.2.11
tapable: 2.2.1
enhanced-resolve@5.18.2:
dependencies:
graceful-fs: 4.2.11
tapable: 2.2.2
optional: true
enquirer@2.3.6:
dependencies:
ansi-colors: 4.1.3
@@ -20831,17 +20944,34 @@ snapshots:
transitivePeerDependencies:
- supports-color
eslint-import-resolver-typescript@4.4.4(eslint-plugin-import-x@4.15.2(@typescript-eslint/utils@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.29.0(jiti@2.4.2)))(eslint-plugin-import@2.31.0)(eslint@9.29.0(jiti@2.4.2)):
dependencies:
debug: 4.4.1
eslint: 9.29.0(jiti@2.4.2)
eslint-import-context: 0.1.8(unrs-resolver@1.9.1)
get-tsconfig: 4.10.1
is-bun-module: 2.0.0
stable-hash-x: 0.2.0
tinyglobby: 0.2.14
unrs-resolver: 1.9.1
optionalDependencies:
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.29.0(jiti@2.4.2))
eslint-plugin-import-x: 4.15.2(@typescript-eslint/utils@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.29.0(jiti@2.4.2))
transitivePeerDependencies:
- supports-color
eslint-merge-processors@2.0.0(eslint@9.29.0(jiti@2.4.2)):
dependencies:
eslint: 9.29.0(jiti@2.4.2)
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.29.0(jiti@2.4.2)):
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.29.0(jiti@2.4.2)):
dependencies:
debug: 3.2.7
optionalDependencies:
'@typescript-eslint/parser': 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)
eslint: 9.29.0(jiti@2.4.2)
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 4.4.4(eslint-plugin-import-x@4.15.2(@typescript-eslint/utils@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.29.0(jiti@2.4.2)))(eslint-plugin-import@2.31.0)(eslint@9.29.0(jiti@2.4.2))
transitivePeerDependencies:
- supports-color
@@ -20870,7 +21000,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2)):
eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.29.0(jiti@2.4.2)):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.8
@@ -20881,7 +21011,7 @@ snapshots:
doctrine: 2.1.0
eslint: 9.29.0(jiti@2.4.2)
eslint-import-resolver-node: 0.3.9
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.29.0(jiti@2.4.2))
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.29.0(jiti@2.4.2))
hasown: 2.0.2
is-core-module: 2.16.1
is-glob: 4.0.3
@@ -20996,6 +21126,7 @@ snapshots:
dependencies:
esrecurse: 4.3.0
estraverse: 4.3.0
optional: true
eslint-scope@8.4.0:
dependencies:
@@ -21079,7 +21210,8 @@ snapshots:
dependencies:
estraverse: 5.3.0
estraverse@4.3.0: {}
estraverse@4.3.0:
optional: true
estraverse@5.3.0: {}
@@ -21772,7 +21904,8 @@ snapshots:
dependencies:
is-glob: 4.0.3
glob-to-regexp@0.4.1: {}
glob-to-regexp@0.4.1:
optional: true
glob@10.4.5:
dependencies:
@@ -22120,14 +22253,15 @@ snapshots:
dependencies:
html-escaper: 3.0.3
html-webpack-plugin@5.5.3(webpack@5.98.0(@swc/core@1.12.4)(esbuild@0.23.1)):
html-webpack-plugin@5.6.3(webpack@5.99.9(@swc/core@1.12.4)(esbuild@0.23.1)):
dependencies:
'@types/html-minifier-terser': 6.1.0
html-minifier-terser: 6.1.0
lodash: 4.17.21
pretty-error: 4.0.0
tapable: 2.2.1
webpack: 5.98.0(@swc/core@1.12.4)(esbuild@0.23.1)
optionalDependencies:
webpack: 5.99.9(@swc/core@1.12.4)(esbuild@0.23.1)
htmlparser2@6.1.0:
dependencies:
@@ -22461,6 +22595,10 @@ snapshots:
dependencies:
builtin-modules: 5.0.0
is-bun-module@2.0.0:
dependencies:
semver: 7.7.2
is-callable@1.2.7: {}
is-core-module@2.16.1:
@@ -22529,7 +22667,7 @@ snapshots:
is-lower-case@2.0.2:
dependencies:
tslib: 2.6.3
tslib: 2.8.1
is-map@2.0.3: {}
@@ -22626,7 +22764,7 @@ snapshots:
is-upper-case@2.0.2:
dependencies:
tslib: 2.6.3
tslib: 2.8.1
is-url-superb@4.0.0: {}
@@ -22722,9 +22860,10 @@ snapshots:
jest-worker@27.5.1:
dependencies:
'@types/node': 22.15.32
'@types/node': 22.16.0
merge-stream: 2.0.0
supports-color: 8.1.1
optional: true
jiti@1.21.7: {}
@@ -23065,7 +23204,8 @@ snapshots:
load-tsconfig@0.2.5: {}
loader-runner@4.3.0: {}
loader-runner@4.3.0:
optional: true
local-pkg@0.5.1:
dependencies:
@@ -23191,11 +23331,11 @@ snapshots:
lower-case-first@2.0.2:
dependencies:
tslib: 2.6.3
tslib: 2.8.1
lower-case@2.0.2:
dependencies:
tslib: 2.6.3
tslib: 2.8.1
lowercase-keys@3.0.0: {}
@@ -23450,8 +23590,6 @@ snapshots:
module-details-from-path@1.0.3: {}
mrmime@1.0.1: {}
mrmime@2.0.1: {}
ms@2.0.0: {}
@@ -23776,14 +23914,17 @@ snapshots:
nullthrows@1.1.1: {}
nuxt-custom-elements@2.0.0-beta.18(webpack@5.98.0(@swc/core@1.12.4)(esbuild@0.23.1)):
nuxt-custom-elements@2.0.0-beta.32(webpack@5.99.9(@swc/core@1.12.4)(esbuild@0.23.1)):
dependencies:
change-case: 4.1.2
change-case: 5.4.4
clone: 2.1.2
defu: 6.1.2
html-webpack-plugin: 5.5.3(webpack@5.98.0(@swc/core@1.12.4)(esbuild@0.23.1))
webpack-bundle-analyzer: 4.9.0
defu: 6.1.4
html-webpack-plugin: 5.6.3(webpack@5.99.9(@swc/core@1.12.4)(esbuild@0.23.1))
lodash-es: 4.17.21
vue-web-component-wrapper: 1.6.9
webpack-bundle-analyzer: 4.10.2
transitivePeerDependencies:
- '@rspack/core'
- bufferutil
- utf-8-validate
- webpack
@@ -25551,6 +25692,7 @@ snapshots:
ajv: 8.17.1
ajv-formats: 2.1.1(ajv@8.17.1)
ajv-keywords: 5.1.0(ajv@8.17.1)
optional: true
scslre@0.3.0:
dependencies:
@@ -25796,11 +25938,11 @@ snapshots:
dependencies:
semver: 7.7.2
sirv@1.0.19:
sirv@2.0.4:
dependencies:
'@polka/url': 1.0.0-next.28
mrmime: 1.0.1
totalist: 1.1.0
mrmime: 2.0.1
totalist: 3.0.1
sirv@3.0.1:
dependencies:
@@ -25913,7 +26055,7 @@ snapshots:
sponge-case@1.0.1:
dependencies:
tslib: 2.6.3
tslib: 2.8.1
sprintf-js@1.0.3: {}
@@ -25943,6 +26085,8 @@ snapshots:
stable-hash-x@0.1.1: {}
stable-hash-x@0.2.0: {}
stack-trace@0.0.10: {}
stackback@0.0.2: {}
@@ -26188,7 +26332,7 @@ snapshots:
swap-case@2.0.2:
dependencies:
tslib: 2.6.3
tslib: 2.8.1
symbol-observable@1.2.0: {}
@@ -26271,7 +26415,8 @@ snapshots:
tapable@2.2.1: {}
tapable@2.2.2: {}
tapable@2.2.2:
optional: true
tar-fs@2.1.2:
dependencies:
@@ -26312,17 +26457,18 @@ snapshots:
mkdirp: 3.0.1
yallist: 5.0.0
terser-webpack-plugin@5.3.14(@swc/core@1.12.4)(esbuild@0.23.1)(webpack@5.98.0(@swc/core@1.12.4)(esbuild@0.23.1)):
terser-webpack-plugin@5.3.14(@swc/core@1.12.4)(esbuild@0.23.1)(webpack@5.99.9(@swc/core@1.12.4)(esbuild@0.23.1)):
dependencies:
'@jridgewell/trace-mapping': 0.3.25
'@jridgewell/trace-mapping': 0.3.29
jest-worker: 27.5.1
schema-utils: 4.3.2
serialize-javascript: 6.0.2
terser: 5.43.1
webpack: 5.98.0(@swc/core@1.12.4)(esbuild@0.23.1)
webpack: 5.99.9(@swc/core@1.12.4)(esbuild@0.23.1)
optionalDependencies:
'@swc/core': 1.12.4
esbuild: 0.23.1
optional: true
terser@5.43.1:
dependencies:
@@ -26402,7 +26548,7 @@ snapshots:
title-case@3.0.3:
dependencies:
tslib: 2.6.3
tslib: 2.8.1
tldts-core@6.1.86: {}
@@ -26437,8 +26583,6 @@ snapshots:
toml@3.0.0: {}
totalist@1.1.0: {}
totalist@3.0.1: {}
touch@3.1.1: {}
@@ -26676,7 +26820,7 @@ snapshots:
unctx@2.4.1:
dependencies:
acorn: 8.14.1
acorn: 8.15.0
estree-walker: 3.0.3
magic-string: 0.30.17
unplugin: 2.3.5
@@ -26888,13 +27032,20 @@ snapshots:
escalade: 3.2.0
picocolors: 1.1.1
update-browserslist-db@1.1.3(browserslist@4.25.1):
dependencies:
browserslist: 4.25.1
escalade: 3.2.0
picocolors: 1.1.1
optional: true
upper-case-first@2.0.2:
dependencies:
tslib: 2.6.3
tslib: 2.8.1
upper-case@2.0.2:
dependencies:
tslib: 2.6.3
tslib: 2.8.1
uqr@0.1.2: {}
@@ -27350,6 +27501,10 @@ snapshots:
'@vue/language-core': 2.2.10(typescript@5.8.3)
typescript: 5.8.3
vue-web-component-wrapper@1.6.9: {}
vue-web-component-wrapper@1.7.7: {}
vue@3.5.17(typescript@5.8.3):
dependencies:
'@vue/compiler-dom': 3.5.17
@@ -27396,6 +27551,7 @@ snapshots:
dependencies:
glob-to-regexp: 0.4.1
graceful-fs: 4.2.11
optional: true
wcwidth@1.0.1:
dependencies:
@@ -27407,37 +27563,41 @@ snapshots:
webidl-conversions@7.0.0: {}
webpack-bundle-analyzer@4.9.0:
webpack-bundle-analyzer@4.10.2:
dependencies:
'@discoveryjs/json-ext': 0.5.7
acorn: 8.14.1
acorn: 8.15.0
acorn-walk: 8.3.4
chalk: 4.1.2
commander: 7.2.0
debounce: 1.2.1
escape-string-regexp: 4.0.0
gzip-size: 6.0.0
lodash: 4.17.21
html-escaper: 2.0.2
opener: 1.5.2
sirv: 1.0.19
picocolors: 1.1.1
sirv: 2.0.4
ws: 7.5.10
transitivePeerDependencies:
- bufferutil
- utf-8-validate
webpack-sources@3.3.3: {}
webpack-sources@3.3.3:
optional: true
webpack-virtual-modules@0.6.2: {}
webpack@5.98.0(@swc/core@1.12.4)(esbuild@0.23.1):
webpack@5.99.9(@swc/core@1.12.4)(esbuild@0.23.1):
dependencies:
'@types/eslint-scope': 3.7.7
'@types/estree': 1.0.8
'@types/json-schema': 7.0.15
'@webassemblyjs/ast': 1.14.1
'@webassemblyjs/wasm-edit': 1.14.1
'@webassemblyjs/wasm-parser': 1.14.1
acorn: 8.15.0
browserslist: 4.25.0
browserslist: 4.25.1
chrome-trace-event: 1.0.4
enhanced-resolve: 5.18.1
enhanced-resolve: 5.18.2
es-module-lexer: 1.7.0
eslint-scope: 5.1.1
events: 3.3.0
@@ -27449,13 +27609,14 @@ snapshots:
neo-async: 2.6.2
schema-utils: 4.3.2
tapable: 2.2.2
terser-webpack-plugin: 5.3.14(@swc/core@1.12.4)(esbuild@0.23.1)(webpack@5.98.0(@swc/core@1.12.4)(esbuild@0.23.1))
terser-webpack-plugin: 5.3.14(@swc/core@1.12.4)(esbuild@0.23.1)(webpack@5.99.9(@swc/core@1.12.4)(esbuild@0.23.1))
watchpack: 2.4.4
webpack-sources: 3.3.3
transitivePeerDependencies:
- '@swc/core'
- esbuild
- uglify-js
optional: true
whatwg-encoding@2.0.0:
dependencies:

View File

@@ -78,15 +78,13 @@ const commonLanguageOptions = {
// Define globals separately
const commonGlobals = {
browser: true,
window: true,
document: true,
console: true,
Event: true,
HTMLElement: true,
HTMLInputElement: true,
CustomEvent: true,
es2022: true,
window: 'readonly',
document: 'readonly',
console: 'readonly',
Event: 'readonly',
HTMLElement: 'readonly',
HTMLInputElement: 'readonly',
CustomEvent: 'readonly',
};
export default [

View File

@@ -13,7 +13,7 @@ import { computed } from 'vue';
type SelectValueType = string | number;
type AcceptableValue = SelectValueType | SelectValueType[] | Record<string, unknown> | null;
type AcceptableValue = SelectValueType | SelectValueType[] | Record<string, unknown> | bigint | null;
interface SelectItemInterface {
label: string;
@@ -89,7 +89,13 @@ function getItemValue(item: SelectItemType): SelectValueType | null {
if (isStructuredItem(item)) {
const value = props.valueKey in item ? item[props.valueKey] : item.value;
return typeof value === 'string' || typeof value === 'number' ? value : null;
if (typeof value === 'string' || typeof value === 'number') {
return value;
} else if (typeof value === 'bigint') {
return String(value);
} else {
return null;
}
}
if (isLabelItem(item) || isSeparatorItem(item)) return null;
@@ -174,8 +180,6 @@ function handleUpdateModelValue(value: AcceptableValue) {
</SelectTrigger>
<SelectContent>
<slot name="content-top" />
<SelectGroup v-for="{ groupIndex, items } in groupedOrderedItems" :key="groupIndex">
<template v-for="{ item, index, type } in items" :key="index">
<SelectLabel v-if="type === 'label'">
@@ -196,7 +200,6 @@ function handleUpdateModelValue(value: AcceptableValue) {
</SelectItem>
</template>
</SelectGroup>
<slot name="content-bottom" />
</SelectContent>
</SelectRoot>
</template>

View File

@@ -3,7 +3,7 @@ import { onMounted, ref } from 'vue';
const useTeleport = () => {
const teleportTarget = ref<string | HTMLElement>('#modals');
const determineTeleportTarget = () => {
const determineTeleportTarget = (): HTMLElement | undefined => {
const myModalsComponent =
document.querySelector('unraid-modals') || document.querySelector('uui-modals');
if (!myModalsComponent?.shadowRoot) return;
@@ -12,6 +12,7 @@ const useTeleport = () => {
if (!potentialTarget) return;
teleportTarget.value = potentialTarget as HTMLElement;
return teleportTarget.value;
};
onMounted(() => {

View File

@@ -8,7 +8,6 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import useTeleport from '@/composables/useTeleport';
import type { ControlElement } from '@jsonforms/core';
import { useJsonFormsControl } from '@jsonforms/vue';
import type { RendererProps } from '@jsonforms/vue';
@@ -32,12 +31,6 @@ const options = computed(() => {
const onChange = (value: unknown) => {
handleChange(control.value.path, String(value));
};
// Without this, the select dropdown will not be visible, unless it's already in a teleported context.
const { teleportTarget, determineTeleportTarget } = useTeleport();
const onSelectOpen = () => {
determineTeleportTarget();
};
</script>
<template>
@@ -46,14 +39,13 @@ const onSelectOpen = () => {
:disabled="!control.enabled"
:required="control.required"
@update:model-value="onChange"
@update:open="onSelectOpen"
>
<SelectTrigger>
<SelectValue v-if="selected">{{ selected }}</SelectValue>
<span v-else>{{ control.schema.default ?? 'Select an option' }}</span>
</SelectTrigger>
<SelectContent :to="teleportTarget">
<SelectContent>
<template v-for="option in options" :key="option.value">
<TooltipProvider v-if="option.tooltip" :delay-duration="50">
<Tooltip>
@@ -62,7 +54,7 @@ const onSelectOpen = () => {
<SelectItemText>{{ option.label }}</SelectItemText>
</SelectItem>
</TooltipTrigger>
<TooltipContent :to="teleportTarget" side="right" :side-offset="5">
<TooltipContent side="right" :side-offset="5">
<p class="max-w-xs">{{ option.tooltip }}</p>
</TooltipContent>
</Tooltip>

View File

@@ -46,6 +46,15 @@ vi.mock('~/store/activationCode', () => ({
})),
}));
vi.mock('~/components/Activation/store/activationCodeData', () => ({
useActivationCodeDataStore: () => ({
loading: ref(false),
activationCode: ref(null),
isFreshInstall: ref(false),
partnerInfo: ref(null),
}),
}));
describe('Auth Component', () => {
let serverStore: ReturnType<typeof useServerStore>;

View File

@@ -6,7 +6,6 @@ import { nextTick } from 'vue';
import { setActivePinia } from 'pinia';
import { mount } from '@vue/test-utils';
import { Badge } from '@unraid/ui';
import { createTestingPinia } from '@pinia/testing';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
@@ -26,6 +25,22 @@ vi.mock('@unraid/shared-callbacks', () => ({
useCallback: vi.fn(() => ({ send: vi.fn(), watcher: vi.fn() })),
}));
vi.mock('@vue/apollo-composable', () => ({
useQuery: () => ({
result: { value: {} },
loading: { value: false },
}),
useLazyQuery: () => ({
result: { value: {} },
loading: { value: false },
load: vi.fn(),
refetch: vi.fn(),
onResult: vi.fn(),
onError: vi.fn(),
}),
provideApolloClient: vi.fn(),
}));
vi.mock('~/helpers/urls', async (importOriginal) => {
const actual = await importOriginal<typeof import('~/helpers/urls')>();
const mockReleaseNotesUrl = 'http://mock.release.notes/v';
@@ -66,14 +81,6 @@ vi.mock('vue-i18n', () => ({
}),
}));
vi.mock('@unraid/ui', async (importOriginal) => {
const actual = await importOriginal<typeof import('@unraid/ui')>();
return {
...actual,
Badge: actual.Badge,
};
});
describe('HeaderOsVersion', () => {
let wrapper: VueWrapper<unknown>;
let testingPinia: TestingPinia;
@@ -102,7 +109,6 @@ describe('HeaderOsVersion', () => {
wrapper = mount(HeaderOsVersion, {
global: {
plugins: [testingPinia],
components: { Badge },
},
});
});
@@ -112,16 +118,13 @@ describe('HeaderOsVersion', () => {
vi.restoreAllMocks();
});
it('renders OS version badge with correct link and no update status initially', () => {
const versionBadgeLink = wrapper.find('a[title*="release notes"]');
it('renders OS version link with correct URL and no update status initially', () => {
const versionLink = wrapper.find('a[title*="release notes"]');
expect(versionBadgeLink.exists()).toBe(true);
expect(versionBadgeLink.attributes('href')).toBe(`${testMockReleaseNotesUrl}6.12.0`);
expect(versionLink.exists()).toBe(true);
expect(versionLink.attributes('href')).toBe(`${testMockReleaseNotesUrl}6.12.0`);
const badge = versionBadgeLink.findComponent(Badge);
expect(badge.exists()).toBe(true);
expect(badge.text()).toContain('6.12.0');
expect(versionLink.text()).toContain('6.12.0');
expect(findUpdateStatusComponent()).toBeNull();
});

View File

@@ -1,133 +0,0 @@
/**
* I18nHost Component Test Coverage
*/
import { defineComponent, inject } from 'vue';
import { I18nInjectionKey } from 'vue-i18n';
import { mount } from '@vue/test-utils';
import _en_US from '~/locales/en_US.json';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import type { VueWrapper } from '@vue/test-utils';
import type { Composer, I18n } from 'vue-i18n';
import I18nHost from '~/components/I18nHost.ce.vue';
const en_US: Record<string, string> = _en_US;
vi.mock('~/helpers/i18n-utils', () => ({
createHtmlEntityDecoder: vi.fn(() => (text: string) => text),
}));
const TestConsumerComponent = defineComponent({
template: '<div>{{ i18n?.global.locale.value }}</div>',
setup() {
const i18n = inject(I18nInjectionKey);
return { i18n };
},
});
describe('I18nHost', () => {
let wrapper: VueWrapper<unknown>;
const originalWindowLocaleData = (window as unknown as { LOCALE_DATA?: string }).LOCALE_DATA;
beforeEach(() => {
delete (window as unknown as { LOCALE_DATA?: string }).LOCALE_DATA;
vi.clearAllMocks();
});
afterEach(() => {
wrapper?.unmount();
if (originalWindowLocaleData !== undefined) {
(window as unknown as { LOCALE_DATA?: string }).LOCALE_DATA = originalWindowLocaleData;
} else {
delete (window as unknown as { LOCALE_DATA?: string }).LOCALE_DATA;
}
vi.restoreAllMocks();
});
it('provides i18n instance with default locale when no window data exists', () => {
wrapper = mount(I18nHost, {
slots: {
default: TestConsumerComponent,
},
});
const consumerWrapper = wrapper.findComponent(TestConsumerComponent);
const providedI18n = consumerWrapper.vm.i18n as I18n<{
message: typeof en_US;
}>;
expect(providedI18n).toBeDefined();
expect((providedI18n.global as Composer).locale.value).toBe('en_US');
expect((providedI18n.global as Composer).fallbackLocale.value).toBe('en_US');
const messages = (providedI18n.global as Composer).messages.value as {
en_US?: Record<string, string>;
ja?: Record<string, string>;
};
expect(messages.en_US?.['My Servers']).toBe(en_US['My Servers']);
});
it('parses and provides i18n instance with locale from window.LOCALE_DATA', () => {
const mockJaMessages = { 'test-key': 'テストキー' };
const localeData = { ja: mockJaMessages };
(window as unknown as { LOCALE_DATA?: string }).LOCALE_DATA = encodeURIComponent(
JSON.stringify(localeData)
);
wrapper = mount(I18nHost, {
slots: {
default: TestConsumerComponent,
},
});
const consumerWrapper = wrapper.findComponent(TestConsumerComponent);
const providedI18n = consumerWrapper.vm.i18n as I18n<{
message: typeof en_US;
}>;
const messages = (providedI18n.global as Composer).messages.value as {
en_US?: Record<string, string>;
ja?: Record<string, string>;
};
expect(providedI18n).toBeDefined();
expect((providedI18n.global as Composer).locale.value).toBe('ja');
expect((providedI18n.global as Composer).fallbackLocale.value).toBe('en_US');
expect(messages.ja?.['test-key']).toBe(mockJaMessages['test-key']);
expect(messages.en_US?.['My Servers']).toBe(en_US['My Servers']);
});
it('handles invalid JSON in window.LOCALE_DATA gracefully', () => {
(window as unknown as { LOCALE_DATA?: string }).LOCALE_DATA = 'invalid JSON string{%';
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
wrapper = mount(I18nHost, {
slots: {
default: TestConsumerComponent,
},
});
const consumerWrapper = wrapper.findComponent(TestConsumerComponent);
const providedI18n = consumerWrapper.vm.i18n as I18n<{
message: typeof en_US;
}>;
const messages = (providedI18n.global as Composer).messages.value as {
en_US?: Record<string, string>;
ja?: Record<string, string>;
};
expect(providedI18n).toBeDefined();
expect((providedI18n.global as Composer).locale.value).toBe('en_US');
expect((providedI18n.global as Composer).fallbackLocale.value).toBe('en_US');
expect(errorSpy).toHaveBeenCalledOnce();
expect(errorSpy).toHaveBeenCalledWith('[I18nHost] error parsing messages', expect.any(Error));
expect(messages.en_US?.['My Servers']).toBe(en_US['My Servers']);
errorSpy.mockRestore();
});
});

View File

@@ -28,6 +28,22 @@ vi.mock('@unraid/shared-callbacks', () => ({
})),
}));
vi.mock('@vue/apollo-composable', () => ({
useQuery: () => ({
result: { value: {} },
loading: { value: false },
}),
useLazyQuery: () => ({
result: { value: {} },
loading: { value: false },
load: vi.fn(),
refetch: vi.fn(),
onResult: vi.fn(),
onError: vi.fn(),
}),
provideApolloClient: vi.fn(),
}));
vi.mock('@unraid/ui', async (importOriginal) => {
const actual = await importOriginal<typeof import('@unraid/ui')>();

View File

@@ -8,6 +8,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import type { VueWrapper } from '@vue/test-utils';
import type { Server, ServerconnectPluginInstalled, ServerState } from '~/types/server';
import type { Pinia } from 'pinia';
import type { MaybeRef } from '@vueuse/core';
import UserProfile from '~/components/UserProfile.ce.vue';
import { useServerStore } from '~/store/server';

View File

@@ -1,8 +1,7 @@
<script lang="ts" setup>
const { registerEntry } = useCustomElements();
onBeforeMount(() => {
registerEntry('UnraidComponents');
});
import { NuxtLayout, NuxtPage } from '#components';
</script>
<template>

View File

@@ -14,6 +14,9 @@ import { useActivationCodeModalStore } from '~/components/Activation/store/activ
import { usePurchaseStore } from '~/store/purchase';
import { useThemeStore } from '~/store/theme';
import Modal from '~/components/Modal.vue';
import ActivationSteps from './ActivationSteps.vue';
export interface Props {
t: ComposerTranslation;
}

View File

@@ -1,5 +1,5 @@
import { computed } from 'vue';
import { createPinia, defineStore, setActivePinia } from 'pinia';
import { defineStore } from 'pinia';
import { useQuery } from '@vue/apollo-composable';
import {
@@ -8,7 +8,8 @@ import {
} from '~/components/Activation/graphql/activationCode.query';
import { RegistrationState } from '~/composables/gql/graphql';
setActivePinia(createPinia()); /** required in web component context */
// Uses the shared global Pinia instance
import '~/store/globalPinia';
export const useActivationCodeDataStore = defineStore('activationCodeData', () => {
const { result: activationCodeResult, loading: activationCodeLoading } = useQuery(

View File

@@ -1,5 +1,5 @@
import { computed, onMounted, onUnmounted } from 'vue';
import { createPinia, defineStore, setActivePinia, storeToRefs } from 'pinia';
import { defineStore, storeToRefs } from 'pinia';
import { useSessionStorage } from '@vueuse/core';
import { ACTIVATION_CODE_MODAL_HIDDEN_STORAGE_KEY } from '~/consts';
@@ -7,7 +7,8 @@ import { ACTIVATION_CODE_MODAL_HIDDEN_STORAGE_KEY } from '~/consts';
import { useActivationCodeDataStore } from '~/components/Activation/store/activationCodeData';
import { useCallbackActionsStore } from '~/store/callbackActions';
setActivePinia(createPinia()); /** required in web component context */
// Uses the shared global Pinia instance
import '~/store/globalPinia';
export const useActivationCodeModalStore = defineStore('activationCodeModal', () => {
const isHidden = useSessionStorage<boolean | null>(ACTIVATION_CODE_MODAL_HIDDEN_STORAGE_KEY, null);

View File

@@ -2,7 +2,9 @@
import ApiKeyManager from '~/components/ApiKey/ApiKeyManager.vue';
</script>
<template>
<ApiKeyManager />
<div>
<ApiKeyManager />
</div>
</template>
<style lang="postcss">
/* Import unraid-ui globals first */

View File

@@ -2,6 +2,8 @@
import { storeToRefs } from 'pinia';
import { useServerStore } from '~/store/server';
import BrandMark from '~/components/Brand/Mark.vue';
export interface Props {
gradientStart?: string;
gradientStop?: string;

View File

@@ -1,4 +1,5 @@
<script setup lang="ts">
import { onBeforeMount } from 'vue';
import { useCallbackActionsStore } from '~/store/callbackActions';
const callbackStore = useCallbackActionsStore();
@@ -9,7 +10,9 @@ onBeforeMount(() => {
</script>
<template>
<slot />
<div>
<slot />
</div>
</template>
<style lang="postcss">

View File

@@ -3,6 +3,9 @@
// const { t } = useI18n();
import { ref, computed, watch } from 'vue';
import { storeToRefs } from 'pinia';
import { watchDebounced } from '@vueuse/core';
import { useMutation, useQuery } from '@vue/apollo-composable';
import { BrandButton, jsonFormsRenderers, Label } from '@unraid/ui';
@@ -104,12 +107,12 @@ const onChange = ({ data }: { data: Record<string, unknown> }) => {
>
<template v-if="connectPluginInstalled">
<Label>Account Status:</Label>
<div v-html="'<unraid-i18n-host><unraid-auth></unraid-auth></unraid-i18n-host>'"></div>
<div v-html="'<unraid-auth></unraid-auth>'"></div>
</template>
<Label>Download Unraid API Logs:</Label>
<div
v-html="
'<unraid-i18n-host><unraid-download-api-logs></unraid-download-api-logs></unraid-i18n-host>'
'<unraid-download-api-logs></unraid-download-api-logs>'
"
></div>
</div>

View File

@@ -6,6 +6,8 @@ import {
Button,
} from '@unraid/ui';
import DummyServerSwitcher from '~/components/DummyServerSwitcher.vue';
import { CogIcon } from '@heroicons/vue/24/solid';
</script>

View File

@@ -22,6 +22,9 @@ import { storeToRefs } from 'pinia';
import { PageContainer } from '@unraid/ui';
import { useServerStore } from '~/store/server';
import UpdateOsStatus from '~/components/UpdateOs/Status.vue';
import UpdateOsDowngrade from '~/components/UpdateOs/Downgrade.vue';
import UpdateOsThirdPartyDrivers from '~/components/UpdateOs/ThirdPartyDrivers.vue';
const { t } = useI18n();
@@ -55,22 +58,24 @@ onBeforeMount(() => {
</script>
<template>
<PageContainer>
<UpdateOsStatus
:title="t('Downgrade Unraid OS')"
:subtitle="subtitle"
:downgrade-not-available="restoreVersion === '' && rebootType === ''"
:show-external-downgrade="showExternalDowngrade"
:t="t"
/>
<UpdateOsDowngrade
v-if="restoreVersion && rebootType === ''"
:release-date="restoreReleaseDate"
:version="restoreVersion"
:t="t"
/>
<UpdateOsThirdPartyDrivers v-if="rebootType === 'thirdPartyDriversDownloading'" :t="t" />
</PageContainer>
<div>
<PageContainer>
<UpdateOsStatus
:title="t('Downgrade Unraid OS')"
:subtitle="subtitle"
:downgrade-not-available="restoreVersion === '' && rebootType === ''"
:show-external-downgrade="showExternalDowngrade"
:t="t"
/>
<UpdateOsDowngrade
v-if="restoreVersion && rebootType === ''"
:release-date="restoreReleaseDate"
:version="restoreVersion"
:t="t"
/>
<UpdateOsThirdPartyDrivers v-if="rebootType === 'thirdPartyDriversDownloading'" :t="t" />
</PageContainer>
</div>
</template>
<style lang="postcss">

View File

@@ -78,7 +78,7 @@ const updateOsStatus = computed(() => {
</script>
<template>
<div class="flex flex-col">
<div class="flex flex-col gap-y-2">
<a
:href="unraidLogoHeaderLink.href"
:title="unraidLogoHeaderLink.title"
@@ -88,28 +88,21 @@ const updateOsStatus = computed(() => {
>
<img
:src="'/webGui/images/UN-logotype-gradient.svg'"
class="w-[160px] h-auto max-h-[30px] ml-[10px] mt-[25px] mb-[8px] object-contain"
class="w-[160px] h-auto max-h-[30px] object-contain"
alt="Unraid Logo"
/>
</a>
<div class="flex flex-row justify-start gap-x-4px">
<div class="flex flex-wrap justify-start gap-2">
<a
class="group leading-none"
class="text-xs xs:text-sm flex flex-row items-center gap-x-1 font-semibold text-header-text-secondary hover:text-orange-dark focus:text-orange-dark hover:underline focus:underline leading-none"
:title="t('View release notes')"
:href="getReleaseNotesUrl(osVersion).toString()"
target="_blank"
rel="noopener"
>
<Badge
variant="custom"
:icon="InformationCircleIcon"
icon-styles="text-header-text-secondary"
size="sm"
class="text-header-text-secondary group-hover:text-orange-dark group-focus:text-orange-dark group-hover:underline group-focus:underline"
>
{{ osVersion }}
</Badge>
<InformationCircleIcon class="fill-current w-3 h-3 xs:w-4 xs:h-4 flex-shrink-0" />
{{ osVersion }}
</a>
<component
:is="updateOsStatus.href ? 'a' : 'button'"

View File

@@ -1,54 +0,0 @@
<script lang="ts" setup>
import en_US from '~/locales/en_US.json';
import { provide } from 'vue';
import { createI18n, I18nInjectionKey } from 'vue-i18n';
import { createHtmlEntityDecoder } from '~/helpers/i18n-utils';
// import ja from '~/locales/ja.json';
const defaultLocale = 'en_US'; // ja, en_US
let parsedLocale = '';
let parsedMessages = {};
let nonDefaultLocale = false;
/**
* In myservers2.php, we have a script tag that sets window.LOCALE_DATA to a stringified JSON object.
* Unfortunately, this was the only way I could get the data from PHP to vue-i18n :(
* I tried using i18n.setLocaleMessage() but it didn't work no matter what I tried.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const windowLocaleData = (window as any).LOCALE_DATA || null;
if (windowLocaleData) {
try {
parsedMessages = JSON.parse(decodeURIComponent(windowLocaleData));
parsedLocale = Object.keys(parsedMessages)[0];
nonDefaultLocale = parsedLocale !== defaultLocale;
} catch (error) {
console.error('[I18nHost] error parsing messages', error);
}
}
const i18n = createI18n<false>({
legacy: false, // must set to `false`
locale: nonDefaultLocale ? parsedLocale : defaultLocale,
fallbackLocale: defaultLocale,
messages: {
en_US,
// ja,
...(nonDefaultLocale ? parsedMessages : {}),
},
/** safely decodes html-encoded symbols like &amp; and &apos; */
postTranslation: createHtmlEntityDecoder(),
});
provide(I18nInjectionKey, i18n);
</script>
<template>
<slot />
</template>
<style>
/* unraid-i18n-host {
font-size: 16px;
} */
</style>

View File

@@ -6,17 +6,19 @@ import {
Input,
Label,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
Switch,
useTeleport,
} from '@unraid/ui';
import { GET_LOG_FILES } from './log.query';
import SingleLogViewer from './SingleLogViewer.vue';
// Types
interface LogFile {
path: string;
name: string;
size: number;
}
// Component state
const selectedLogFile = ref<string>('');
const lineCount = ref<number>(100);
@@ -48,6 +50,14 @@ const logFiles = computed(() => {
return logFilesResult.value?.logFiles || [];
});
// Transform log files for the Select component
const logFileOptions = computed(() => {
return logFiles.value.map((file: LogFile) => ({
value: file.path,
label: `${file.name} (${formatFileSize(file.size)})`
}));
});
// Format file size for display
const formatFileSize = (bytes: number): string => {
if (bytes === 0) return '0 Bytes';
@@ -92,13 +102,6 @@ watch(selectedLogFile, (newValue) => {
highlightLanguage.value = autoDetectLanguage(newValue);
}
});
// Without this, the select dropdown will not be visible, unless it's already in a teleported context.
const { teleportTarget, determineTeleportTarget } = useTeleport();
const onSelectOpen = () => {
determineTeleportTarget();
};
</script>
<template>
@@ -111,16 +114,12 @@ const onSelectOpen = () => {
<div class="flex flex-wrap gap-4 items-end">
<div class="flex-1 min-w-[200px]">
<Label for="log-file-select">Log File</Label>
<Select v-model="selectedLogFile" @update:open="onSelectOpen">
<SelectTrigger class="w-full">
<SelectValue placeholder="Select a log file" />
</SelectTrigger>
<SelectContent :to="teleportTarget">
<SelectItem v-for="file in logFiles" :key="file.path" :value="file.path">
{{ file.name }} ({{ formatFileSize(file.size) }})
</SelectItem>
</SelectContent>
</Select>
<Select
v-model="selectedLogFile"
:items="logFileOptions"
placeholder="Select a log file"
class="w-full"
/>
</div>
<div>
@@ -137,16 +136,12 @@ const onSelectOpen = () => {
<div>
<Label for="highlight-language">Syntax</Label>
<Select v-model="highlightLanguage" @update:open="onSelectOpen">
<SelectTrigger id="highlight-language" class="w-full">
<SelectValue placeholder="Select language" />
</SelectTrigger>
<SelectContent :to="teleportTarget">
<SelectItem v-for="lang in highlightLanguages" :key="lang.value" :value="lang.value">
{{ lang.label }}
</SelectItem>
</SelectContent>
</Select>
<Select
v-model="highlightLanguage"
:items="highlightLanguages"
placeholder="Select language"
class="w-full"
/>
</div>
<div class="flex flex-col gap-2">

View File

@@ -7,6 +7,11 @@ import { useTrialStore } from '~/store/trial';
import { useUpdateOsStore } from '~/store/updateOs';
import { useApiKeyStore } from '~/store/apiKey';
import ApiKeyCreate from '~/components/ApiKey/ApiKeyCreate.vue';
import UpcCallbackFeedback from '~/components/UserProfile/CallbackFeedback.vue';
import UpcTrial from '~/components/UserProfile/Trial.vue';
import UpdateOsCheckUpdateResponseModal from '~/components/UpdateOs/CheckUpdateResponseModal.vue';
import UpdateOsChangelogModal from '~/components/UpdateOs/ChangelogModal.vue';
import ActivationModal from '~/components/Activation/ActivationModal.vue';
const { t } = useI18n();

View File

@@ -1,4 +1,5 @@
<script setup lang="ts">
import { computed, type Component } from 'vue';
import { BellIcon, ExclamationTriangleIcon, ShieldExclamationIcon } from '@heroicons/vue/24/solid';
import { cn } from '@unraid/ui';

View File

@@ -1,4 +1,6 @@
<script setup lang="ts">
import { computed, reactive, type Component } from 'vue';
import { computedAsync } from '@vueuse/core';
import { Markdown } from '@/helpers/markdown';
import {
ArchiveBoxIcon,

View File

@@ -1,4 +1,5 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import { useQuery } from '@vue/apollo-composable';
import { vInfiniteScroll } from '@vueuse/components';
@@ -12,6 +13,8 @@ import { useFragment } from '~/composables/gql/fragment-masking';
import { useUnraidApiStore } from '~/store/unraidApi';
import { getNotifications, NOTIFICATION_FRAGMENT } from './graphql/notification.query';
import NotificationsItem from './Item.vue';
/**
* Page size is the max amount of items fetched from the api in a single request.
*/

View File

@@ -1,4 +1,5 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import { useMutation, useQuery, useSubscription } from '@vue/apollo-composable';
import {
@@ -29,11 +30,12 @@ import {
notificationAddedSubscription,
notificationOverviewSubscription,
} from './graphql/notification.subscription';
import NotificationsIndicator from './Indicator.vue';
import NotificationsList from './List.vue';
const { mutate: archiveAll, loading: loadingArchiveAll } = useMutation(archiveAllNotifications);
const { mutate: deleteArchives, loading: loadingDeleteAll } = useMutation(deleteArchivedNotifications);
const { mutate: recalculateOverview } = useMutation(resetOverview);
const { determineTeleportTarget } = useTeleport();
const importance = ref<Importance | undefined>(undefined);
const filterItems = [
@@ -117,13 +119,12 @@ const readArchivedCount = computed(() => {
});
const prepareToViewNotifications = () => {
determineTeleportTarget();
void recalculateOverview();
};
</script>
<template>
<Sheet :modal="false">
<Sheet>
<SheetTrigger @click="prepareToViewNotifications">
<span class="sr-only">Notifications</span>
<NotificationsIndicator :overview="overview" :seen="haveSeenNotifications" />
@@ -187,7 +188,7 @@ const prepareToViewNotifications = () => {
importance = strVal === 'all' || !strVal ? undefined : (strVal as Importance);
}
"
/>
></Select>
</div>
<TabsContent value="unread" class="flex-col flex-1 min-h-0">

View File

@@ -30,6 +30,7 @@ import RegistrationKeyLinkedStatus from '~/components/Registration/KeyLinkedStat
import RegistrationReplaceCheck from '~/components/Registration/ReplaceCheck.vue';
import RegistrationUpdateExpirationAction from '~/components/Registration/UpdateExpirationAction.vue';
import UserProfileUptimeExpire from '~/components/UserProfile/UptimeExpire.vue';
import RegistrationItem from '~/components/Registration/Item.vue';
import useDateTimeHelper from '~/composables/dateTime';
import { useReplaceRenewStore } from '~/store/replaceRenew';
import { useServerStore } from '~/store/server';
@@ -269,57 +270,59 @@ const items = computed((): RegistrationItemProps[] => {
</script>
<template>
<PageContainer class="max-w-800px">
<CardWrapper :increased-padding="true">
<div class="flex flex-col gap-20px sm:gap-24px">
<header class="flex flex-col gap-y-16px">
<h3
class="text-20px md:text-24px font-semibold leading-normal flex flex-row items-center gap-8px"
:class="serverErrors.length ? 'text-unraid-red' : 'text-green-500'"
>
<component :is="headingIcon" class="w-24px h-24px" />
<span>
{{ heading }}
</span>
</h3>
<div
v-if="subheading"
class="prose text-16px leading-relaxed whitespace-normal opacity-75"
v-html="subheading"
/>
<span v-if="authAction" class="grow-0">
<BrandButton
:disabled="authAction?.disabled"
:icon="authAction.icon"
:text="t(authAction.text)"
:title="authAction.title ? t(authAction.title) : undefined"
@click="authAction.click?.()"
<div>
<PageContainer class="max-w-800px">
<CardWrapper :increased-padding="true">
<div class="flex flex-col gap-20px sm:gap-24px">
<header class="flex flex-col gap-y-16px">
<h3
class="text-20px md:text-24px font-semibold leading-normal flex flex-row items-center gap-8px"
:class="serverErrors.length ? 'text-unraid-red' : 'text-green-500'"
>
<component :is="headingIcon" class="w-24px h-24px" />
<span>
{{ heading }}
</span>
</h3>
<div
v-if="subheading"
class="prose text-16px leading-relaxed whitespace-normal opacity-75"
v-html="subheading"
/>
</span>
</header>
<dl>
<RegistrationItem
v-for="item in items"
:key="item.label"
:component="item?.component"
:component-props="item?.componentProps"
:error="item.error ?? false"
:warning="item.warning ?? false"
:label="item.label"
:text="item.text"
>
<template v-if="item.component" #right>
<component
:is="item.component"
v-bind="item.componentProps"
:class="[item.componentOpacity && !item.error ? 'opacity-75' : '']"
<span v-if="authAction" class="grow-0">
<BrandButton
:disabled="authAction?.disabled"
:icon="authAction.icon"
:text="t(authAction.text)"
:title="authAction.title ? t(authAction.title) : undefined"
@click="authAction.click?.()"
/>
</template>
</RegistrationItem>
</dl>
</div>
</CardWrapper>
</PageContainer>
</span>
</header>
<dl>
<RegistrationItem
v-for="item in items"
:key="item.label"
:component="item?.component"
:component-props="item?.componentProps"
:error="item.error ?? false"
:warning="item.warning ?? false"
:label="item.label"
:text="item.text"
>
<template v-if="item.component" #right>
<component
:is="item.component"
v-bind="item.componentProps"
:class="[item.componentOpacity && !item.error ? 'opacity-75' : '']"
/>
</template>
</RegistrationItem>
</dl>
</div>
</CardWrapper>
</PageContainer>
</div>
</template>
<style lang="postcss">

View File

@@ -1,4 +1,5 @@
<script setup lang="ts">
import { computed } from 'vue';
import { ShieldExclamationIcon } from '@heroicons/vue/24/solid';
import { storeToRefs } from 'pinia';

View File

@@ -1,4 +1,5 @@
<script setup lang="ts">
import { computed } from 'vue';
import { storeToRefs } from 'pinia';
import type { ComposerTranslation } from 'vue-i18n';

View File

@@ -1,4 +1,5 @@
<script setup lang="ts">
import { computed } from 'vue';
import { storeToRefs } from 'pinia';
import { ArrowPathIcon, ArrowTopRightOnSquareIcon } from '@heroicons/vue/24/solid';
@@ -10,6 +11,7 @@ import type { ComposerTranslation } from 'vue-i18n';
import useDateTimeHelper from '~/composables/dateTime';
import { useReplaceRenewStore } from '~/store/replaceRenew';
import { useServerStore } from '~/store/server';
import RegistrationUpdateExpiration from './UpdateExpiration.vue';
export interface Props {
t: ComposerTranslation;

View File

@@ -134,19 +134,21 @@ const navigateToExternalSSOUrl = () => {
</script>
<template>
<template v-if="isSsoEnabled">
<div class="w-full flex flex-col gap-1 my-1">
<p v-if="currentState === 'idle' || currentState === 'error'" class="text-center">or</p>
<p v-if="currentState === 'error'" class="text-red-500 text-center">{{ error }}</p>
<BrandButton
:disabled="currentState === 'loading'"
variant="outline"
class="rounded-none uppercase tracking-widest"
@click="navigateToExternalSSOUrl"
>{{ buttonText }}</BrandButton
>
</div>
</template>
<div>
<template v-if="isSsoEnabled">
<div class="w-full flex flex-col gap-1 my-1">
<p v-if="currentState === 'idle' || currentState === 'error'" class="text-center">or</p>
<p v-if="currentState === 'error'" class="text-red-500 text-center">{{ error }}</p>
<BrandButton
:disabled="currentState === 'loading'"
variant="outline"
class="rounded-none uppercase tracking-widest"
@click="navigateToExternalSSOUrl"
>{{ buttonText }}</BrandButton
>
</div>
</template>
</div>
</template>
<style lang="postcss">

View File

@@ -73,21 +73,23 @@ const handleThemeChange = (event: Event) => {
</script>
<template>
<select
v-if="enableThemeSwitcher"
:disabled="submitting"
:value="props.current"
class="text-xs relative float-left mr-2 text-white bg-black"
@change="handleThemeChange"
>
<option
v-for="theme in computedThemes"
:key="theme"
:value="theme"
<div>
<select
v-if="enableThemeSwitcher"
:disabled="submitting"
:value="props.current"
class="text-xs relative float-left mr-2 text-white bg-black"
@change="handleThemeChange"
>
{{ theme }}
</option>
</select>
<option
v-for="theme in computedThemes"
:key="theme"
:value="theme"
>
{{ theme }}
</option>
</select>
</div>
</template>
<style lang="postcss">

View File

@@ -24,6 +24,8 @@ import { WEBGUI_TOOLS_UPDATE } from '~/helpers/urls';
import { useAccountStore } from '~/store/account';
import { useServerStore } from '~/store/server';
import UpdateOsStatus from '~/components/UpdateOs/Status.vue';
import UpdateOsThirdPartyDrivers from '~/components/UpdateOs/ThirdPartyDrivers.vue';
const { t } = useI18n();
@@ -59,17 +61,17 @@ onBeforeMount(() => {
</script>
<template>
<PageContainer>
<BrandLoading v-if="showLoader" class="mx-auto my-12 max-w-160px" />
<UpdateOsStatus
v-else
:show-update-check="true"
:title="t('Update Unraid OS')"
:subtitle="subtitle"
:t="t"
/>
<UpdateOsThirdPartyDrivers v-if="rebootType === 'thirdPartyDriversDownloading'" :t="t" />
</PageContainer>
<PageContainer>
<BrandLoading v-if="showLoader" class="mx-auto my-12 max-w-160px" />
<UpdateOsStatus
v-else
:show-update-check="true"
:title="t('Update Unraid OS')"
:subtitle="subtitle"
:t="t"
/>
<UpdateOsThirdPartyDrivers v-if="rebootType === 'thirdPartyDriversDownloading'" :t="t" />
</PageContainer>
</template>
<style lang="postcss">

View File

@@ -18,6 +18,7 @@ import RawChangelogRenderer from '~/components/UpdateOs/RawChangelogRenderer.vue
import { usePurchaseStore } from '~/store/purchase';
import { useThemeStore } from '~/store/theme';
import { useUpdateOsStore } from '~/store/updateOs';
import Modal from '~/components/Modal.vue';
export interface Props {
open?: boolean;

View File

@@ -1,4 +1,5 @@
<script lang="ts" setup>
import { computed, onBeforeMount, ref, watch } from 'vue';
import { storeToRefs } from 'pinia';
import {
@@ -20,6 +21,8 @@ import { useAccountStore } from '~/store/account';
import { usePurchaseStore } from '~/store/purchase';
import { useServerStore } from '~/store/server';
import { useUpdateOsStore } from '~/store/updateOs';
import Modal from '~/components/Modal.vue';
import UpdateOsIgnoredRelease from './IgnoredRelease.vue';
export interface Props {
open?: boolean;

View File

@@ -1,4 +1,6 @@
<script setup lang="ts">
import { computed } from 'vue';
import { storeToRefs } from 'pinia';
import { XMarkIcon } from '@heroicons/vue/24/solid';
import { BrandButton } from '@unraid/ui';

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup>
import { h } from 'vue';
import { computed, h } from 'vue';
import { storeToRefs } from 'pinia';
import {

View File

@@ -4,7 +4,7 @@
* @todo require keyfile to update
* @todo require valid guid / server state to update
*/
import { ref, watchEffect } from 'vue';
import { computed, ref, watchEffect } from 'vue';
import { storeToRefs } from 'pinia';
import {

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup>
import { ref, watchEffect } from 'vue';
import { computed, ref, watchEffect } from 'vue';
import { storeToRefs } from 'pinia';
import { ArrowTopRightOnSquareIcon, ExclamationTriangleIcon, EyeIcon } from '@heroicons/vue/24/solid';
@@ -13,6 +13,7 @@ import useDateTimeHelper from '~/composables/dateTime';
import { useServerStore } from '~/store/server';
import { useUpdateOsStore } from '~/store/updateOs';
import { useUpdateOsActionsStore } from '~/store/updateOsActions';
import RegistrationUpdateExpiration from '~/components/Registration/UpdateExpiration.vue';
const props = defineProps<{
t: ComposerTranslation;

View File

@@ -13,6 +13,13 @@ import { useCallbackActionsStore } from '~/store/callbackActions';
import { useServerStore } from '~/store/server';
import { useThemeStore } from '~/store/theme';
// Auto-imported components - now manually imported
import UpcUptimeExpire from '~/components/UserProfile/UptimeExpire.vue';
import UpcServerState from '~/components/UserProfile/ServerState.vue';
import NotificationsSidebar from '~/components/Notifications/Sidebar.vue';
import UpcDropdownContent from '~/components/UserProfile/DropdownContent.vue';
import UpcDropdownTrigger from '~/components/UserProfile/DropdownTrigger.vue';
export interface Props {
server?: Server | string;
}
@@ -93,7 +100,7 @@ onMounted(() => {
<template>
<div
id="UserProfile"
class="text-foreground relative z-20 flex flex-col h-full gap-y-4px pt-4px pr-16px pl-40px"
class="text-foreground relative z-20 flex flex-col h-full gap-y-1"
>
<div
v-if="bannerGradient"
@@ -102,11 +109,11 @@ onMounted(() => {
/>
<div
class="text-xs text-header-text-secondary text-right font-semibold leading-normal relative z-10 flex flex-col items-end justify-end gap-x-4px xs:flex-row xs:items-baseline xs:gap-x-12px"
class="text-xs text-header-text-secondary text-right font-semibold leading-normal relative z-10 flex flex-wrap items-baseline justify-end gap-x-1 xs:flex-row xs:gap-x-4"
>
<UpcUptimeExpire :t="t" />
<UpcUptimeExpire :as="'span'" :t="t" class="text-12px" />
<span class="hidden xs:block">&bull;</span>
<UpcServerState :t="t" />
<UpcServerState :t="t" class="text-12px" />
</div>
<div class="relative z-10 flex flex-row items-center justify-end gap-x-16px h-full">

View File

@@ -1,6 +1,7 @@
<script lang="ts" setup>
// @todo ensure key installs and updateOs can be handled at the same time
// @todo with multiple actions of key install and update after successful key install, rather than showing default success message, show a message to have them confirm the update
import { computed, ref } from 'vue';
import { storeToRefs } from 'pinia';
import { useClipboard } from '@vueuse/core';
@@ -22,6 +23,10 @@ import { useCallbackActionsStore } from '~/store/callbackActions';
import { useInstallKeyStore } from '~/store/installKey';
import { useServerStore } from '~/store/server';
import { useUpdateOsActionsStore } from '~/store/updateOsActions';
import Modal from '~/components/Modal.vue';
import UpcCallbackFeedbackStatus from './CallbackFeedbackStatus.vue';
import UpcUptimeExpire from './UptimeExpire.vue';
import RegistrationUpdateExpiration from '~/components/Registration/UpdateExpiration.vue';
export interface Props {
open?: boolean;

View File

@@ -1,4 +1,5 @@
<script setup lang="ts">
import { computed, h } from 'vue';
import { storeToRefs } from 'pinia';
import { CheckCircleIcon, ExclamationTriangleIcon, UserCircleIcon } from '@heroicons/vue/24/solid';
@@ -8,6 +9,7 @@ import type { ComposerTranslation } from 'vue-i18n';
import { useServerStore } from '~/store/server';
import { useUnraidApiStore } from '~/store/unraidApi';
import UpcDropdownItem from './DropdownItem.vue';
const props = defineProps<{ t: ComposerTranslation }>();

View File

@@ -1,4 +1,5 @@
<script setup lang="ts">
import { computed } from 'vue';
import { storeToRefs } from 'pinia';
import {
@@ -27,6 +28,12 @@ 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();
@@ -200,7 +207,7 @@ const unraidConnectWelcome = computed(() => {
gradient-stop="currentcolor"
class="text-foreground w-[120px]"
/>
<UpcBeta />
<Beta />
</h2>
<template v-if="unraidConnectWelcome">
<h3 class="text-16px font-semibold mt-2">
@@ -211,29 +218,29 @@ const unraidConnectWelcome = computed(() => {
</p>
</template>
</header>
<ul class="list-reset flex flex-col gap-y-4px p-0">
<UpcDropdownConnectStatus v-if="showConnectStatus" :t="t" />
<UpcDropdownError v-if="showErrors" :t="t" />
<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">
<UpcKeyline />
</li>
<li v-if="!registered && connectPluginInstalled">
<UpcDropdownItem :item="signInAction[0]" :t="t" />
</li>
<template v-if="filteredKeyActions">
<li v-for="action in filteredKeyActions" :key="action.name">
<UpcDropdownItem :item="action" :t="t" />
<li v-if="showKeyline" class="my-8px">
<Keyline />
</li>
</template>
<template v-if="links.length">
<li v-for="(link, index) in links" :key="`link_${index}`">
<UpcDropdownItem :item="link" :t="t" />
<li v-if="!registered && connectPluginInstalled">
<DropdownItem :item="signInAction[0]" :t="t" />
</li>
</template>
<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>

View File

@@ -5,6 +5,7 @@ import type { ComposerTranslation } from 'vue-i18n';
import { useErrorsStore } from '~/store/errors';
import type { UserProfileLink } from '~/types/userProfile';
import UpcDropdownItem from './DropdownItem.vue';
defineProps<{ t: ComposerTranslation; }>();

View File

@@ -1,4 +1,5 @@
<script setup lang="ts">
import { computed } from 'vue';
import { ArrowTopRightOnSquareIcon } from '@heroicons/vue/24/solid';
import type { ComposerTranslation } from 'vue-i18n';

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup>
import { h } from 'vue';
import { computed, h } from 'vue';
import { storeToRefs } from 'pinia';
import { BrandButton, BrandLoading } from '@unraid/ui';
@@ -8,6 +8,8 @@ import type { ComposerTranslation } from 'vue-i18n';
import { useServerStore } from '~/store/server';
import { useUnraidApiStore } from '~/store/unraidApi';
import UpcUptimeExpire from './UptimeExpire.vue';
import KeyActions from '~/components/KeyActions.vue';
defineProps<{ t: ComposerTranslation }>();

View File

@@ -1,4 +1,5 @@
<script setup lang="ts">
import { computed } from 'vue';
import { storeToRefs } from 'pinia';
import {
@@ -11,6 +12,7 @@ import {
import type { ComposerTranslation } from 'vue-i18n';
import BrandAvatar from '~/components/Brand/Avatar.vue';
import { useErrorsStore } from '~/store/errors';
import { useServerStore } from '~/store/server';
import { useUpdateOsStore } from '~/store/updateOs';
@@ -18,7 +20,7 @@ import { useUpdateOsStore } from '~/store/updateOs';
const props = defineProps<{ t: ComposerTranslation }>();
const { errors } = storeToRefs(useErrorsStore());
const { rebootType, state, stateData } = storeToRefs(useServerStore());
const { connectPluginInstalled, rebootType, state, stateData } = storeToRefs(useServerStore());
const { available: osUpdateAvailable } = storeToRefs(useUpdateOsStore());
const showErrorIcon = computed(() => errors.value.length || stateData.value.error);
@@ -77,6 +79,6 @@ const title = computed((): string => {
<Bars3Icon class="w-20px" />
<BrandAvatar />
<BrandAvatar v-if="connectPluginInstalled" />
</button>
</template>

View File

@@ -1,9 +1,11 @@
<script setup lang="ts">
import { computed } from 'vue';
import { storeToRefs } from 'pinia';
import type { ComposerTranslation } from 'vue-i18n';
import { useServerStore } from '~/store/server';
import type { ServerStateDataAction } from '~/types/server';
import UpcServerStateBuy from './ServerStateBuy.vue';
defineProps<{ t: ComposerTranslation; }>();

View File

@@ -1,4 +1,5 @@
<script lang="ts" setup>
import { computed } from 'vue';
import { storeToRefs } from 'pinia';
import { BrandLoading } from '@unraid/ui';
@@ -6,6 +7,7 @@ import { BrandLoading } from '@unraid/ui';
import type { ComposerTranslation } from 'vue-i18n';
import { useTrialStore } from '~/store/trial';
import Modal from '~/components/Modal.vue';
export interface Props {
open?: boolean;

View File

@@ -1,4 +1,5 @@
<script setup lang="ts">
import { computed } from 'vue';
import { storeToRefs } from 'pinia';
import type { ComposerTranslation } from 'vue-i18n';
@@ -8,12 +9,14 @@ import { useServerStore } from '~/store/server';
export interface Props {
forExpire?: boolean;
shortText?: boolean;
as?: 'p' | 'span';
t: ComposerTranslation;
}
const props = withDefaults(defineProps<Props>(), {
forExpire: false,
shortText: false,
as: 'p',
});
const serverStore = useServerStore();
@@ -59,7 +62,7 @@ const output = computed(() => {
</script>
<template>
<p :title="output.title">
<component :is="as" :title="output.title">
{{ output.text }}
</p>
</component>
</template>

View File

@@ -55,27 +55,29 @@ watchEffect(async () => {
</script>
<template>
<span v-if="loading" class="italic">{{ t('Checking WAN IPs') }}</span>
<template v-else>
<span v-if="computedError" class="text-unraid-red font-semibold">{{ computedError }}</span>
<div>
<span v-if="loading" class="italic">{{ t('Checking WAN IPs') }}</span>
<template v-else>
<span v-if="isRemoteAccess || (phpWanIp === wanIp && !isRemoteAccess)">{{
t('Remark: your WAN IPv4 is {0}', [wanIp])
}}</span>
<span v-else class="inline-block w-1/2 whitespace-normal">
{{
t("Remark: Unraid's WAN IPv4 {0} does not match your client's WAN IPv4 {1}.", [
phpWanIp,
wanIp,
])
}}
{{
t('This may indicate a complex network that will not work with this Remote Access solution.')
}}
{{ t('Ignore this message if you are currently connected via Remote Access or VPN.') }}
</span>
<span v-if="computedError" class="text-unraid-red font-semibold">{{ computedError }}</span>
<template v-else>
<span v-if="isRemoteAccess || (phpWanIp === wanIp && !isRemoteAccess)">{{
t('Remark: your WAN IPv4 is {0}', [wanIp])
}}</span>
<span v-else class="inline-block w-1/2 whitespace-normal">
{{
t("Remark: Unraid's WAN IPv4 {0} does not match your client's WAN IPv4 {1}.", [
phpWanIp,
wanIp,
])
}}
{{
t('This may indicate a complex network that will not work with this Remote Access solution.')
}}
{{ t('Ignore this message if you are currently connected via Remote Access or VPN.') }}
</span>
</template>
</template>
</template>
</div>
</template>
<style lang="postcss">

View File

@@ -0,0 +1,49 @@
import { createI18n } from 'vue-i18n';
import type { App } from 'vue';
import { DefaultApolloClient } from '@vue/apollo-composable';
import en_US from '~/locales/en_US.json';
import { createHtmlEntityDecoder } from '~/helpers/i18n-utils';
import { globalPinia } from '~/store/globalPinia';
import { client } from '~/helpers/create-apollo-client';
export default function (Vue: App) {
// Create and configure i18n
const defaultLocale = 'en_US';
let parsedLocale = '';
let parsedMessages = {};
let nonDefaultLocale = false;
// Check for window locale data
if (typeof window !== 'undefined') {
const windowLocaleData = (window as unknown as { LOCALE_DATA?: string }).LOCALE_DATA || null;
if (windowLocaleData) {
try {
parsedMessages = JSON.parse(decodeURIComponent(windowLocaleData));
parsedLocale = Object.keys(parsedMessages)[0];
nonDefaultLocale = parsedLocale !== defaultLocale;
} catch (error) {
console.error('[WebComponentPlugins] error parsing messages', error);
}
}
}
const i18n = createI18n({
legacy: false,
locale: nonDefaultLocale ? parsedLocale : defaultLocale,
fallbackLocale: defaultLocale,
messages: {
en_US,
...(nonDefaultLocale ? parsedMessages : {}),
},
postTranslation: createHtmlEntityDecoder(),
});
Vue.use(i18n);
// Use the shared Pinia instance
Vue.use(globalPinia);
// Provide Apollo client for all web components
Vue.provide(DefaultApolloClient, client);
}

View File

@@ -1,5 +1,6 @@
import { computed, onUnmounted, ref, watchEffect } from 'vue';
import { useQuery } from '@vue/apollo-composable';
import { useStorage } from '@vueuse/core';
import { useStorage, watchOnce } from '@vueuse/core';
import {
getNotifications,
NOTIFICATION_FRAGMENT,

View File

@@ -1,3 +1,4 @@
import { ref, computed, onBeforeMount, onBeforeUnmount } from 'vue';
import dayjs, { extend } from 'dayjs';
import localizedFormat from 'dayjs/plugin/localizedFormat';
import type { ComposerTranslation } from 'vue-i18n';
@@ -206,7 +207,7 @@ const useDateTimeHelper = (
outputDateTimeReadableDiff.value = buildStringFromValues(dateDiff((providedDateTime ?? Date.now()).toString(), diffCountUp ?? false));
};
let interval: string | number | NodeJS.Timeout | undefined;
let interval: string | number | ReturnType<typeof setInterval> | undefined;
onBeforeMount(() => {
if (providedDateTime) {
runDiff();

View File

@@ -1,26 +0,0 @@
import { ref, onMounted } from "vue";
const useTeleport = () => {
const teleportTarget = ref<string | HTMLElement | Element>("#modals");
const determineTeleportTarget = () => {
const myModalsComponent = document.querySelector("unraid-modals");
if (!myModalsComponent?.shadowRoot) return;
const potentialTarget = myModalsComponent.shadowRoot.querySelector("#modals");
if (!potentialTarget) return;
teleportTarget.value = potentialTarget;
};
onMounted(() => {
determineTeleportTarget();
});
return {
teleportTarget,
determineTeleportTarget,
};
};
export default useTeleport;

View File

@@ -6,10 +6,57 @@ import withNuxt from './.nuxt/eslint.config.mjs';
export default withNuxt(
{
ignores: ['./coverage/**'],
languageOptions: {
globals: {
// Node.js globals
NodeJS: 'readonly',
// Browser APIs
EventListenerOrEventListenerObject: 'readonly',
HTMLCollectionOf: 'readonly',
// webGUI-specific globals
openPlugin: 'readonly',
openBox: 'readonly',
openChanges: 'readonly',
FeedbackButton: 'readonly',
flashBackup: 'readonly',
confirmDowngrade: 'readonly',
downloadDiagnostics: 'readonly',
// Nuxt globals
defineNuxtConfig: 'readonly',
},
},
rules: {
'vue/multi-word-component-names': 'off',
'vue/no-v-html': 'off',
'vue/no-undef-components': [
'error',
{
ignorePatterns: [
// Custom Elements (components ending with Ce)
'.*Ce$',
// Web Components (components starting with unraid-)
'^unraid-.*',
// Client-only component
'^client-only$',
// Other common components
'^ClientOnly$',
'^BrandLogo$',
'^ColorSwitcherCe$',
'^DummyServerSwitcher$',
'^HeaderOsVersionCe$',
'^ConnectSettingsCe$',
],
},
],
'eol-last': ['error', 'always'],
// TypeScript rules for unused variables and undefined variables
'@typescript-eslint/no-unused-vars': ['error', {
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_'
}],
'no-undef': 'error',
},
},
eslintPrettier

View File

@@ -1,6 +1,5 @@
import { InMemoryCache } from '@apollo/client/core/index.js';
import { InMemoryCache, type InMemoryCacheConfig } from '@apollo/client/core';
import type { InMemoryCacheConfig } from '@apollo/client/core/index.js';
import type { NotificationOverview } from '~/composables/gql/graphql';
import {
@@ -11,6 +10,11 @@ import { NotificationType } from '~/composables/gql/graphql';
import { NotificationTypename } from '~/composables/gql/typename';
import { mergeAndDedup } from './merge';
// Utility function to check if a value is defined (not null and not undefined)
const isDefined = <T>(value: T | null | undefined): value is T => {
return value !== null && value !== undefined;
};
/**------------------------------------------------------------------------
* ! Understanding Cache Type Policies
*

View File

@@ -3,7 +3,6 @@ import { onError } from '@apollo/client/link/error/index.js';
import { RetryLink } from '@apollo/client/link/retry/index.js';
import { GraphQLWsLink } from '@apollo/client/link/subscriptions/index.js';
import { getMainDefinition } from '@apollo/client/utilities/index.js';
import { provideApolloClient } from '@vue/apollo-composable';
import { createClient } from 'graphql-ws';
import { createApolloCache } from './apollo-cache';
import { WEBGUI_GRAPHQL } from './urls';
@@ -94,6 +93,4 @@ const additiveLink = from([errorLink, retryLink, disableQueryLink, splitLinks]);
export const client = new ApolloClient({
link: additiveLink,
cache: createApolloCache(),
});
provideApolloClient(client);
});

View File

@@ -1,5 +1,5 @@
declare global {
// eslint-disable-next-line no-var
var csrf_token: string;
}

View File

@@ -1,6 +1,6 @@
<template>
<div class="text-black bg-white dark:text-white dark:bg-black">
<client-only>
<ClientOnly>
<div class="flex flex-row items-center justify-center gap-6 p-6 bg-white dark:bg-zinc-800">
<template v-for="route in routes" :key="route.path">
<NuxtLink
@@ -14,11 +14,15 @@
<ModalsCe />
</div>
<slot />
</client-only>
</ClientOnly>
</div>
</template>
<script setup>
import { computed, watch } from 'vue';
import { useRouter } from 'vue-router';
import { storeToRefs } from 'pinia';
import { ClientOnly, NuxtLink } from '#components';
import ModalsCe from '~/components/Modals.ce.vue';
import { useThemeStore } from '~/store/theme';

View File

@@ -1,6 +1,9 @@
import path from 'path';
import removeConsole from 'vite-plugin-remove-console';
import type { UserConfig } from 'vite';
/**
* Used to avoid redeclaring variables in the webgui codebase.
* @see alt solution https://github.com/terser/terser/issues/1001, https://github.com/terser/terser/pull/1038
@@ -53,6 +56,11 @@ export default defineNuxtConfig({
'@nuxt/eslint',
],
// Disable auto-imports
imports: {
autoImport: false,
},
// Properly handle ES modules in testing and build environments
build: {
transpile: [/node_modules\/.*\.mjs$/],
@@ -60,13 +68,8 @@ export default defineNuxtConfig({
ignore: ['/webGui/images'],
components: [
{ path: '~/components/Brand', prefix: 'Brand' },
{ path: '~/components/ConnectSettings', prefix: 'ConnectSettings' },
{ path: '~/components/UserProfile', prefix: 'Upc' },
{ path: '~/components/UpdateOs', prefix: 'UpdateOs' },
'~/components',
],
// Disable component auto-imports
components: false,
vite: {
plugins: [
@@ -88,77 +91,156 @@ export default defineNuxtConfig({
reserved: terserReservations(charsToReserve),
toplevel: true,
},
// keep_fnames: true,
},
},
},
customElements: {
analyzer: process.env.NODE_ENV !== 'test',
entries: [
// @ts-expect-error The nuxt-custom-elements module types don't perfectly match our configuration object structure.
// The custom elements configuration requires specific properties and methods that may not align with the
// module's TypeScript definitions, particularly around the viteExtend function and tag configuration format.
{
name: 'UnraidComponents',
viteExtend(config: UserConfig) {
// Configure terser options for custom elements build
if (!config.build) config.build = {};
config.build.minify = 'terser';
config.build.terserOptions = {
mangle: {
reserved: terserReservations(charsToReserve),
toplevel: true,
},
};
// Add a custom plugin to wrap the bundle and preserve jQuery
if (!config.plugins) config.plugins = [];
config.plugins.push({
name: 'jquery-isolation',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
generateBundle(options: any, bundle: any) {
// Find the main JS file
const jsFile = Object.keys(bundle).find(key => key.endsWith('.js'));
if (jsFile && bundle[jsFile] && 'code' in bundle[jsFile]) {
const originalCode = bundle[jsFile].code;
// Wrap the entire bundle to preserve and restore jQuery
bundle[jsFile].code = `
(function() {
// Preserve the original jQuery $ if it exists
var originalJQuery = (typeof window !== 'undefined' && typeof window.$ !== 'undefined') ? window.$ : undefined;
// Temporarily clear $ to avoid conflicts
if (typeof window !== 'undefined' && typeof window.$ !== 'undefined') {
window.$ = undefined;
}
// Execute the web component code
${originalCode}
// Restore jQuery $ if it was originally defined
if (originalJQuery !== undefined && typeof window !== 'undefined') {
window.$ = originalJQuery;
}
})();
`;
}
}
});
return config;
},
tags: [
{
name: 'UnraidI18nHost',
path: '@/components/I18nHost.ce',
},
{
async: false,
name: 'UnraidAuth',
path: '@/components/Auth.ce',
appContext: '@/components/Wrapper/web-component-plugins',
},
{
async: false,
name: 'UnraidConnectSettings',
path: '@/components/ConnectSettings/ConnectSettings.ce',
appContext: '@/components/Wrapper/web-component-plugins',
},
{
async: false,
name: 'UnraidDownloadApiLogs',
path: '@/components/DownloadApiLogs.ce',
appContext: '@/components/Wrapper/web-component-plugins',
},
{
async: false,
name: 'UnraidHeaderOsVersion',
path: '@/components/HeaderOsVersion.ce',
appContext: '@/components/Wrapper/web-component-plugins',
},
{
async: false,
name: 'UnraidModals',
path: '@/components/Modals.ce',
appContext: '@/components/Wrapper/web-component-plugins',
},
{
async: false,
name: 'UnraidUserProfile',
path: '@/components/UserProfile.ce',
appContext: '@/components/Wrapper/web-component-plugins',
},
{
async: false,
name: 'UnraidUpdateOs',
path: '@/components/UpdateOs.ce',
appContext: '@/components/Wrapper/web-component-plugins',
},
{
async: false,
name: 'UnraidDowngradeOs',
path: '@/components/DowngradeOs.ce',
appContext: '@/components/Wrapper/web-component-plugins',
},
{
async: false,
name: 'UnraidRegistration',
path: '@/components/Registration.ce',
appContext: '@/components/Wrapper/web-component-plugins',
},
{
async: false,
name: 'UnraidWanIpCheck',
path: '@/components/WanIpCheck.ce',
appContext: '@/components/Wrapper/web-component-plugins',
},
{
async: false,
name: 'UnraidWelcomeModal',
path: '@/components/Activation/WelcomeModal.ce',
appContext: '@/components/Wrapper/web-component-plugins',
},
{
async: false,
name: 'UnraidSsoButton',
path: '@/components/SsoButton.ce',
appContext: '@/components/Wrapper/web-component-plugins',
},
{
async: false,
name: 'UnraidLogViewer',
path: '@/components/Logs/LogViewer.ce',
appContext: '@/components/Wrapper/web-component-plugins',
},
{
async: false,
name: 'UnraidThemeSwitcher',
path: '@/components/ThemeSwitcher.ce',
appContext: '@/components/Wrapper/web-component-plugins',
},
{
async: false,
name: 'UnraidApiKeyManager',
path: '@/components/ApiKeyPage.ce',
appContext: '@/components/Wrapper/web-component-plugins',
},
],
},

View File

@@ -55,6 +55,7 @@
"@types/eslint-config-prettier": "^6.11.3",
"@types/node": "^22",
"@types/semver": "^7.5.8",
"@typescript-eslint/eslint-plugin": "^8.34.1",
"@unraid/tailwind-rem-to-rem": "^1.1.0",
"@vitejs/plugin-vue": "^5.0.0",
"@vitest/coverage-v8": "^3.1.1",
@@ -64,10 +65,12 @@
"@vueuse/nuxt": "^13.0.0",
"eslint": "^9.21.0",
"eslint-config-prettier": "^10.0.0",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import": "^2.31.0",
"happy-dom": "^18.0.0",
"lodash-es": "^4.17.21",
"nuxt": "^3.14.1592",
"nuxt-custom-elements": "2.0.0-beta.18",
"nuxt-custom-elements": "2.0.0-beta.32",
"prettier": "3.5.3",
"prettier-plugin-tailwindcss": "^0.6.9",
"shadcn-nuxt": "^2.0.0",
@@ -75,6 +78,7 @@
"tailwindcss-animate": "^1.0.7",
"terser": "^5.37.0",
"typescript": "^5.7.3",
"vite": "^6.3.5",
"vite-plugin-remove-console": "^2.2.0",
"vite-plugin-vue-tracer": "^0.1.3",
"vitest": "^3.1.1",
@@ -118,6 +122,7 @@
"semver": "^7.6.3",
"tailwind-merge": "^2.5.5",
"vue-i18n": "^11.0.0",
"vue-web-component-wrapper": "^1.7.7",
"vuetify": "^3.7.14",
"wretch": "^2.11.0"
},

View File

@@ -1,7 +1,10 @@
<script setup lang="ts">
import { onBeforeMount } from 'vue';
import { useI18n } from 'vue-i18n';
import { useUpdateOsStore } from '~/store/updateOs';
import { storeToRefs } from 'pinia';
import UpdateOsChangelogModal from '~/components/UpdateOs/ChangelogModal.vue';
import ColorSwitcherCe from '~/components/ColorSwitcher.ce.vue';
const updateOsStore = useUpdateOsStore();
const { changelogModalVisible } = storeToRefs(updateOsStore);

View File

@@ -1,15 +1,7 @@
<script setup>
import { onMounted } from 'vue';
import RCloneConfig from '~/components/RClone/RCloneConfig.vue';
import RCloneOverview from '~/components/RClone/RCloneOverview.vue';
import { useDummyServerStore } from '~/_data/serverState';
const { registerEntry } = useCustomElements();
useDummyServerStore();
onBeforeMount(() => {
registerEntry('UnraidComponents');
});
onMounted(() => {
document.cookie = 'unraid_session_cookie=mockusersession';

View File

@@ -1,4 +1,7 @@
<script lang="ts" setup>
import { ref, onMounted, watch } from 'vue';
import { storeToRefs } from 'pinia';
import { useHead } from '#imports';
import { ExclamationTriangleIcon } from '@heroicons/vue/24/solid';
import { BrandButton, Toaster } from '@unraid/ui';
import { useDummyServerStore } from '~/_data/serverState';
@@ -11,14 +14,18 @@ import SsoButtonCe from '~/components/SsoButton.ce.vue';
import { useThemeStore } from '~/store/theme';
import ModalsCe from '~/components/Modals.ce.vue';
import ConnectSettingsCe from '~/components/ConnectSettings/ConnectSettings.ce.vue';
import DummyServerSwitcher from '~/components/DummyServerSwitcher.vue';
import ColorSwitcherCe from '~/components/ColorSwitcher.ce.vue';
import HeaderOsVersionCe from '~/components/HeaderOsVersion.ce.vue';
import UserProfileCe from '~/components/UserProfile.ce.vue';
import UpdateOsCe from '~/components/UpdateOs.ce.vue';
import DowngradeOsCe from '~/components/DowngradeOs.ce.vue';
import RegistrationCe from '~/components/Registration.ce.vue';
import WelcomeModalCe from '~/components/Activation/WelcomeModal.ce.vue';
const serverStore = useDummyServerStore();
const { serverState } = storeToRefs(serverStore);
const { registerEntry } = useCustomElements();
const { theme } = storeToRefs(useThemeStore());
onBeforeMount(() => {
registerEntry('UnraidComponents');
});
useHead({
meta: [{ name: 'viewport', content: 'width=1300' }],

View File

@@ -1,4 +1,6 @@
<script setup lang="ts">
import { onMounted } from 'vue';
const parseRedirectTarget = (target: string | null) => {
if (target && target !== '/') {
// parse target and ensure it is a bare path with no query parameters

View File

@@ -1,18 +1,19 @@
<script lang="ts" setup>
import { storeToRefs } from 'pinia';
import { useDummyServerStore } from '~/_data/serverState';
import { Toaster } from '@unraid/ui';
import BrandLogo from '~/components/Brand/Logo.vue';
import HeaderOsVersionCe from '~/components/HeaderOsVersion.ce.vue';
import ConnectSettingsCe from '~/components/ConnectSettings/ConnectSettings.ce.vue';
const serverStore = useDummyServerStore();
const { serverState } = storeToRefs(serverStore);
const { registerEntry } = useCustomElements();
onBeforeMount(() => {
registerEntry('UnraidComponents');
});
</script>
<template>
<client-only>
<unraid-i18n-host
<div
class="flex flex-col gap-6 p-6 mx-auto text-black bg-white dark:text-white dark:bg-black"
>
<h2 class="text-xl font-semibold font-mono">Web Components</h2>
@@ -60,7 +61,7 @@ onBeforeMount(() => {
<hr class="border-black dark:border-white" />
<h3 class="text-lg font-semibold font-mono">ApiKeyManagerCe</h3>
<unraid-api-key-manager />
</unraid-i18n-host>
</div>
<Toaster rich-colors close-button />
</client-only>
</template>

View File

@@ -1,4 +1,5 @@
<script setup lang="ts">
import { storeToRefs } from 'pinia';
import { useActivationCodeModalStore } from '~/components/Activation/store/activationCodeModal';
import { useActivationCodeDataStore } from '~/components/Activation/store/activationCodeData';
import { useCallbackActionsStore } from '~/store/callbackActions';

7
web/plugins/apollo.ts Normal file
View File

@@ -0,0 +1,7 @@
import { defineNuxtPlugin } from '#imports';
import { DefaultApolloClient } from '@vue/apollo-composable';
import { client } from '~/helpers/create-apollo-client';
export default defineNuxtPlugin(({ vueApp }) => {
vueApp.provide(DefaultApolloClient, client);
});

View File

@@ -1,4 +1,5 @@
import { createI18n } from 'vue-i18n';
import { defineNuxtPlugin } from '#imports';
import en_US from '@/locales/en_US.json';
import { createHtmlEntityDecoder } from '~/helpers/i18n-utils';

View File

@@ -1,5 +1,5 @@
import { computed, ref, watchEffect } from 'vue';
import { createPinia, defineStore, setActivePinia } from 'pinia';
import { defineStore } from 'pinia';
import { useMutation } from '@vue/apollo-composable';
import { logErrorMessages } from '@vue/apollo-util';
@@ -15,11 +15,11 @@ import { useUnraidApiStore } from '~/store/unraidApi';
import { CONNECT_SIGN_IN, CONNECT_SIGN_OUT } from './account.fragment';
/**
* Uses the shared global Pinia instance from ~/store/globalPinia.ts
* @see https://stackoverflow.com/questions/73476371/using-pinia-with-vue-js-web-components
* @see https://github.com/vuejs/pinia/discussions/1085
*/
setActivePinia(createPinia());
import '~/store/globalPinia';
export interface ConnectSignInMutationPayload {
apiKey: string;

View File

@@ -1,9 +1,9 @@
import { ref } from 'vue';
import { createPinia, defineStore, setActivePinia } from 'pinia';
import { defineStore } from 'pinia';
import type { ApiKeyFragment, ApiKeyWithKeyFragment } from '~/composables/gql/graphql';
setActivePinia(createPinia());
import '~/store/globalPinia';
export const useApiKeyStore = defineStore('apiKey', () => {
const modalVisible = ref(false);

View File

@@ -1,5 +1,5 @@
import { computed, ref, watch, watchEffect } from 'vue';
import { createPinia, defineStore, setActivePinia } from 'pinia';
import { defineStore } from 'pinia';
import { useCallback } from '@unraid/shared-callbacks';
@@ -19,9 +19,9 @@ import { useServerStore } from '~/store/server';
import { useUpdateOsStore } from '~/store/updateOs';
import { useUpdateOsActionsStore } from '~/store/updateOsActions';
type CallbackStatus = 'closing' | 'error' | 'loading' | 'ready' | 'success';
import '~/store/globalPinia';
setActivePinia(createPinia());
type CallbackStatus = 'closing' | 'error' | 'loading' | 'ready' | 'success';
export const useCallbackActionsStore = defineStore('callbackActions', () => {
const { send, watcher: providedWatcher } = useCallback({

View File

@@ -1,12 +1,13 @@
import { ref } from 'vue';
import { createPinia, defineStore, setActivePinia } from 'pinia';
import { defineStore } from 'pinia';
import { useToggle } from '@vueuse/core';
/**
* Uses the shared global Pinia instance from ~/store/globalPinia.ts
* @see https://stackoverflow.com/questions/73476371/using-pinia-with-vue-js-web-components
* @see https://github.com/vuejs/pinia/discussions/1085
*/
setActivePinia(createPinia());
import '~/store/globalPinia';
export const useDropdownStore = defineStore('dropdown', () => {
const dropdownVisible = ref<boolean>(false);

View File

@@ -1,5 +1,5 @@
import { ref } from 'vue';
import { createPinia, defineStore, setActivePinia } from 'pinia';
import { defineStore } from 'pinia';
import { OBJ_TO_STR } from '~/helpers/functions';
@@ -7,10 +7,11 @@ import type { BrandButtonProps } from '@unraid/ui';
import type { Server } from '~/types/server';
/**
* Uses the shared global Pinia instance from ~/store/globalPinia.ts
* @see https://stackoverflow.com/questions/73476371/using-pinia-with-vue-js-web-components
* @see https://github.com/vuejs/pinia/discussions/1085
*/
setActivePinia(createPinia());
import '~/store/globalPinia';
export type ErrorType =
| 'account'

7
web/store/globalPinia.ts Normal file
View File

@@ -0,0 +1,7 @@
import { createPinia, setActivePinia } from 'pinia';
// Create a single shared Pinia instance for all web components
export const globalPinia = createPinia();
// Set it as the active pinia instance
setActivePinia(globalPinia);

View File

@@ -1,5 +1,5 @@
import { computed, ref } from 'vue';
import { createPinia, defineStore, setActivePinia } from 'pinia';
import { defineStore } from 'pinia';
import type { ExternalKeyActions } from '@unraid/shared-callbacks';
@@ -7,10 +7,11 @@ import { WebguiInstallKey } from '~/composables/services/webgui';
import { useErrorsStore } from '~/store/errors';
/**
* Uses the shared global Pinia instance from ~/store/globalPinia.ts
* @see https://stackoverflow.com/questions/73476371/using-pinia-with-vue-js-web-components
* @see https://github.com/vuejs/pinia/discussions/1085
*/
setActivePinia(createPinia());
import '~/store/globalPinia';
export const useInstallKeyStore = defineStore('installKey', () => {
const errorsStore = useErrorsStore();

View File

@@ -1,11 +1,12 @@
import { createPinia, defineStore, setActivePinia } from 'pinia';
import { defineStore } from 'pinia';
import { useToggle } from '@vueuse/core';
/**
* Uses the shared global Pinia instance from ~/store/globalPinia.ts
* @see https://stackoverflow.com/questions/73476371/using-pinia-with-vue-js-web-components
* @see https://github.com/vuejs/pinia/discussions/1085
*/
setActivePinia(createPinia());
import '~/store/globalPinia';
export const useModalStore = defineStore('modal', () => {
const [modalVisible, modalToggle] = useToggle(true);

Some files were not shown because too many files have changed in this diff Show More