mirror of
https://github.com/unraid/api.git
synced 2026-02-04 15:09:11 -06:00
chore: nunjucks template engine for test pages (#1783)
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -123,3 +123,6 @@ api/dev/Unraid.net/myservers.cfg
|
||||
# local Mise settings
|
||||
.mise.toml
|
||||
|
||||
# Compiled test pages (generated from Nunjucks templates)
|
||||
web/public/test-pages/*.html
|
||||
|
||||
|
||||
40
pnpm-lock.yaml
generated
40
pnpm-lock.yaml
generated
@@ -1221,6 +1221,9 @@ importers:
|
||||
'@types/node':
|
||||
specifier: 22.18.0
|
||||
version: 22.18.0
|
||||
'@types/nunjucks':
|
||||
specifier: ^3.2.6
|
||||
version: 3.2.6
|
||||
'@types/semver':
|
||||
specifier: 7.7.0
|
||||
version: 7.7.0
|
||||
@@ -1284,6 +1287,9 @@ importers:
|
||||
lodash-es:
|
||||
specifier: 4.17.21
|
||||
version: 4.17.21
|
||||
nunjucks:
|
||||
specifier: 3.2.4
|
||||
version: 3.2.4(chokidar@3.6.0)
|
||||
prettier:
|
||||
specifier: 3.6.2
|
||||
version: 3.6.2
|
||||
@@ -4912,6 +4918,9 @@ packages:
|
||||
'@types/normalize-package-data@2.4.4':
|
||||
resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==}
|
||||
|
||||
'@types/nunjucks@3.2.6':
|
||||
resolution: {integrity: sha512-pHiGtf83na1nCzliuAdq8GowYiXvH5l931xZ0YEHaLMNFgynpEqx+IPStlu7UaDkehfvl01e4x/9Tpwhy7Ue3w==}
|
||||
|
||||
'@types/parse-path@7.1.0':
|
||||
resolution: {integrity: sha512-EULJ8LApcVEPbrfND0cRQqutIOdiIgJ1Mgrhpy755r14xMohPTEpkV/k28SJvuOs9bHRFW8x+KeDAEPiGQPB9Q==}
|
||||
deprecated: This is a stub types definition. parse-path provides its own type definitions, so you do not need this installed.
|
||||
@@ -5662,6 +5671,9 @@ packages:
|
||||
resolution: {integrity: sha512-FNoYzHawTMk/6KMQoEG5O4PuioX19UbwdQKF44yw0nLfOypfQdjtfZzo/UIJWAJ23sNIFbD1Ug9lbaDGMwbqQA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
a-sync-waterfall@1.0.1:
|
||||
resolution: {integrity: sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==}
|
||||
|
||||
abbrev@3.0.0:
|
||||
resolution: {integrity: sha512-+/kfrslGQ7TNV2ecmQwMJj/B65g5KVq1/L3SGVZ3tCYGqlzFuFCGBZJtMP99wH3NpEUyAjn0zPdPUg0D+DwrOA==}
|
||||
engines: {node: ^18.17.0 || >=20.5.0}
|
||||
@@ -6430,6 +6442,10 @@ packages:
|
||||
commander@2.20.3:
|
||||
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
|
||||
|
||||
commander@5.1.0:
|
||||
resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
commander@6.2.1:
|
||||
resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -9674,6 +9690,16 @@ packages:
|
||||
nullthrows@1.1.1:
|
||||
resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==}
|
||||
|
||||
nunjucks@3.2.4:
|
||||
resolution: {integrity: sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==}
|
||||
engines: {node: '>= 6.9.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
chokidar: ^3.3.0
|
||||
peerDependenciesMeta:
|
||||
chokidar:
|
||||
optional: true
|
||||
|
||||
nuxt@4.1.1:
|
||||
resolution: {integrity: sha512-xLDbWgz3ggAfUjcbmTzmLLPWOEB61thnjnqyasZlYyh/Ty2EDT1qvOiM9HT+9ycBxElI2DmyYewY8WOPRxWMiQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
@@ -16869,6 +16895,8 @@ snapshots:
|
||||
|
||||
'@types/normalize-package-data@2.4.4': {}
|
||||
|
||||
'@types/nunjucks@3.2.6': {}
|
||||
|
||||
'@types/parse-path@7.1.0':
|
||||
dependencies:
|
||||
parse-path: 7.1.0
|
||||
@@ -17763,6 +17791,8 @@ snapshots:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
a-sync-waterfall@1.0.1: {}
|
||||
|
||||
abbrev@3.0.0: {}
|
||||
|
||||
abind@1.0.5: {}
|
||||
@@ -18606,6 +18636,8 @@ snapshots:
|
||||
|
||||
commander@2.20.3: {}
|
||||
|
||||
commander@5.1.0: {}
|
||||
|
||||
commander@6.2.1: {}
|
||||
|
||||
commander@9.5.0:
|
||||
@@ -22114,6 +22146,14 @@ snapshots:
|
||||
|
||||
nullthrows@1.1.1: {}
|
||||
|
||||
nunjucks@3.2.4(chokidar@3.6.0):
|
||||
dependencies:
|
||||
a-sync-waterfall: 1.0.1
|
||||
asap: 2.0.6
|
||||
commander: 5.1.0
|
||||
optionalDependencies:
|
||||
chokidar: 3.6.0
|
||||
|
||||
nuxt@4.1.1(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.18.0)(@vue/compiler-sfc@3.5.20)(db0@0.3.2)(eslint@9.34.0(jiti@2.5.1))(ioredis@5.7.0)(lightningcss@1.30.1)(magicast@0.3.5)(meow@13.2.0)(optionator@0.9.4)(rollup@4.50.1)(stylus@0.57.0)(terser@5.43.1)(tsx@4.20.5)(typescript@5.9.2)(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))(vue-tsc@3.0.6(typescript@5.9.2))(xml2js@0.6.2)(yaml@2.8.1):
|
||||
dependencies:
|
||||
'@nuxt/cli': 3.28.0(magicast@0.3.5)
|
||||
|
||||
45
unraid-ui/src/forms/composables/useJsonFormsTranslation.ts
Normal file
45
unraid-ui/src/forms/composables/useJsonFormsTranslation.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { getTranslator, type JsonFormsState, type JsonFormsSubStates } from '@jsonforms/core';
|
||||
import { inject } from 'vue';
|
||||
|
||||
type TranslationContext = Record<string, unknown> | undefined;
|
||||
|
||||
/**
|
||||
* Exposes helpers that translate JsonForms i18n keys by reusing the
|
||||
* translator registered on the nearest <JsonForms> provider.
|
||||
*/
|
||||
export function useJsonFormsTranslation() {
|
||||
const jsonforms = inject<JsonFormsSubStates | undefined>('jsonforms', undefined);
|
||||
|
||||
const translateKey = (key?: string, defaultMessage?: string, context?: TranslationContext) => {
|
||||
if (!key) return defaultMessage;
|
||||
|
||||
if (!jsonforms) {
|
||||
return defaultMessage;
|
||||
}
|
||||
|
||||
const translator = getTranslator()({ jsonforms } as JsonFormsState);
|
||||
const translated = translator?.(key, defaultMessage, context);
|
||||
|
||||
if (typeof translated === 'string' && translated === key) {
|
||||
return defaultMessage;
|
||||
}
|
||||
|
||||
return translated ?? defaultMessage;
|
||||
};
|
||||
|
||||
const translateWithPrefix = (
|
||||
prefix?: string,
|
||||
suffix?: string,
|
||||
defaultMessage?: string,
|
||||
context?: TranslationContext
|
||||
) => {
|
||||
if (!prefix) return defaultMessage;
|
||||
const key = suffix ? `${prefix}.${suffix}` : prefix;
|
||||
return translateKey(key, defaultMessage, context);
|
||||
};
|
||||
|
||||
return {
|
||||
translateKey,
|
||||
translateWithPrefix,
|
||||
};
|
||||
}
|
||||
@@ -35,30 +35,30 @@ type LocaleMessages = typeof enUS;
|
||||
|
||||
const localeMessages: Record<string, LocaleMessages> = {
|
||||
en_US: enUS,
|
||||
ar,
|
||||
bn,
|
||||
ca,
|
||||
cs,
|
||||
da,
|
||||
de,
|
||||
es,
|
||||
fr,
|
||||
hi,
|
||||
hr,
|
||||
hu,
|
||||
it,
|
||||
ja,
|
||||
ko,
|
||||
lv,
|
||||
nl,
|
||||
no,
|
||||
pl,
|
||||
pt,
|
||||
ro,
|
||||
ru,
|
||||
sv,
|
||||
uk,
|
||||
zh,
|
||||
ar: ar as LocaleMessages,
|
||||
bn: bn as LocaleMessages,
|
||||
ca: ca as LocaleMessages,
|
||||
cs: cs as LocaleMessages,
|
||||
da: da as LocaleMessages,
|
||||
de: de as LocaleMessages,
|
||||
es: es as LocaleMessages,
|
||||
fr: fr as LocaleMessages,
|
||||
hi: hi as LocaleMessages,
|
||||
hr: hr as LocaleMessages,
|
||||
hu: hu as LocaleMessages,
|
||||
it: it as LocaleMessages,
|
||||
ja: ja as LocaleMessages,
|
||||
ko: ko as LocaleMessages,
|
||||
lv: lv as LocaleMessages,
|
||||
nl: nl as LocaleMessages,
|
||||
no: no as LocaleMessages,
|
||||
pl: pl as LocaleMessages,
|
||||
pt: pt as LocaleMessages,
|
||||
ro: ro as LocaleMessages,
|
||||
ru: ru as LocaleMessages,
|
||||
sv: sv as LocaleMessages,
|
||||
uk: uk as LocaleMessages,
|
||||
zh: zh as LocaleMessages,
|
||||
};
|
||||
|
||||
type AnyObject = Record<string, unknown>;
|
||||
|
||||
1
web/components.d.ts
vendored
1
web/components.d.ts
vendored
@@ -44,6 +44,7 @@ declare module 'vue' {
|
||||
DeveloperAuthorizationLink: typeof import('./src/components/ApiKey/DeveloperAuthorizationLink.vue')['default']
|
||||
'DevModalTest.standalone': typeof import('./src/components/DevModalTest.standalone.vue')['default']
|
||||
DevSettings: typeof import('./src/components/DevSettings.vue')['default']
|
||||
'DevThemeSwitcher.standalone': typeof import('./src/components/DevThemeSwitcher.standalone.vue')['default']
|
||||
Downgrade: typeof import('./src/components/UpdateOs/Downgrade.vue')['default']
|
||||
'DowngradeOs.standalone': typeof import('./src/components/DowngradeOs.standalone.vue')['default']
|
||||
'DownloadApiLogs.standalone': typeof import('./src/components/DownloadApiLogs.standalone.vue')['default']
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"license": "GPL-2.0-or-later",
|
||||
"scripts": {
|
||||
"// Development": "",
|
||||
"predev": "node ./scripts/build-ui-if-needed.js",
|
||||
"predev": "node ./scripts/build-ui-if-needed.js && node ./scripts/build-test-pages.js",
|
||||
"dev": "vite --mode development",
|
||||
"preview": "vite preview",
|
||||
"serve": "NODE_ENV=production PORT=${PORT:-4321} vite preview --port ${PORT:-4321}",
|
||||
@@ -16,6 +16,7 @@
|
||||
"build": "NODE_ENV=production vite build && pnpm run manifest-ts",
|
||||
"prebuild:watch": "pnpm predev",
|
||||
"build:watch": "vite build --watch && pnpm run manifest-ts",
|
||||
"test-pages:build": "node ./scripts/build-test-pages.js",
|
||||
"manifest-ts": "node ./scripts/add-timestamp-standalone-manifest.js",
|
||||
"// Deployment": "",
|
||||
"unraid:deploy": "pnpm build:dev",
|
||||
@@ -57,6 +58,7 @@
|
||||
"@types/crypto-js": "4.2.2",
|
||||
"@types/eslint-config-prettier": "6.11.3",
|
||||
"@types/node": "22.18.0",
|
||||
"@types/nunjucks": "^3.2.6",
|
||||
"@types/semver": "7.7.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.41.0",
|
||||
"@unraid/tailwind-rem-to-rem": "2.0.0",
|
||||
@@ -78,6 +80,7 @@
|
||||
"happy-dom": "18.0.1",
|
||||
"kebab-case": "2.0.2",
|
||||
"lodash-es": "4.17.21",
|
||||
"nunjucks": "3.2.4",
|
||||
"prettier": "3.6.2",
|
||||
"prettier-plugin-tailwindcss": "0.6.14",
|
||||
"tailwindcss": "4.1.12",
|
||||
|
||||
@@ -1,371 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>All Components - Unraid Component Test</title>
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<style>
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background: #f3f4f6;
|
||||
}
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
.component-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.component-card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
.component-card h3 {
|
||||
margin: 0 0 10px 0;
|
||||
color: #1f2937;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.component-card .selector {
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
margin-bottom: 15px;
|
||||
background: #f3f4f6;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
}
|
||||
.component-mount {
|
||||
min-height: 50px;
|
||||
border: 1px dashed #e5e7eb;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
position: relative;
|
||||
}
|
||||
.status {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #10b981;
|
||||
}
|
||||
.category-header {
|
||||
background: #1f2937;
|
||||
color: white;
|
||||
padding: 10px 15px;
|
||||
border-radius: 4px;
|
||||
margin: 30px 0 15px 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Authentication & User -->
|
||||
<div class="category-header">👤 Authentication & User</div>
|
||||
<div class="component-grid">
|
||||
<div class="component-card">
|
||||
<h3>Authentication</h3>
|
||||
<span class="selector"><unraid-auth></span>
|
||||
<div class="component-mount">
|
||||
<unraid-auth></unraid-auth>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="component-card">
|
||||
<h3>User Profile</h3>
|
||||
<span class="selector"><unraid-user-profile></span>
|
||||
<div class="component-mount">
|
||||
<unraid-user-profile></unraid-user-profile>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="component-card">
|
||||
<h3>SSO Button</h3>
|
||||
<span class="selector"><unraid-sso-button></span>
|
||||
<div class="component-mount">
|
||||
<unraid-sso-button></unraid-sso-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="component-card">
|
||||
<h3>Registration</h3>
|
||||
<span class="selector"><unraid-registration></span>
|
||||
<div class="component-mount">
|
||||
<unraid-registration></unraid-registration>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- System & Settings -->
|
||||
<div class="category-header">⚙️ System & Settings</div>
|
||||
<div class="component-grid">
|
||||
<div class="component-card">
|
||||
<h3>Connect Settings</h3>
|
||||
<span class="selector"><unraid-connect-settings></span>
|
||||
<div class="component-mount">
|
||||
<unraid-connect-settings></unraid-connect-settings>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="component-card">
|
||||
<h3>Theme Switcher</h3>
|
||||
<span class="selector"><unraid-theme-switcher></span>
|
||||
<div class="component-mount">
|
||||
<unraid-theme-switcher current="white"></unraid-theme-switcher>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="component-card">
|
||||
<h3>Header OS Version</h3>
|
||||
<span class="selector"><unraid-header-os-version></span>
|
||||
<div class="component-mount">
|
||||
<unraid-header-os-version></unraid-header-os-version>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="component-card">
|
||||
<h3>WAN IP Check</h3>
|
||||
<span class="selector"><unraid-wan-ip-check></span>
|
||||
<div class="component-mount">
|
||||
<unraid-wan-ip-check php-wan-ip="192.168.1.1"></unraid-wan-ip-check>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- OS Management -->
|
||||
<div class="category-header">💿 OS Management</div>
|
||||
<div class="component-grid">
|
||||
<div class="component-card">
|
||||
<h3>Update OS</h3>
|
||||
<span class="selector"><unraid-update-os></span>
|
||||
<div class="component-mount">
|
||||
<unraid-update-os></unraid-update-os>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="component-card">
|
||||
<h3>Downgrade OS</h3>
|
||||
<span class="selector"><unraid-downgrade-os></span>
|
||||
<div class="component-mount">
|
||||
<unraid-downgrade-os></unraid-downgrade-os>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- API & Developer -->
|
||||
<div class="category-header">🔧 API & Developer</div>
|
||||
<div class="component-grid">
|
||||
<div class="component-card">
|
||||
<h3>API Key Manager</h3>
|
||||
<span class="selector"><unraid-api-key-manager></span>
|
||||
<div class="component-mount">
|
||||
<unraid-api-key-manager></unraid-api-key-manager>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="component-card">
|
||||
<h3>API Key Authorize</h3>
|
||||
<span class="selector"><unraid-api-key-authorize></span>
|
||||
<div class="component-mount">
|
||||
<unraid-api-key-authorize></unraid-api-key-authorize>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="component-card">
|
||||
<h3>Download API Logs</h3>
|
||||
<span class="selector"><unraid-download-api-logs></span>
|
||||
<div class="component-mount">
|
||||
<unraid-download-api-logs></unraid-download-api-logs>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="component-card">
|
||||
<h3>Log Viewer</h3>
|
||||
<span class="selector"><unraid-log-viewer></span>
|
||||
<div class="component-mount">
|
||||
<unraid-log-viewer></unraid-log-viewer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- UI Components -->
|
||||
<div class="category-header">🎨 UI Components</div>
|
||||
<div class="component-grid">
|
||||
<div class="component-card">
|
||||
<h3>Modals</h3>
|
||||
<span class="selector"><unraid-modals></span>
|
||||
<div class="component-mount">
|
||||
<unraid-modals></unraid-modals>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="component-card">
|
||||
<h3>Welcome Modal</h3>
|
||||
<span class="selector"><unraid-welcome-modal></span>
|
||||
<div class="component-mount">
|
||||
<unraid-welcome-modal></unraid-welcome-modal>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="component-card">
|
||||
<h3>Dev Modal Test</h3>
|
||||
<span class="selector"><unraid-dev-modal-test></span>
|
||||
<div class="component-mount">
|
||||
<unraid-dev-modal-test></unraid-dev-modal-test>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="component-card">
|
||||
<h3>Toaster</h3>
|
||||
<span class="selector"><unraid-toaster></span>
|
||||
<div class="component-mount">
|
||||
<unraid-toaster></unraid-toaster>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test Controls -->
|
||||
<div class="category-header">🎮 Test Controls</div>
|
||||
<div style="background: white; padding: 20px; border-radius: 8px; margin-top: 15px;">
|
||||
<h3>Language Selection</h3>
|
||||
<div style="margin-bottom: 20px;">
|
||||
<unraid-locale-switcher></unraid-locale-switcher>
|
||||
</div>
|
||||
|
||||
<h3>jQuery Interaction Tests</h3>
|
||||
<div style="display: flex; gap: 10px; flex-wrap: wrap; margin-top: 15px;">
|
||||
<button id="test-notification" class="test-btn">Trigger Notification</button>
|
||||
<button id="test-modal" class="test-btn">Open Test Modal</button>
|
||||
<button id="test-theme" class="test-btn">Toggle Theme</button>
|
||||
<button id="test-update-profile" class="test-btn">Update Profile Data</button>
|
||||
<button id="test-settings" class="test-btn">Update Settings</button>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 20px;">
|
||||
<h4>Console Output</h4>
|
||||
<div id="test-output" style="background: #1f2937; color: #10b981; padding: 10px; border-radius: 4px; font-family: monospace; font-size: 12px; min-height: 100px; max-height: 200px; overflow-y: auto;">
|
||||
> Ready for testing...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.test-btn {
|
||||
padding: 8px 16px;
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
.test-btn:hover {
|
||||
background: #2563eb;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Load the manifest and inject resources -->
|
||||
<script src="/test-pages/language-switcher.js"></script>
|
||||
<script src="/test-pages/load-manifest.js"></script>
|
||||
<script src="/test-pages/test-server-state.js"></script>
|
||||
<script src="/test-pages/shared-header.js"></script>
|
||||
|
||||
<!-- Test interactions -->
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
const output = $('#test-output');
|
||||
|
||||
function log(message) {
|
||||
// Use shared header's testLog if available, otherwise local log
|
||||
if (window.testLog) {
|
||||
window.testLog(message);
|
||||
}
|
||||
if (output.length) {
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
output.append('\n> [' + timestamp + '] ' + message);
|
||||
output.scrollTop(output[0].scrollHeight);
|
||||
}
|
||||
}
|
||||
|
||||
// Test notification
|
||||
$('#test-notification').on('click', function() {
|
||||
log('Triggering notification...');
|
||||
const event = new CustomEvent('unraid:notification', {
|
||||
detail: {
|
||||
title: 'Test Notification',
|
||||
message: 'This is a test from jQuery!',
|
||||
type: 'success'
|
||||
}
|
||||
});
|
||||
document.dispatchEvent(event);
|
||||
});
|
||||
|
||||
// Test modal
|
||||
$('#test-modal').on('click', function() {
|
||||
log('Opening test modal...');
|
||||
// This would trigger the modal system
|
||||
window.dispatchEvent(new CustomEvent('unraid:open-modal', {
|
||||
detail: { modalId: 'test-modal' }
|
||||
}));
|
||||
});
|
||||
|
||||
// Test theme toggle
|
||||
$('#test-theme').on('click', function() {
|
||||
log('Toggling theme...');
|
||||
const currentTheme = $('body').hasClass('dark') ? 'light' : 'dark';
|
||||
$('body').toggleClass('dark');
|
||||
log('Theme changed to: ' + currentTheme);
|
||||
});
|
||||
|
||||
// Test profile update
|
||||
$('#test-update-profile').on('click', function() {
|
||||
log('Updating profile data...');
|
||||
const profileData = {
|
||||
name: 'Test User ' + Math.floor(Math.random() * 100),
|
||||
email: 'test' + Math.floor(Math.random() * 100) + '@example.com',
|
||||
username: 'testuser'
|
||||
};
|
||||
$('unraid-user-profile').attr('server', JSON.stringify(profileData));
|
||||
log('Profile updated: ' + JSON.stringify(profileData));
|
||||
});
|
||||
|
||||
// Test settings update
|
||||
$('#test-settings').on('click', function() {
|
||||
log('Updating connect settings...');
|
||||
const settings = {
|
||||
enabled: Math.random() > 0.5,
|
||||
url: 'https://connect.unraid.net',
|
||||
lastSync: new Date().toISOString()
|
||||
};
|
||||
$('unraid-connect-settings').attr('initial-settings', JSON.stringify(settings));
|
||||
log('Settings updated: ' + JSON.stringify(settings));
|
||||
});
|
||||
|
||||
// Listen for component events
|
||||
$(document).on('unraid:theme-changed', function(e, data) {
|
||||
log('Theme changed event received: ' + JSON.stringify(data));
|
||||
});
|
||||
|
||||
$(document).on('unraid:settings-saved', function(e, data) {
|
||||
log('Settings saved event received: ' + JSON.stringify(data));
|
||||
});
|
||||
|
||||
log('Test page initialized - all components loaded');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,218 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Authentication Flow - Unraid Component Test</title>
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<style>
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: #f3f4f6;
|
||||
}
|
||||
.auth-container {
|
||||
max-width: 600px;
|
||||
margin: 50px auto;
|
||||
padding: 20px;
|
||||
}
|
||||
.auth-card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.auth-card h2 {
|
||||
margin: 0 0 20px 0;
|
||||
color: #1f2937;
|
||||
}
|
||||
.user-info {
|
||||
background: #f3f4f6;
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.status-badge {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.status-badge.authenticated {
|
||||
background: #10b981;
|
||||
color: white;
|
||||
}
|
||||
.status-badge.unauthenticated {
|
||||
background: #ef4444;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="auth-container">
|
||||
<!-- Login Card -->
|
||||
<div class="auth-card">
|
||||
<h2>🔐 Authentication</h2>
|
||||
<unraid-auth></unraid-auth>
|
||||
<div class="user-info">
|
||||
<strong>Session Status:</strong>
|
||||
<div id="auth-status">
|
||||
<span class="status-badge unauthenticated">Not Authenticated</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SSO Options -->
|
||||
<div class="auth-card">
|
||||
<h2>🔗 Single Sign-On</h2>
|
||||
<p style="color: #6b7280; margin-bottom: 20px;">Alternative authentication methods</p>
|
||||
<unraid-sso-button></unraid-sso-button>
|
||||
</div>
|
||||
|
||||
<!-- User Profile -->
|
||||
<div class="auth-card">
|
||||
<h2>👤 User Profile</h2>
|
||||
<p style="color: #6b7280; margin-bottom: 20px;">Displays when authenticated</p>
|
||||
<unraid-user-profile id="user-profile"></unraid-user-profile>
|
||||
</div>
|
||||
|
||||
<!-- Registration -->
|
||||
<div class="auth-card">
|
||||
<h2>📝 System Registration</h2>
|
||||
<unraid-registration></unraid-registration>
|
||||
</div>
|
||||
|
||||
<!-- Test Controls -->
|
||||
<div class="auth-card" style="background: #1f2937; color: white;">
|
||||
<h2 style="color: white;">🧪 Test Controls</h2>
|
||||
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
|
||||
<button id="simulate-login" class="btn">Simulate Login</button>
|
||||
<button id="simulate-logout" class="btn">Simulate Logout</button>
|
||||
<button id="update-profile" class="btn">Update Profile</button>
|
||||
<button id="check-session" class="btn">Check Session</button>
|
||||
</div>
|
||||
<div id="console-output" style="margin-top: 20px; padding: 10px; background: black; border-radius: 4px; font-family: monospace; font-size: 12px; min-height: 80px;">
|
||||
> Ready...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.btn {
|
||||
padding: 8px 16px;
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
.btn:hover {
|
||||
background: #2563eb;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Global Modals -->
|
||||
<unraid-modals></unraid-modals>
|
||||
|
||||
<!-- Load the manifest -->
|
||||
<script src="/test-pages/language-switcher.js"></script>
|
||||
<script src="/test-pages/load-manifest.js"></script>
|
||||
|
||||
<!-- Authentication test logic -->
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
const output = $('#console-output');
|
||||
let isAuthenticated = false;
|
||||
|
||||
function log(message) {
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
output.append('\n> [' + timestamp + '] ' + message);
|
||||
output.scrollTop(output[0].scrollHeight);
|
||||
}
|
||||
|
||||
// Simulate login
|
||||
$('#simulate-login').on('click', function() {
|
||||
log('Simulating login...');
|
||||
isAuthenticated = true;
|
||||
|
||||
const userData = {
|
||||
username: 'admin',
|
||||
email: 'admin@unraid.local',
|
||||
name: 'Administrator',
|
||||
avatarUrl: '/webGui/images/default-avatar.png',
|
||||
role: 'admin',
|
||||
sessionId: 'sess_' + Math.random().toString(36).substr(2, 9)
|
||||
};
|
||||
|
||||
// Update components
|
||||
$('#user-profile').attr('server', JSON.stringify(userData));
|
||||
$('#auth-status').html('<span class="status-badge authenticated">Authenticated as ' + userData.username + '</span>');
|
||||
|
||||
// Dispatch login event
|
||||
$(document).trigger('unraid:auth-login', userData);
|
||||
log('Login successful: ' + userData.username);
|
||||
});
|
||||
|
||||
// Simulate logout
|
||||
$('#simulate-logout').on('click', function() {
|
||||
log('Simulating logout...');
|
||||
isAuthenticated = false;
|
||||
|
||||
$('#user-profile').attr('server', '{}');
|
||||
$('#auth-status').html('<span class="status-badge unauthenticated">Not Authenticated</span>');
|
||||
|
||||
// Dispatch logout event
|
||||
$(document).trigger('unraid:auth-logout');
|
||||
log('Logged out successfully');
|
||||
});
|
||||
|
||||
// Update profile
|
||||
$('#update-profile').on('click', function() {
|
||||
if (!isAuthenticated) {
|
||||
log('Error: Not authenticated');
|
||||
return;
|
||||
}
|
||||
|
||||
log('Updating profile...');
|
||||
const updatedData = {
|
||||
username: 'admin',
|
||||
email: 'newemail@unraid.local',
|
||||
name: 'Updated Admin',
|
||||
lastModified: new Date().toISOString()
|
||||
};
|
||||
|
||||
$('#user-profile').attr('server', JSON.stringify(updatedData));
|
||||
log('Profile updated');
|
||||
});
|
||||
|
||||
// Check session
|
||||
$('#check-session').on('click', function() {
|
||||
log('Checking session status...');
|
||||
log('Authenticated: ' + (isAuthenticated ? 'Yes' : 'No'));
|
||||
|
||||
if (isAuthenticated) {
|
||||
// Would normally make an API call here
|
||||
log('Session valid until: ' + new Date(Date.now() + 3600000).toLocaleTimeString());
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for auth events from components
|
||||
$(document).on('unraid:auth-required', function() {
|
||||
log('Authentication required by component');
|
||||
});
|
||||
|
||||
$(document).on('unraid:session-expired', function() {
|
||||
log('Session expired - please login again');
|
||||
$('#simulate-logout').click();
|
||||
});
|
||||
|
||||
log('Authentication test page ready');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,357 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Component Mounting Test - Unraid Component Test</title>
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<style>
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background: #f3f4f6;
|
||||
}
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.test-section {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
h2 {
|
||||
color: #666;
|
||||
margin-top: 0;
|
||||
margin-bottom: 15px;
|
||||
font-size: 18px;
|
||||
}
|
||||
.status {
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 10px;
|
||||
font-family: monospace;
|
||||
font-size: 14px;
|
||||
}
|
||||
.status.loading {
|
||||
background: #fff3cd;
|
||||
color: #856404;
|
||||
}
|
||||
.status.success {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
.status.error {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
.mount-target {
|
||||
padding: 20px;
|
||||
background: #fafafa;
|
||||
border: 2px dashed #ddd;
|
||||
border-radius: 4px;
|
||||
min-height: 100px;
|
||||
position: relative;
|
||||
}
|
||||
.mount-target::before {
|
||||
content: attr(data-label);
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
left: 10px;
|
||||
background: white;
|
||||
padding: 0 5px;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
.debug-info {
|
||||
margin-top: 20px;
|
||||
padding: 15px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.multiple-mounts {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.test-button {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.test-button:hover {
|
||||
background: #0056b3;
|
||||
}
|
||||
.test-button:disabled {
|
||||
background: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Teleport target for dropdowns and modals -->
|
||||
<div id="teleports"></div>
|
||||
|
||||
<!-- Mount point for Modals component -->
|
||||
<unraid-modals></unraid-modals>
|
||||
|
||||
<div class="container">
|
||||
<h1>🧪 Standalone Vue Apps Test Page</h1>
|
||||
<div id="status" class="status loading">Loading...</div>
|
||||
|
||||
<!-- Test Section 1: Single Mount -->
|
||||
<div class="test-section">
|
||||
<h2>Test 1: Single Component Mount</h2>
|
||||
<p>Testing single instance of HeaderOsVersion component</p>
|
||||
<div class="mount-target" data-label="HeaderOsVersion Mount">
|
||||
<unraid-header-os-version></unraid-header-os-version>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test Section 2: Multiple Mounts -->
|
||||
<div class="test-section">
|
||||
<h2>Test 2: Multiple Component Mounts (Shared Pinia Store)</h2>
|
||||
<p>Testing that multiple instances share the same Pinia store</p>
|
||||
<div class="multiple-mounts">
|
||||
<div class="mount-target" data-label="Instance 1">
|
||||
<unraid-header-os-version></unraid-header-os-version>
|
||||
</div>
|
||||
<div class="mount-target" data-label="Instance 2">
|
||||
<unraid-header-os-version></unraid-header-os-version>
|
||||
</div>
|
||||
<div class="mount-target" data-label="Instance 3">
|
||||
<unraid-header-os-version></unraid-header-os-version>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test Section 3: Dynamic Mount -->
|
||||
<div class="test-section">
|
||||
<h2>Test 3: Dynamic Component Creation</h2>
|
||||
<p>Test dynamically adding components after page load</p>
|
||||
<button class="test-button" id="addComponent">Add New Component</button>
|
||||
<button class="test-button" id="removeComponent">Remove Last Component</button>
|
||||
<button class="test-button" id="remountAll">Remount All</button>
|
||||
<div id="dynamicContainer" style="margin-top: 20px;">
|
||||
<!-- Dynamic components will be added here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test Section 4: Modal Testing -->
|
||||
<div class="test-section">
|
||||
<h2>Test 4: Modal Components</h2>
|
||||
<p>Test modal functionality</p>
|
||||
<button class="test-button" onclick="testTrialModal()">Open Trial Modal</button>
|
||||
<button class="test-button" onclick="testUpdateModal()">Open Update Modal</button>
|
||||
<button class="test-button" onclick="testApiKeyModal()">Open API Key Modal</button>
|
||||
<div style="margin-top: 10px;">
|
||||
<small>Note: Modals require proper store state to display</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Debug Info -->
|
||||
<div class="test-section">
|
||||
<h2>Debug Information</h2>
|
||||
<div class="debug-info" id="debugInfo">
|
||||
Waiting for initialization...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Configuration for local testing -->
|
||||
<script>
|
||||
// Set GraphQL endpoint - handled by Vite proxy in dev mode
|
||||
window.GRAPHQL_ENDPOINT = window.location.port === '3000' ? '/graphql' : 'http://localhost:3001/graphql';
|
||||
|
||||
// Mock webGui path for images
|
||||
window.__WEBGUI_PATH__ = '';
|
||||
|
||||
// Add some debug logging
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const status = document.getElementById('status');
|
||||
const debugInfo = document.getElementById('debugInfo');
|
||||
|
||||
// Log when scripts are loaded
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
if (mutation.type === 'childList') {
|
||||
mutation.addedNodes.forEach((node) => {
|
||||
if (node.nodeName === 'SCRIPT') {
|
||||
console.log('Script loaded:', node.src || 'inline');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
observer.observe(document.head, { childList: true });
|
||||
observer.observe(document.body, { childList: true });
|
||||
|
||||
// Check for Vue app mounting
|
||||
let checkInterval = setInterval(() => {
|
||||
const mountedElements = document.querySelectorAll('[data-vue-mounted="true"]');
|
||||
let totalComponents = document.querySelectorAll('unraid-header-os-version, unraid-modals').length;
|
||||
let mountedCount = mountedElements.length;
|
||||
|
||||
if (mountedCount > 0) {
|
||||
status.className = 'status success';
|
||||
status.textContent = `✅ Successfully mounted ${mountedCount} component(s)`;
|
||||
|
||||
// Update debug info
|
||||
debugInfo.textContent = `
|
||||
Components Found: ${totalComponents}
|
||||
Components Mounted: ${mountedCount}
|
||||
Unified Vue App: ${window.__unifiedApp ? 'Initialized' : 'Not found'}
|
||||
Mounted Components: ${window.__mountedComponents ? window.__mountedComponents.length : 0}
|
||||
Pinia Store: ${window.globalPinia ? 'Initialized' : 'Not found'}
|
||||
GraphQL Endpoint: ${window.GRAPHQL_ENDPOINT || 'Not configured'}
|
||||
`.trim();
|
||||
|
||||
clearInterval(checkInterval);
|
||||
|
||||
// Log to test console if available
|
||||
if (window.testLog) {
|
||||
window.testLog(`Mounted ${mountedCount} components successfully`, 'success');
|
||||
}
|
||||
}
|
||||
}, 500);
|
||||
|
||||
// Timeout after 10 seconds
|
||||
setTimeout(() => {
|
||||
if (checkInterval) {
|
||||
clearInterval(checkInterval);
|
||||
if (status.className === 'status loading') {
|
||||
status.className = 'status error';
|
||||
status.textContent = '❌ Failed to mount components (timeout)';
|
||||
}
|
||||
}
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
// Dynamic component controls
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
let dynamicCount = 0;
|
||||
const dynamicContainer = document.getElementById('dynamicContainer');
|
||||
|
||||
document.getElementById('addComponent').addEventListener('click', () => {
|
||||
dynamicCount++;
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'mount-target';
|
||||
wrapper.setAttribute('data-label', `Dynamic Instance ${dynamicCount}`);
|
||||
wrapper.style.marginBottom = '10px';
|
||||
|
||||
// Create the custom element
|
||||
const element = document.createElement('unraid-header-os-version');
|
||||
wrapper.appendChild(element);
|
||||
dynamicContainer.appendChild(wrapper);
|
||||
|
||||
// The unified mount system doesn't support dynamic addition after initial mount
|
||||
// For now, we'll just add the element and note it won't be mounted until reload
|
||||
console.log('Note: Dynamic components require page reload to mount with the unified app system');
|
||||
|
||||
// Show a message that reload is needed
|
||||
if (!wrapper.querySelector('.reload-note')) {
|
||||
const note = document.createElement('div');
|
||||
note.className = 'reload-note';
|
||||
note.style.cssText = 'color: #666; font-size: 12px; margin-top: 10px;';
|
||||
note.textContent = 'Reload page to mount this component';
|
||||
wrapper.appendChild(note);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('removeComponent').addEventListener('click', () => {
|
||||
const lastChild = dynamicContainer.lastElementChild;
|
||||
if (lastChild) {
|
||||
// If component was mounted, unmount it properly
|
||||
const mountedElement = lastChild.querySelector('[data-vue-mounted="true"]');
|
||||
if (mountedElement && window.__mountedComponents) {
|
||||
const componentIndex = window.__mountedComponents.findIndex(c => c.element === mountedElement);
|
||||
if (componentIndex !== -1) {
|
||||
window.__mountedComponents[componentIndex].unmount();
|
||||
window.__mountedComponents.splice(componentIndex, 1);
|
||||
}
|
||||
}
|
||||
dynamicContainer.removeChild(lastChild);
|
||||
dynamicCount = Math.max(0, dynamicCount - 1);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('remountAll').addEventListener('click', () => {
|
||||
console.log('Remounting all components...');
|
||||
// The unified app requires a full reload to remount
|
||||
location.reload();
|
||||
});
|
||||
});
|
||||
|
||||
// Modal test functions
|
||||
window.testTrialModal = function() {
|
||||
console.log('Testing trial modal...');
|
||||
if (window.globalPinia) {
|
||||
const trialStore = window.globalPinia._s.get('trial');
|
||||
if (trialStore) {
|
||||
trialStore.trialModalVisible = true;
|
||||
console.log('Trial modal triggered');
|
||||
} else {
|
||||
console.error('Trial store not found');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.testUpdateModal = function() {
|
||||
console.log('Testing update modal...');
|
||||
if (window.globalPinia) {
|
||||
const updateStore = window.globalPinia._s.get('updateOs');
|
||||
if (updateStore) {
|
||||
updateStore.updateOsModalVisible = true;
|
||||
console.log('Update modal triggered');
|
||||
} else {
|
||||
console.error('Update store not found');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.testApiKeyModal = function() {
|
||||
console.log('Testing API key modal...');
|
||||
if (window.globalPinia) {
|
||||
const apiKeyStore = window.globalPinia._s.get('apiKey');
|
||||
if (apiKeyStore) {
|
||||
apiKeyStore.showCreateModal = true;
|
||||
console.log('API key modal triggered');
|
||||
} else {
|
||||
console.error('API key store not found');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- Load shared header and manifest resources -->
|
||||
<script src="/test-pages/language-switcher.js"></script>
|
||||
<script src="/test-pages/shared-header.js"></script>
|
||||
<script src="/test-pages/load-manifest.js"></script>
|
||||
<script src="/test-pages/test-server-state.js"></script>
|
||||
|
||||
<script>
|
||||
// Initialize page
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (window.initializeSharedHeader) {
|
||||
window.initializeSharedHeader('Component Mounting Test');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,204 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Dashboard - Unraid Component Test</title>
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<style>
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background: #f3f4f6;
|
||||
}
|
||||
.dashboard-header {
|
||||
background: #1f2937;
|
||||
color: white;
|
||||
padding: 16px 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
.logo {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.main-content {
|
||||
padding: 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
.card h2 {
|
||||
margin-top: 0;
|
||||
color: #1f2937;
|
||||
}
|
||||
.breadcrumb {
|
||||
padding: 10px 20px;
|
||||
background: white;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
.breadcrumb a {
|
||||
color: #3b82f6;
|
||||
text-decoration: none;
|
||||
}
|
||||
.component-mount {
|
||||
padding: 10px;
|
||||
border: 2px dashed #e5e7eb;
|
||||
border-radius: 4px;
|
||||
min-height: 50px;
|
||||
position: relative;
|
||||
}
|
||||
.component-mount::before {
|
||||
content: attr(data-component);
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
left: 10px;
|
||||
background: white;
|
||||
padding: 0 5px;
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Main dashboard content -->
|
||||
<div class="dashboard-header">
|
||||
<div class="header-left">
|
||||
<div class="logo">UNRAID</div>
|
||||
<!-- OS Version Component -->
|
||||
<unraid-header-os-version></unraid-header-os-version>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<!-- User Profile Component -->
|
||||
<unraid-user-profile id="header-user-profile"></unraid-user-profile>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Breadcrumb -->
|
||||
<div class="breadcrumb">
|
||||
<a href="/test-pages/">Test Pages</a> / Dashboard
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="main-content">
|
||||
<div class="card">
|
||||
<h2>System Information</h2>
|
||||
<div class="component-mount" data-component="unraid-header-os-version">
|
||||
<unraid-header-os-version></unraid-header-os-version>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Theme Settings</h2>
|
||||
<div class="component-mount" data-component="unraid-theme-switcher">
|
||||
<unraid-theme-switcher current="white"></unraid-theme-switcher>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Authentication</h2>
|
||||
<div class="component-mount" data-component="unraid-auth">
|
||||
<unraid-auth></unraid-auth>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>WAN IP Check</h2>
|
||||
<div class="component-mount" data-component="unraid-wan-ip-check">
|
||||
<unraid-wan-ip-check php-wan-ip="192.168.1.1"></unraid-wan-ip-check>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>API Logs</h2>
|
||||
<button id="toggle-logs" style="padding: 8px 16px; background: #3b82f6; color: white; border: none; border-radius: 4px; cursor: pointer;">
|
||||
Toggle Log Component
|
||||
</button>
|
||||
<div id="logs-container" class="component-mount" data-component="unraid-download-api-logs" style="margin-top: 10px; display: none;">
|
||||
<unraid-download-api-logs></unraid-download-api-logs>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Global Modals -->
|
||||
<unraid-modals></unraid-modals>
|
||||
|
||||
<!-- Load the manifest and inject resources (mimics PHP extractor) -->
|
||||
<script src="/test-pages/language-switcher.js"></script>
|
||||
<script src="/test-pages/load-manifest.js"></script>
|
||||
<script src="/test-pages/test-server-state.js"></script>
|
||||
<script src="/test-pages/shared-header.js"></script>
|
||||
|
||||
<!-- jQuery interactions mimicking Unraid -->
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Example: Pass server data to user profile component via jQuery
|
||||
// This mimics how Unraid PHP would set data
|
||||
var serverData = {
|
||||
name: 'TestServer',
|
||||
version: '6.12.4',
|
||||
username: 'admin',
|
||||
email: 'admin@unraid.net',
|
||||
avatarUrl: '/webGui/images/default-avatar.png'
|
||||
};
|
||||
|
||||
// Set attribute on the component (Vue will pick this up)
|
||||
$('#header-user-profile').attr('server', JSON.stringify(serverData));
|
||||
|
||||
// Toggle logs visibility with jQuery
|
||||
$('#toggle-logs').on('click', function() {
|
||||
$('#logs-container').slideToggle();
|
||||
});
|
||||
|
||||
// Example: Update WAN IP dynamically (like Unraid would do after an AJAX call)
|
||||
setTimeout(function() {
|
||||
$('unraid-wan-ip-check').attr('php-wan-ip', '203.0.113.42');
|
||||
console.log('Updated WAN IP via jQuery');
|
||||
}, 3000);
|
||||
|
||||
// Example: Trigger component methods from jQuery
|
||||
// Components can expose methods via window object
|
||||
window.updateUserProfile = function(newData) {
|
||||
$('#header-user-profile').attr('server', JSON.stringify(newData));
|
||||
};
|
||||
|
||||
// Example: Listen for events from Vue components
|
||||
// Components can emit custom DOM events
|
||||
$(document).on('unraid:theme-changed', function(e, data) {
|
||||
console.log('Theme changed to:', data.theme);
|
||||
// Update body class or do other jQuery operations
|
||||
$('body').toggleClass('dark-mode', data.theme === 'dark');
|
||||
});
|
||||
|
||||
// Example: Show a notification (like Unraid's addNotification)
|
||||
window.addNotification = function(title, message, type) {
|
||||
// This would trigger the Vue toast/notification system
|
||||
var event = new CustomEvent('unraid:notification', {
|
||||
detail: { title: title, message: message, type: type }
|
||||
});
|
||||
document.dispatchEvent(event);
|
||||
};
|
||||
|
||||
// Test notification after 2 seconds
|
||||
setTimeout(function() {
|
||||
addNotification('Test Notification', 'This is from jQuery!', 'success');
|
||||
}, 2000);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
249
web/public/test-pages/dev-tools.js
Normal file
249
web/public/test-pages/dev-tools.js
Normal file
@@ -0,0 +1,249 @@
|
||||
(() => {
|
||||
const localeOptions = [
|
||||
{ value: 'en_US', label: 'English (US)' },
|
||||
{ value: 'ar', label: 'العربية (Arabic)' },
|
||||
{ value: 'bn', label: 'বাংলা (Bengali)' },
|
||||
{ value: 'ca', label: 'Català (Catalan)' },
|
||||
{ value: 'cs', label: 'Čeština (Czech)' },
|
||||
{ value: 'da', label: 'Dansk (Danish)' },
|
||||
{ value: 'de', label: 'Deutsch (German)' },
|
||||
{ value: 'es', label: 'Español (Spanish)' },
|
||||
{ value: 'fr', label: 'Français (French)' },
|
||||
{ value: 'hi', label: 'हिन्दी (Hindi)' },
|
||||
{ value: 'hr', label: 'Hrvatski (Croatian)' },
|
||||
{ value: 'hu', label: 'Magyar (Hungarian)' },
|
||||
{ value: 'it', label: 'Italiano (Italian)' },
|
||||
{ value: 'ja', label: '日本語 (Japanese)' },
|
||||
{ value: 'ko', label: '한국어 (Korean)' },
|
||||
{ value: 'lv', label: 'Latviešu (Latvian)' },
|
||||
{ value: 'nl', label: 'Nederlands (Dutch)' },
|
||||
{ value: 'no', label: 'Norsk (Norwegian)' },
|
||||
{ value: 'pl', label: 'Polski (Polish)' },
|
||||
{ value: 'pt', label: 'Português (Portuguese)' },
|
||||
{ value: 'ro', label: 'Română (Romanian)' },
|
||||
{ value: 'ru', label: 'Русский (Russian)' },
|
||||
{ value: 'sv', label: 'Svenska (Swedish)' },
|
||||
{ value: 'uk', label: 'Українська (Ukrainian)' },
|
||||
{ value: 'zh', label: '中文 (Chinese)' },
|
||||
];
|
||||
|
||||
if (document.getElementById('dev-tools')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
#dev-tools {
|
||||
position: fixed;
|
||||
right: 16px;
|
||||
bottom: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
background: rgba(17, 24, 39, 0.95);
|
||||
color: #f9fafb;
|
||||
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.35);
|
||||
z-index: 9999;
|
||||
max-width: 260px;
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
#dev-tools h3 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: #60a5fa;
|
||||
}
|
||||
|
||||
#dev-tools .control-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
#dev-tools label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
color: rgba(226, 232, 240, 0.9);
|
||||
}
|
||||
|
||||
#dev-tools select {
|
||||
padding: 8px 10px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(148, 163, 184, 0.4);
|
||||
background: rgba(15, 23, 42, 0.9);
|
||||
color: #f9fafb;
|
||||
font-size: 13px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
#dev-tools select:hover {
|
||||
border-color: rgba(148, 163, 184, 0.6);
|
||||
}
|
||||
|
||||
#dev-tools select:focus {
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.3);
|
||||
}
|
||||
|
||||
#dev-tools .info {
|
||||
font-size: 11px;
|
||||
color: rgba(226, 232, 240, 0.7);
|
||||
line-height: 1.4;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
#dev-tools .divider {
|
||||
height: 1px;
|
||||
background: rgba(148, 163, 184, 0.2);
|
||||
margin: 4px 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.id = 'dev-tools';
|
||||
|
||||
const title = document.createElement('h3');
|
||||
title.textContent = '🛠️ Dev Tools';
|
||||
|
||||
const localeGroup = document.createElement('div');
|
||||
localeGroup.className = 'control-group';
|
||||
|
||||
const localeLabel = document.createElement('label');
|
||||
localeLabel.htmlFor = 'dev-locale-select';
|
||||
localeLabel.textContent = 'Language';
|
||||
|
||||
const localeSelect = document.createElement('select');
|
||||
localeSelect.id = 'dev-locale-select';
|
||||
|
||||
const themeGroup = document.createElement('div');
|
||||
themeGroup.className = 'control-group';
|
||||
|
||||
const themeLabel = document.createElement('label');
|
||||
themeLabel.htmlFor = 'dev-theme-switcher';
|
||||
themeLabel.textContent = 'Theme';
|
||||
|
||||
const themeSwitcherContainer = document.createElement('div');
|
||||
themeSwitcherContainer.id = 'dev-theme-switcher-container';
|
||||
|
||||
const STORAGE_KEY_LOCALE = 'unraid:test:locale';
|
||||
const availableLocales = new Set(localeOptions.map((option) => option.value));
|
||||
|
||||
const readPersistedLocale = () => {
|
||||
try {
|
||||
return window.localStorage?.getItem(STORAGE_KEY_LOCALE) ?? undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const resolveInitialLocale = () => {
|
||||
const candidates = [
|
||||
typeof window.LOCALE === 'string' ? window.LOCALE : undefined,
|
||||
readPersistedLocale(),
|
||||
'en_US',
|
||||
];
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (candidate && availableLocales.has(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return 'en_US';
|
||||
};
|
||||
|
||||
const initialLocale = resolveInitialLocale();
|
||||
window.LOCALE = initialLocale;
|
||||
let currentLocale = initialLocale;
|
||||
|
||||
localeOptions.forEach((option) => {
|
||||
const optionElement = document.createElement('option');
|
||||
optionElement.value = option.value;
|
||||
optionElement.textContent = option.label;
|
||||
if (option.value === currentLocale) {
|
||||
optionElement.selected = true;
|
||||
}
|
||||
localeSelect.appendChild(optionElement);
|
||||
});
|
||||
|
||||
const createThemeSwitcher = () => {
|
||||
const themeSwitcherElement = document.createElement('unraid-dev-theme-switcher');
|
||||
themeSwitcherContainer.appendChild(themeSwitcherElement);
|
||||
};
|
||||
|
||||
localeSelect.addEventListener('change', (event) => {
|
||||
const nextLocale = event.target.value;
|
||||
if (nextLocale === currentLocale) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
window.localStorage?.setItem(STORAGE_KEY_LOCALE, nextLocale);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
window.LOCALE = nextLocale;
|
||||
currentLocale = nextLocale;
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
const localeInfo = document.createElement('div');
|
||||
localeInfo.className = 'info';
|
||||
localeInfo.textContent = 'Reloads page to apply locale.';
|
||||
|
||||
const themeInfo = document.createElement('div');
|
||||
themeInfo.className = 'info';
|
||||
themeInfo.textContent = 'Updates theme instantly via Vue component.';
|
||||
|
||||
localeGroup.appendChild(localeLabel);
|
||||
localeGroup.appendChild(localeSelect);
|
||||
localeGroup.appendChild(localeInfo);
|
||||
|
||||
themeGroup.appendChild(themeLabel);
|
||||
themeGroup.appendChild(themeSwitcherContainer);
|
||||
themeGroup.appendChild(themeInfo);
|
||||
|
||||
container.appendChild(title);
|
||||
container.appendChild(localeGroup);
|
||||
|
||||
const divider = document.createElement('div');
|
||||
divider.className = 'divider';
|
||||
container.appendChild(divider);
|
||||
|
||||
container.appendChild(themeGroup);
|
||||
|
||||
const attach = () => {
|
||||
if (!document.head.contains(style)) {
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
if (!document.body.contains(container)) {
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
};
|
||||
|
||||
const initializeTheme = () => {
|
||||
createThemeSwitcher();
|
||||
};
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener(
|
||||
'DOMContentLoaded',
|
||||
() => {
|
||||
attach();
|
||||
initializeTheme();
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
} else {
|
||||
attach();
|
||||
initializeTheme();
|
||||
}
|
||||
})();
|
||||
@@ -1,264 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Unraid Component Test Pages</title>
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<style>
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background: #f3f4f6;
|
||||
}
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.header {
|
||||
background: linear-gradient(135deg, #1f2937 0%, #374151 100%);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
}
|
||||
.header h1 {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 32px;
|
||||
}
|
||||
.header p {
|
||||
margin: 0;
|
||||
opacity: 0.9;
|
||||
}
|
||||
.category-section {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.category-header {
|
||||
background: #1f2937;
|
||||
color: white;
|
||||
padding: 12px 20px;
|
||||
border-radius: 6px 6px 0 0;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.page-list {
|
||||
background: white;
|
||||
border-radius: 0 0 6px 6px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
.page-item {
|
||||
padding: 15px 20px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.page-item:hover {
|
||||
background: #f9fafb;
|
||||
}
|
||||
.page-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.page-item h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
color: #1f2937;
|
||||
}
|
||||
.page-item p {
|
||||
margin: 5px 0 0 0;
|
||||
color: #6b7280;
|
||||
font-size: 14px;
|
||||
}
|
||||
.page-item .badge {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
background: #dbeafe;
|
||||
color: #1e40af;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.page-item .badge.new {
|
||||
background: #d1fae5;
|
||||
color: #065f46;
|
||||
}
|
||||
.page-item a {
|
||||
padding: 8px 16px;
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.page-item a:hover {
|
||||
background: #2563eb;
|
||||
}
|
||||
.info-box {
|
||||
background: #fef3c7;
|
||||
border: 1px solid #f59e0b;
|
||||
border-radius: 6px;
|
||||
padding: 15px 20px;
|
||||
margin-bottom: 30px;
|
||||
color: #92400e;
|
||||
}
|
||||
.info-box strong {
|
||||
color: #78350f;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>🧪 Unraid Component Test Environment</h1>
|
||||
<p>HTML-based test pages that mimic Unraid OS integration with jQuery and component mounting</p>
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<strong>ℹ️ Testing Mode:</strong> These pages replicate how Unraid OS mounts Vue components into existing HTML/PHP pages using jQuery for interaction. Each page demonstrates real-world integration patterns.
|
||||
</div>
|
||||
|
||||
<!-- Core Test Pages -->
|
||||
<div class="category-section">
|
||||
<div class="category-header">
|
||||
🎯 Core Test Pages
|
||||
</div>
|
||||
<div class="page-list">
|
||||
<div class="page-item">
|
||||
<div>
|
||||
<h3>All Components <span class="badge new">COMPREHENSIVE</span></h3>
|
||||
<p>Complete component library showcase with all available components</p>
|
||||
</div>
|
||||
<a href="/test-pages/all-components.html">Open →</a>
|
||||
</div>
|
||||
|
||||
<div class="page-item">
|
||||
<div>
|
||||
<h3>Dashboard</h3>
|
||||
<p>Main dashboard with header components, system status, and user profile</p>
|
||||
</div>
|
||||
<a href="/test-pages/dashboard.html">Open →</a>
|
||||
</div>
|
||||
|
||||
<div class="page-item">
|
||||
<div>
|
||||
<h3>Settings</h3>
|
||||
<p>System settings with theme switcher, Connect configuration, and API keys</p>
|
||||
</div>
|
||||
<a href="/test-pages/settings.html">Open →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Feature-Specific Pages -->
|
||||
<div class="category-section">
|
||||
<div class="category-header">
|
||||
⚡ Feature-Specific Pages
|
||||
</div>
|
||||
<div class="page-list">
|
||||
<div class="page-item">
|
||||
<div>
|
||||
<h3>Authentication Flow <span class="badge new">NEW</span></h3>
|
||||
<p>Complete auth workflow with login, SSO, user profile, and registration</p>
|
||||
</div>
|
||||
<a href="/test-pages/authentication.html">Open →</a>
|
||||
</div>
|
||||
|
||||
<div class="page-item">
|
||||
<div>
|
||||
<h3>OS Management <span class="badge new">NEW</span></h3>
|
||||
<p>System updates, downgrades, and version management with progress simulation</p>
|
||||
</div>
|
||||
<a href="/test-pages/os-management.html">Open →</a>
|
||||
</div>
|
||||
|
||||
<div class="page-item">
|
||||
<div>
|
||||
<h3>Update Modal Testing <span class="badge new">NEW</span></h3>
|
||||
<p>Test various update scenarios including expired licenses, renewals, and auth requirements</p>
|
||||
</div>
|
||||
<a href="/test-pages/update-modal.html">Open →</a>
|
||||
</div>
|
||||
|
||||
<div class="page-item">
|
||||
<div>
|
||||
<h3>Component Mounting Test</h3>
|
||||
<p>Test single and multiple component mounting with shared Pinia store and dynamic creation</p>
|
||||
</div>
|
||||
<a href="/test-pages/component-mounting.html">Open →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Links -->
|
||||
<div class="category-section">
|
||||
<div class="category-header">
|
||||
🔗 Quick Component Tests
|
||||
</div>
|
||||
<div class="page-list">
|
||||
<div class="page-item">
|
||||
<div>
|
||||
<h3>Theme Testing</h3>
|
||||
<p>Light/dark mode switching and CSS variable management</p>
|
||||
</div>
|
||||
<a href="/test-pages/all-components.html#theme-switcher">Open →</a>
|
||||
</div>
|
||||
|
||||
<div class="page-item">
|
||||
<div>
|
||||
<h3>Modal System</h3>
|
||||
<p>Global modal management and event-driven popups</p>
|
||||
</div>
|
||||
<a href="/test-pages/all-components.html#modals">Open →</a>
|
||||
</div>
|
||||
|
||||
<div class="page-item">
|
||||
<div>
|
||||
<h3>API & Logs</h3>
|
||||
<p>API key management, log viewer, and debug tools</p>
|
||||
</div>
|
||||
<a href="/test-pages/all-components.html#api-developer">Open →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Testing Info -->
|
||||
<div style="background: white; padding: 20px; border-radius: 8px; margin-top: 30px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
|
||||
<h3 style="margin-top: 0;">🛠️ Testing Guidelines</h3>
|
||||
<ul style="color: #6b7280; line-height: 1.8;">
|
||||
<li>Each page loads components using the same mechanism as Unraid OS (manifest-based loading)</li>
|
||||
<li>jQuery is available for simulating PHP/backend interactions</li>
|
||||
<li>Components communicate via DOM attributes and custom events</li>
|
||||
<li>Hot module replacement (HMR) is enabled in dev mode for instant updates</li>
|
||||
<li>Use browser DevTools console to monitor component events and interactions</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Load the manifest and inject resources (mimics PHP extractor) -->
|
||||
<script src="/test-pages/language-switcher.js"></script>
|
||||
<script src="/test-pages/load-manifest.js"></script>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Add some interactivity to the index page
|
||||
$('.page-item').on('mouseenter', function() {
|
||||
$(this).find('a').css('transform', 'translateX(2px)');
|
||||
}).on('mouseleave', function() {
|
||||
$(this).find('a').css('transform', 'translateX(0)');
|
||||
});
|
||||
|
||||
// Set up smooth transitions
|
||||
$('a').css('transition', 'all 0.2s ease');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,355 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>OS Management - Unraid Component Test</title>
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<style>
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: #f3f4f6;
|
||||
}
|
||||
.header {
|
||||
background: #1f2937;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
.info-bar {
|
||||
background: white;
|
||||
padding: 15px 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
.version-info {
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
}
|
||||
.version-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.version-label {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
.version-value {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.main-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 25px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
.card h2 {
|
||||
margin: 0 0 20px 0;
|
||||
color: #1f2937;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.card-description {
|
||||
color: #6b7280;
|
||||
margin-bottom: 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.status-indicator {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.status-indicator.up-to-date {
|
||||
background: #d1fae5;
|
||||
color: #065f46;
|
||||
}
|
||||
.status-indicator.update-available {
|
||||
background: #fed7aa;
|
||||
color: #92400e;
|
||||
}
|
||||
.status-indicator.checking {
|
||||
background: #dbeafe;
|
||||
color: #1e40af;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.main-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header with OS Version -->
|
||||
<div class="header">
|
||||
<div class="container">
|
||||
<h1>
|
||||
💿 OS Management
|
||||
<unraid-header-os-version></unraid-header-os-version>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<!-- System Information Bar -->
|
||||
<div class="info-bar">
|
||||
<div class="version-info">
|
||||
<div class="version-item">
|
||||
<span class="version-label">Current Version</span>
|
||||
<span class="version-value" id="current-version">6.12.4</span>
|
||||
</div>
|
||||
<div class="version-item">
|
||||
<span class="version-label">Latest Available</span>
|
||||
<span class="version-value" id="latest-version">6.12.5</span>
|
||||
</div>
|
||||
<div class="version-item">
|
||||
<span class="version-label">Update Channel</span>
|
||||
<span class="version-value" id="update-channel">Stable</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-indicator update-available" id="update-status">
|
||||
<span>●</span> Update Available
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Grid -->
|
||||
<div class="main-grid">
|
||||
<!-- Update OS Card -->
|
||||
<div class="card">
|
||||
<h2>⬆️ System Updates</h2>
|
||||
<p class="card-description">Check for and install Unraid OS updates</p>
|
||||
<unraid-update-os></unraid-update-os>
|
||||
</div>
|
||||
|
||||
<!-- Downgrade OS Card -->
|
||||
<div class="card">
|
||||
<h2>⬇️ System Downgrade</h2>
|
||||
<p class="card-description">Rollback to a previous Unraid OS version</p>
|
||||
<unraid-downgrade-os></unraid-downgrade-os>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test Controls -->
|
||||
<div class="card" style="margin-top: 20px; background: #1f2937;">
|
||||
<h2 style="color: white;">🧪 Test Scenarios</h2>
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 10px;">
|
||||
<button class="test-btn" id="check-updates">Check for Updates</button>
|
||||
<button class="test-btn" id="simulate-update">Simulate Update Available</button>
|
||||
<button class="test-btn" id="simulate-current">Simulate Up-to-date</button>
|
||||
<button class="test-btn" id="change-channel">Switch Channel (Beta)</button>
|
||||
<button class="test-btn" id="simulate-download">Simulate Download Progress</button>
|
||||
<button class="test-btn" id="simulate-install">Simulate Installation</button>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 20px;">
|
||||
<div style="background: black; color: #10b981; padding: 15px; border-radius: 6px; font-family: monospace; font-size: 12px; min-height: 120px; max-height: 200px; overflow-y: auto;" id="console">
|
||||
> OS Management Test Console
|
||||
> Ready for testing...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.test-btn {
|
||||
padding: 10px 16px;
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.test-btn:hover {
|
||||
background: #2563eb;
|
||||
}
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
background: #e5e7eb;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
margin-top: 15px;
|
||||
}
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #3b82f6, #2563eb);
|
||||
transition: width 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Global Modals -->
|
||||
<unraid-modals></unraid-modals>
|
||||
|
||||
<!-- Load manifest -->
|
||||
<script src="/test-pages/language-switcher.js"></script>
|
||||
<script src="/test-pages/load-manifest.js"></script>
|
||||
|
||||
<!-- Test Logic -->
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
const $console = $('#console');
|
||||
let currentVersion = '6.12.4';
|
||||
let latestVersion = '6.12.5';
|
||||
let updateChannel = 'stable';
|
||||
|
||||
function log(message, type = 'info') {
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
const prefix = type === 'error' ? '❌' : type === 'success' ? '✅' : '>';
|
||||
$console.append('\n' + prefix + ' [' + timestamp + '] ' + message);
|
||||
$console.scrollTop($console[0].scrollHeight);
|
||||
}
|
||||
|
||||
// Check for updates
|
||||
$('#check-updates').on('click', function() {
|
||||
log('Checking for updates...');
|
||||
$('#update-status').removeClass('up-to-date update-available').addClass('checking');
|
||||
$('#update-status').html('<span>⟳</span> Checking...');
|
||||
|
||||
setTimeout(function() {
|
||||
if (currentVersion !== latestVersion) {
|
||||
$('#update-status').removeClass('checking up-to-date').addClass('update-available');
|
||||
$('#update-status').html('<span>●</span> Update Available');
|
||||
log('Update available: ' + latestVersion, 'success');
|
||||
} else {
|
||||
$('#update-status').removeClass('checking update-available').addClass('up-to-date');
|
||||
$('#update-status').html('<span>✓</span> Up to Date');
|
||||
log('System is up to date', 'success');
|
||||
}
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
// Simulate update available
|
||||
$('#simulate-update').on('click', function() {
|
||||
latestVersion = '6.12.6';
|
||||
$('#latest-version').text(latestVersion);
|
||||
$('#update-status').removeClass('up-to-date checking').addClass('update-available');
|
||||
$('#update-status').html('<span>●</span> Update Available');
|
||||
log('Simulated update available: ' + latestVersion);
|
||||
|
||||
// Trigger component update
|
||||
$('unraid-update-os').attr('latest-version', latestVersion);
|
||||
});
|
||||
|
||||
// Simulate up-to-date
|
||||
$('#simulate-current').on('click', function() {
|
||||
currentVersion = latestVersion = '6.12.5';
|
||||
$('#current-version').text(currentVersion);
|
||||
$('#latest-version').text(latestVersion);
|
||||
$('#update-status').removeClass('update-available checking').addClass('up-to-date');
|
||||
$('#update-status').html('<span>✓</span> Up to Date');
|
||||
log('System is now up to date');
|
||||
});
|
||||
|
||||
// Change channel
|
||||
$('#change-channel').on('click', function() {
|
||||
updateChannel = updateChannel === 'stable' ? 'beta' : 'stable';
|
||||
$('#update-channel').text(updateChannel.charAt(0).toUpperCase() + updateChannel.slice(1));
|
||||
log('Switched to ' + updateChannel + ' channel');
|
||||
|
||||
if (updateChannel === 'beta') {
|
||||
latestVersion = '6.13.0-beta1';
|
||||
$('#latest-version').text(latestVersion);
|
||||
log('Beta version available: ' + latestVersion);
|
||||
} else {
|
||||
latestVersion = '6.12.5';
|
||||
$('#latest-version').text(latestVersion);
|
||||
}
|
||||
});
|
||||
|
||||
// Simulate download progress
|
||||
$('#simulate-download').on('click', function() {
|
||||
log('Starting download simulation...');
|
||||
|
||||
const $card = $(this).closest('.card');
|
||||
if (!$card.find('.progress-bar').length) {
|
||||
$card.append('<div class="progress-bar"><div class="progress-fill" style="width: 0%">0%</div></div>');
|
||||
}
|
||||
|
||||
let progress = 0;
|
||||
const interval = setInterval(function() {
|
||||
progress += 10;
|
||||
$('.progress-fill').css('width', progress + '%').text(progress + '%');
|
||||
log('Download progress: ' + progress + '%');
|
||||
|
||||
if (progress >= 100) {
|
||||
clearInterval(interval);
|
||||
log('Download complete!', 'success');
|
||||
setTimeout(function() {
|
||||
$('.progress-bar').fadeOut();
|
||||
}, 2000);
|
||||
}
|
||||
}, 500);
|
||||
});
|
||||
|
||||
// Simulate installation
|
||||
$('#simulate-install').on('click', function() {
|
||||
log('Preparing installation...');
|
||||
log('Creating backup...');
|
||||
|
||||
setTimeout(function() {
|
||||
log('Backup complete', 'success');
|
||||
log('Installing update...');
|
||||
|
||||
setTimeout(function() {
|
||||
log('Installation complete!', 'success');
|
||||
log('System will restart in 30 seconds...');
|
||||
currentVersion = latestVersion;
|
||||
$('#current-version').text(currentVersion);
|
||||
}, 3000);
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
// Listen for component events
|
||||
$(document).on('unraid:update-started', function(e, data) {
|
||||
log('Update started: ' + data.version);
|
||||
});
|
||||
|
||||
$(document).on('unraid:update-complete', function(e, data) {
|
||||
log('Update complete: ' + data.version, 'success');
|
||||
});
|
||||
|
||||
log('OS Management test page initialized');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,226 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Settings - Unraid Component Test</title>
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<style>
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background: #f3f4f6;
|
||||
}
|
||||
.header {
|
||||
background: white;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
color: #1f2937;
|
||||
}
|
||||
.tabs {
|
||||
background: white;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
padding: 0 20px;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
.tab {
|
||||
padding: 12px 0;
|
||||
border-bottom: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
color: #6b7280;
|
||||
}
|
||||
.tab.active {
|
||||
border-bottom-color: #3b82f6;
|
||||
color: #3b82f6;
|
||||
}
|
||||
.main-content {
|
||||
padding: 20px;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.settings-section {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
.settings-section h2 {
|
||||
margin-top: 0;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
color: #1f2937;
|
||||
}
|
||||
.setting-item {
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid #f3f4f6;
|
||||
}
|
||||
.setting-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.setting-label {
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.setting-description {
|
||||
font-size: 14px;
|
||||
color: #6b7280;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Settings Container -->
|
||||
<div class="settings-container" style="max-width: 1200px; margin: 0 auto;">
|
||||
|
||||
<!-- Tabs -->
|
||||
<div class="tabs">
|
||||
<div class="tab active" data-tab="general">
|
||||
General
|
||||
</div>
|
||||
<div class="tab" data-tab="connect">
|
||||
Connect
|
||||
</div>
|
||||
<div class="tab" data-tab="registration">
|
||||
Registration
|
||||
</div>
|
||||
<div class="tab" data-tab="api">
|
||||
API Keys
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="main-content">
|
||||
<!-- General Settings -->
|
||||
<div class="tab-content" data-tab-content="general" style="display: block;">
|
||||
<div class="settings-section">
|
||||
<h2>Appearance</h2>
|
||||
<div class="setting-item">
|
||||
<div class="setting-label">Theme</div>
|
||||
<div class="setting-description">Choose between light and dark theme</div>
|
||||
<unraid-theme-switcher current="white"></unraid-theme-switcher>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-section">
|
||||
<h2>System Information</h2>
|
||||
<div class="setting-item">
|
||||
<div class="setting-label">OS Version</div>
|
||||
<unraid-header-os-version></unraid-header-os-version>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Connect Settings -->
|
||||
<div class="tab-content" data-tab-content="connect" style="display: none;">
|
||||
<div class="settings-section">
|
||||
<h2>Unraid Connect Settings</h2>
|
||||
<unraid-connect-settings></unraid-connect-settings>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Registration -->
|
||||
<div class="tab-content" data-tab-content="registration" style="display: none;">
|
||||
<div class="settings-section">
|
||||
<h2>System Registration</h2>
|
||||
<unraid-registration></unraid-registration>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- API Keys -->
|
||||
<div class="tab-content" data-tab-content="api" style="display: none;">
|
||||
<div class="settings-section">
|
||||
<h2>API Key Management</h2>
|
||||
<unraid-api-key-manager></unraid-api-key-manager>
|
||||
</div>
|
||||
|
||||
<div class="settings-section">
|
||||
<h2>API Key Authorization</h2>
|
||||
<unraid-api-key-authorize></unraid-api-key-authorize>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- End settings-container -->
|
||||
|
||||
<!-- Global Modals -->
|
||||
<unraid-modals></unraid-modals>
|
||||
|
||||
<!-- Load the manifest and inject resources (mimics PHP extractor) -->
|
||||
<script src="/test-pages/language-switcher.js"></script>
|
||||
<script src="/test-pages/load-manifest.js"></script>
|
||||
<script src="/test-pages/test-server-state.js"></script>
|
||||
<script src="/test-pages/shared-header.js"></script>
|
||||
|
||||
<!-- jQuery tab functionality and component interaction -->
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Tab switching functionality
|
||||
$('.tab').on('click', function() {
|
||||
var tabName = $(this).data('tab');
|
||||
|
||||
// Update active tab styling
|
||||
$('.tab').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
|
||||
// Show/hide content
|
||||
$('.tab-content').hide();
|
||||
$('.tab-content[data-tab-content="' + tabName + '"]').fadeIn();
|
||||
});
|
||||
|
||||
// Example: Programmatically update settings from jQuery
|
||||
// This mimics how Unraid might update settings after saving
|
||||
window.updateConnectSettings = function(settings) {
|
||||
// You could pass this to the Vue component
|
||||
console.log('Updating connect settings:', settings);
|
||||
// The component would listen for attribute changes
|
||||
$('unraid-connect-settings').attr('settings', JSON.stringify(settings));
|
||||
};
|
||||
|
||||
// Example: Listen for events from Vue components
|
||||
$(document).on('unraid:settings-saved', function(e, data) {
|
||||
console.log('Settings saved:', data);
|
||||
// Show a jQuery notification or update the UI
|
||||
showSuccessMessage('Settings saved successfully!');
|
||||
});
|
||||
|
||||
// Simple success message function (like Unraid's)
|
||||
function showSuccessMessage(message) {
|
||||
var $msg = $('<div class="success-message" style="position: fixed; top: 20px; right: 20px; background: #10b981; color: white; padding: 12px 20px; border-radius: 4px; z-index: 9999;">' + message + '</div>');
|
||||
$('body').append($msg);
|
||||
setTimeout(function() {
|
||||
$msg.fadeOut(function() {
|
||||
$(this).remove();
|
||||
});
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// Example: Load settings via AJAX and update components
|
||||
function loadSettings() {
|
||||
// Simulate AJAX call
|
||||
setTimeout(function() {
|
||||
var settings = {
|
||||
connect: { enabled: true, url: 'https://connect.unraid.net' },
|
||||
theme: 'dark',
|
||||
registration: { key: 'XXXX-XXXX-XXXX' }
|
||||
};
|
||||
|
||||
// Update components with loaded data
|
||||
$('unraid-connect-settings').attr('initial-settings', JSON.stringify(settings.connect));
|
||||
$('unraid-registration').attr('registration-data', JSON.stringify(settings.registration));
|
||||
|
||||
console.log('Settings loaded via jQuery');
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// Load settings on page load
|
||||
loadSettings();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,63 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Update Modal Test - Unraid Component Test</title>
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<style>
|
||||
html {
|
||||
font-size: 10px;
|
||||
}
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background: #f3f4f6;
|
||||
}
|
||||
.header {
|
||||
background: #1f2937;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
}
|
||||
.back-link {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
margin-bottom: 10px;
|
||||
display: inline-block;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.back-link:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div>
|
||||
<a href="/test-pages/" class="back-link">← Back to Test Pages</a>
|
||||
<h1>🧪 Update Modal Test Scenarios</h1>
|
||||
</div>
|
||||
<div>
|
||||
<unraid-test-theme-switcher></unraid-test-theme-switcher>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mount the test component -->
|
||||
<unraid-test-update-modal></unraid-test-update-modal>
|
||||
|
||||
<!-- Mount the modals component which includes the changelog modal -->
|
||||
<unraid-modals></unraid-modals>
|
||||
|
||||
<!-- Load the manifest and inject resources -->
|
||||
<script src="/test-pages/language-switcher.js"></script>
|
||||
<script src="/test-pages/load-manifest.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
57
web/scripts/build-test-pages.js
Normal file
57
web/scripts/build-test-pages.js
Normal file
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env node
|
||||
import { mkdir, writeFile } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { glob } from 'glob';
|
||||
import nunjucks from 'nunjucks';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const rootDir = path.join(__dirname, '..');
|
||||
const templatesDir = path.join(rootDir, 'test-pages');
|
||||
const pagesDir = path.join(templatesDir, 'pages');
|
||||
const outputDir = path.join(rootDir, 'public', 'test-pages');
|
||||
|
||||
const env = nunjucks.configure(templatesDir, {
|
||||
autoescape: false,
|
||||
noCache: true,
|
||||
throwOnUndefined: false,
|
||||
});
|
||||
|
||||
async function ensureDir(dirPath) {
|
||||
await mkdir(dirPath, { recursive: true });
|
||||
}
|
||||
|
||||
async function renderTemplates() {
|
||||
const templateFiles = await glob('**/*.njk', { cwd: pagesDir, nodir: true });
|
||||
|
||||
if (templateFiles.length === 0) {
|
||||
console.log('No test page templates found.');
|
||||
return;
|
||||
}
|
||||
|
||||
await ensureDir(outputDir);
|
||||
|
||||
const mode = process.env.NODE_ENV ?? 'development';
|
||||
|
||||
let renderedCount = 0;
|
||||
for (const relativePath of templateFiles) {
|
||||
const templateName = `pages/${relativePath}`.replace(/\\/g, '/');
|
||||
const htmlOutput = env.render(templateName, { mode });
|
||||
const targetPath = path.join(outputDir, relativePath).replace(/\.njk$/, '.html');
|
||||
|
||||
await ensureDir(path.dirname(targetPath));
|
||||
await writeFile(targetPath, htmlOutput, 'utf-8');
|
||||
renderedCount += 1;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Rendered ${renderedCount} test page template${renderedCount === 1 ? '' : 's'} to ${outputDir}`
|
||||
);
|
||||
}
|
||||
|
||||
renderTemplates().catch((error) => {
|
||||
console.error('Failed to render test page templates:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -38,13 +38,21 @@ function expandJsonFormsKey(key) {
|
||||
return expanded;
|
||||
}
|
||||
|
||||
// Keep any explicitly referenced error keys as-is
|
||||
if (key.includes('.error.')) {
|
||||
expanded.add(key);
|
||||
return expanded;
|
||||
}
|
||||
|
||||
// Don't add .label to keys that already have specific suffixes
|
||||
if (key.endsWith('.title') || key.endsWith('.description')) {
|
||||
expanded.add(key);
|
||||
return expanded;
|
||||
}
|
||||
|
||||
expanded.add(key.endsWith('.label') ? key : `${key}.label`);
|
||||
const baseKey = key.endsWith('.label') ? key.slice(0, -'.label'.length) : key;
|
||||
expanded.add(`${baseKey}.label`);
|
||||
expanded.add(`${baseKey}.error.custom`);
|
||||
|
||||
return expanded;
|
||||
}
|
||||
|
||||
150
web/src/components/DevThemeSwitcher.standalone.vue
Normal file
150
web/src/components/DevThemeSwitcher.standalone.vue
Normal file
@@ -0,0 +1,150 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
import { useThemeStore } from '~/store/theme';
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
const themeOptions = [
|
||||
{ value: 'white', label: 'White' },
|
||||
{ value: 'black', label: 'Black' },
|
||||
{ value: 'gray', label: 'Gray' },
|
||||
{ value: 'azure', label: 'Azure' },
|
||||
] as const;
|
||||
|
||||
const STORAGE_KEY_THEME = 'unraid:test:theme';
|
||||
|
||||
const { theme } = storeToRefs(themeStore);
|
||||
|
||||
const currentTheme = ref<string>(theme.value.name);
|
||||
|
||||
const getCurrentTheme = (): string => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const urlTheme = urlParams.get('theme');
|
||||
|
||||
if (urlTheme && themeOptions.some((t) => t.value === urlTheme)) {
|
||||
return urlTheme;
|
||||
}
|
||||
|
||||
if (theme.value?.name) {
|
||||
return theme.value.name;
|
||||
}
|
||||
|
||||
try {
|
||||
return window.localStorage?.getItem(STORAGE_KEY_THEME) || 'white';
|
||||
} catch {
|
||||
return 'white';
|
||||
}
|
||||
};
|
||||
|
||||
const updateTheme = (themeName: string, skipUrlUpdate = false) => {
|
||||
if (!skipUrlUpdate) {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set('theme', themeName);
|
||||
window.history.replaceState({}, '', url);
|
||||
}
|
||||
|
||||
try {
|
||||
window.localStorage?.setItem(STORAGE_KEY_THEME, themeName);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
themeStore.setTheme({ name: themeName }, true);
|
||||
themeStore.setCssVars();
|
||||
|
||||
const linkId = 'dev-theme-css-link';
|
||||
let themeLink = document.getElementById(linkId) as HTMLLinkElement | null;
|
||||
|
||||
const themeCssMap: Record<string, string> = {
|
||||
azure: '/test-pages/unraid-assets/themes/azure.css',
|
||||
black: '/test-pages/unraid-assets/themes/black.css',
|
||||
gray: '/test-pages/unraid-assets/themes/gray.css',
|
||||
white: '/test-pages/unraid-assets/themes/white.css',
|
||||
};
|
||||
|
||||
const cssUrl = themeCssMap[themeName];
|
||||
|
||||
if (cssUrl) {
|
||||
if (!themeLink) {
|
||||
themeLink = document.createElement('link');
|
||||
themeLink.id = linkId;
|
||||
themeLink.rel = 'stylesheet';
|
||||
document.head.appendChild(themeLink);
|
||||
}
|
||||
themeLink.href = cssUrl;
|
||||
} else {
|
||||
if (themeLink) {
|
||||
themeLink.remove();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleThemeChange = (event: Event) => {
|
||||
const newTheme = (event.target as HTMLSelectElement).value;
|
||||
if (newTheme === currentTheme.value) {
|
||||
return;
|
||||
}
|
||||
currentTheme.value = newTheme;
|
||||
updateTheme(newTheme);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
themeStore.setDevOverride(true);
|
||||
|
||||
const initialTheme = getCurrentTheme();
|
||||
currentTheme.value = initialTheme;
|
||||
|
||||
const existingLink = document.getElementById('dev-theme-css-link') as HTMLLinkElement | null;
|
||||
if (!existingLink || !existingLink.href) {
|
||||
updateTheme(initialTheme, true);
|
||||
} else {
|
||||
themeStore.setTheme({ name: initialTheme }, true);
|
||||
themeStore.setCssVars();
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => theme.value.name,
|
||||
(newName) => {
|
||||
if (newName && newName !== currentTheme.value) {
|
||||
currentTheme.value = newName;
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set('theme', newName);
|
||||
window.history.replaceState({}, '', url);
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<select :value="currentTheme" class="dev-theme-select" @change="handleThemeChange">
|
||||
<option v-for="option in themeOptions" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</select>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.dev-theme-select {
|
||||
padding: 8px 10px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(148, 163, 184, 0.4);
|
||||
background: rgba(15, 23, 42, 0.9);
|
||||
color: #f9fafb;
|
||||
font-size: 13px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.dev-theme-select:hover {
|
||||
border-color: rgba(148, 163, 184, 0.6);
|
||||
}
|
||||
|
||||
.dev-theme-select:focus {
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.3);
|
||||
}
|
||||
</style>
|
||||
@@ -146,6 +146,11 @@ export const componentMappings: ComponentMapping[] = [
|
||||
selector: 'unraid-test-theme-switcher',
|
||||
appId: 'test-theme-switcher',
|
||||
},
|
||||
{
|
||||
component: defineAsyncComponent(() => import('../DevThemeSwitcher.standalone.vue')),
|
||||
selector: 'unraid-dev-theme-switcher',
|
||||
appId: 'dev-theme-switcher',
|
||||
},
|
||||
{
|
||||
component: defineAsyncComponent(() => import('../ApiStatus/ApiStatus.standalone.vue')),
|
||||
selector: 'unraid-api-status-manager',
|
||||
|
||||
@@ -75,125 +75,167 @@
|
||||
"headerOsVersion.viewOsReleaseNotes": "View OS Release Notes",
|
||||
"headerOsVersion.visitPartnerWebsite": "Visit Partner website",
|
||||
"headerOsVersion.visitUnraidWebsite": "Visit Unraid website",
|
||||
"jsonforms.apiKey.customPermissions.actions.error.custom": "Select at least one action for every custom permission entry.",
|
||||
"jsonforms.apiKey.customPermissions.actions.label": "Actions",
|
||||
"jsonforms.apiKey.customPermissions.actions.title": "Actions",
|
||||
"jsonforms.apiKey.customPermissions.description": "Configure specific permissions",
|
||||
"jsonforms.apiKey.customPermissions.error.custom": "Resolve the errors in Custom Permissions before continuing.",
|
||||
"jsonforms.apiKey.customPermissions.label": "Permissions",
|
||||
"jsonforms.apiKey.customPermissions.resources.error.custom": "Select at least one resource for every custom permission entry.",
|
||||
"jsonforms.apiKey.customPermissions.resources.label": "Resources",
|
||||
"jsonforms.apiKey.customPermissions.resources.title": "Resources",
|
||||
"jsonforms.apiKey.customPermissions.title": "Custom Permissions",
|
||||
"jsonforms.apiKey.description": "API Key Description",
|
||||
"jsonforms.apiKey.description.title": "Description",
|
||||
"jsonforms.apiKey.name.description": "A descriptive name for this API key",
|
||||
"jsonforms.apiKey.name.error.custom": "Enter an API key name between 1 and 100 characters.",
|
||||
"jsonforms.apiKey.name.label": "API Key Name",
|
||||
"jsonforms.apiKey.name.title": "API Key Name",
|
||||
"jsonforms.apiKey.permissionPresets.description": "Quick add common permission sets",
|
||||
"jsonforms.apiKey.permissionPresets.error.custom": "Choose a valid preset or keep \"None\" selected.",
|
||||
"jsonforms.apiKey.permissionPresets.label": "Add Permission Preset",
|
||||
"jsonforms.apiKey.permissionPresets.title": "Permission Presets",
|
||||
"jsonforms.apiKey.permissions.description": "Configure API key permissions",
|
||||
"jsonforms.apiKey.permissions.description.label": "Select any combination of roles, permission groups, and custom permissions to define what this API key can access.",
|
||||
"jsonforms.apiKey.permissions.header.error.custom": "Resolve the errors in the Permissions section.",
|
||||
"jsonforms.apiKey.permissions.header.label": "Permissions Configuration",
|
||||
"jsonforms.apiKey.permissions.help.error.custom": "Follow the permissions guidance and complete every required field.",
|
||||
"jsonforms.apiKey.permissions.help.label": "Use the preset dropdown for common permission sets, or manually add custom permissions. You can select multiple resources that share the same actions.",
|
||||
"jsonforms.apiKey.permissions.subheader.error.custom": "Fix the invalid fields within the Permissions section.",
|
||||
"jsonforms.apiKey.permissions.subheader.label": "Permissions",
|
||||
"jsonforms.apiKey.roles.description": "Select one or more roles to grant pre-defined permission sets",
|
||||
"jsonforms.apiKey.roles.error.custom": "Select at least one valid role or clear the field.",
|
||||
"jsonforms.apiKey.roles.label": "Roles",
|
||||
"jsonforms.apiKey.roles.title": "Roles",
|
||||
"jsonforms.apiSettings.sandbox.error.custom": "Choose whether the developer sandbox should be enabled.",
|
||||
"jsonforms.apiSettings.sandbox.label": "Enable Developer Sandbox",
|
||||
"jsonforms.apiSettings.sandbox.title": "Enable Developer Sandbox",
|
||||
"jsonforms.oidc.accordion.advancedEndpoints.description": "Override auto-discovery settings (optional)",
|
||||
"jsonforms.oidc.accordion.advancedEndpoints.error.custom": "Review the Advanced Endpoints section and correct the invalid fields.",
|
||||
"jsonforms.oidc.accordion.advancedEndpoints.label": "Advanced Endpoints",
|
||||
"jsonforms.oidc.accordion.advancedEndpoints.title": "Advanced Endpoints",
|
||||
"jsonforms.oidc.accordion.authorizationRules.description": "Configure who can access your server",
|
||||
"jsonforms.oidc.accordion.authorizationRules.error.custom": "Review the Authorization Rules section and correct the invalid fields.",
|
||||
"jsonforms.oidc.accordion.authorizationRules.label": "Authorization Rules",
|
||||
"jsonforms.oidc.accordion.authorizationRules.title": "Authorization Rules",
|
||||
"jsonforms.oidc.accordion.basicConfiguration.description": "Essential provider settings",
|
||||
"jsonforms.oidc.accordion.basicConfiguration.error.custom": "Review the Basic Configuration section and correct the invalid fields.",
|
||||
"jsonforms.oidc.accordion.basicConfiguration.label": "Basic Configuration",
|
||||
"jsonforms.oidc.accordion.basicConfiguration.title": "Basic Configuration",
|
||||
"jsonforms.oidc.accordion.buttonCustomization.description": "Customize the appearance of the login button",
|
||||
"jsonforms.oidc.accordion.buttonCustomization.error.custom": "Review the Button Customization section and correct the invalid fields.",
|
||||
"jsonforms.oidc.accordion.buttonCustomization.label": "Button Customization",
|
||||
"jsonforms.oidc.accordion.buttonCustomization.title": "Button Customization",
|
||||
"jsonforms.oidc.buttons.description": "Customize the appearance of the login button",
|
||||
"jsonforms.oidc.buttons.icon.description": "URL or base64 encoded icon for the login button",
|
||||
"jsonforms.oidc.buttons.icon.error.custom": "Provide a valid icon URL or data URI.",
|
||||
"jsonforms.oidc.buttons.icon.label": "Button Icon URL",
|
||||
"jsonforms.oidc.buttons.icon.title": "Button Icon",
|
||||
"jsonforms.oidc.buttons.style.description": "Custom inline CSS styles for the button (e.g., \"background: linear-gradient(to right, #4f46e5, #7c3aed); border-radius: 9999px;\")",
|
||||
"jsonforms.oidc.buttons.style.error.custom": "Enter valid CSS for the button style or leave it blank.",
|
||||
"jsonforms.oidc.buttons.style.label": "Custom CSS Styles",
|
||||
"jsonforms.oidc.buttons.style.title": "Button Style",
|
||||
"jsonforms.oidc.buttons.text.description": "Custom text for the login button",
|
||||
"jsonforms.oidc.buttons.text.error.custom": "Enter the button text you want to display.",
|
||||
"jsonforms.oidc.buttons.text.label": "Button Text",
|
||||
"jsonforms.oidc.buttons.text.title": "Button Text",
|
||||
"jsonforms.oidc.buttons.title": "Button Customization",
|
||||
"jsonforms.oidc.buttons.variant.description": "Visual style of the login button",
|
||||
"jsonforms.oidc.buttons.variant.error.custom": "Select one of the supported button styles.",
|
||||
"jsonforms.oidc.buttons.variant.label": "Button Style",
|
||||
"jsonforms.oidc.buttons.variant.title": "Button Style",
|
||||
"jsonforms.oidc.provider.authorizationEndpoint.description": "Optional - will be auto-discovered if not provided",
|
||||
"jsonforms.oidc.provider.authorizationEndpoint.error.custom": "Enter a valid authorization endpoint URL.",
|
||||
"jsonforms.oidc.provider.authorizationEndpoint.label": "Authorization Endpoint",
|
||||
"jsonforms.oidc.provider.authorizationEndpoint.title": "Authorization Endpoint",
|
||||
"jsonforms.oidc.provider.clientId.description": "OAuth2 client ID registered with the provider",
|
||||
"jsonforms.oidc.provider.clientId.error.custom": "Enter the OAuth client ID issued by your provider.",
|
||||
"jsonforms.oidc.provider.clientId.label": "OAuth Client ID",
|
||||
"jsonforms.oidc.provider.clientId.title": "OAuth Client ID",
|
||||
"jsonforms.oidc.provider.clientSecret.description": "OAuth2 client secret (if required)",
|
||||
"jsonforms.oidc.provider.clientSecret.error.custom": "Provide the OAuth client secret issued by your provider.",
|
||||
"jsonforms.oidc.provider.clientSecret.label": "OAuth Client Secret",
|
||||
"jsonforms.oidc.provider.clientSecret.title": "OAuth Client Secret",
|
||||
"jsonforms.oidc.provider.discoveryToggle.error.custom": "Choose whether to use automatic discovery.",
|
||||
"jsonforms.oidc.provider.discoveryToggle.label": "Use Automatic Discovery",
|
||||
"jsonforms.oidc.provider.id.description": "Unique identifier for the provider",
|
||||
"jsonforms.oidc.provider.id.error.custom": "Enter a unique provider ID (for example, \"google\").",
|
||||
"jsonforms.oidc.provider.id.label": "Provider ID",
|
||||
"jsonforms.oidc.provider.id.title": "Provider ID",
|
||||
"jsonforms.oidc.provider.issuer.description": "OIDC issuer URL (e.g., https://accounts.google.com). Cannot contain /.well-known/ paths - use the base issuer URL instead of the full discovery endpoint. Must not end with a trailing slash.",
|
||||
"jsonforms.oidc.provider.issuer.error.custom": "Enter the issuer URL (for example, https://accounts.example.com).",
|
||||
"jsonforms.oidc.provider.issuer.label": "Issuer URL",
|
||||
"jsonforms.oidc.provider.issuer.title": "Issuer URL",
|
||||
"jsonforms.oidc.provider.jwksUri.description": "Optional - will be auto-discovered if not provided",
|
||||
"jsonforms.oidc.provider.jwksUri.error.custom": "Provide a valid JWKS URI or rely on discovery.",
|
||||
"jsonforms.oidc.provider.jwksUri.label": "JWKS URI",
|
||||
"jsonforms.oidc.provider.jwksUri.title": "JWKS URI",
|
||||
"jsonforms.oidc.provider.name.description": "Display name for the provider",
|
||||
"jsonforms.oidc.provider.name.error.custom": "Enter the provider name shown to users.",
|
||||
"jsonforms.oidc.provider.name.label": "Provider Name",
|
||||
"jsonforms.oidc.provider.name.title": "Provider Name",
|
||||
"jsonforms.oidc.provider.scopes.description": "OAuth2 scopes to request",
|
||||
"jsonforms.oidc.provider.scopes.error.custom": "Specify at least one scope requested from the provider.",
|
||||
"jsonforms.oidc.provider.scopes.label": "OAuth Scopes",
|
||||
"jsonforms.oidc.provider.scopes.title": "OAuth Scopes",
|
||||
"jsonforms.oidc.provider.tokenEndpoint.description": "Optional - will be auto-discovered if not provided",
|
||||
"jsonforms.oidc.provider.tokenEndpoint.error.custom": "Enter a valid token endpoint URL.",
|
||||
"jsonforms.oidc.provider.tokenEndpoint.label": "Token Endpoint",
|
||||
"jsonforms.oidc.provider.tokenEndpoint.title": "Token Endpoint",
|
||||
"jsonforms.oidc.provider.unraidNet.description": "This is the built-in Unraid.net provider. Only authorization rules can be modified.",
|
||||
"jsonforms.oidc.provider.unraidNet.error.custom": "The Unraid.net provider has managed fields; only supported settings may be edited.",
|
||||
"jsonforms.oidc.provider.unraidNet.label": "Unraid.net Provider",
|
||||
"jsonforms.oidc.provider.unraidNet.title": "Unraid.net Provider",
|
||||
"jsonforms.oidc.provider.userInfoEndpoint.error.custom": "Enter a valid UserInfo endpoint URL.",
|
||||
"jsonforms.oidc.provider.userInfoEndpoint.label": "User Info Endpoint",
|
||||
"jsonforms.oidc.restrictions.allowedDomains.description": "Email domains that are allowed to login (e.g., company.com)",
|
||||
"jsonforms.oidc.restrictions.allowedDomains.error.custom": "List fully qualified domains (one per line) for allowed users.",
|
||||
"jsonforms.oidc.restrictions.allowedDomains.label": "Allowed Email Domains",
|
||||
"jsonforms.oidc.restrictions.allowedDomains.title": "Allowed Email Domains",
|
||||
"jsonforms.oidc.restrictions.allowedEmails.description": "Specific email addresses that are allowed to login",
|
||||
"jsonforms.oidc.restrictions.allowedEmails.error.custom": "List the specific email addresses that should be allowed.",
|
||||
"jsonforms.oidc.restrictions.allowedEmails.label": "Specific Email Addresses",
|
||||
"jsonforms.oidc.restrictions.allowedEmails.title": "Allowed Emails",
|
||||
"jsonforms.oidc.restrictions.allowedUserIds.description": "Specific user IDs (sub claim) that are allowed to login",
|
||||
"jsonforms.oidc.restrictions.allowedUserIds.error.custom": "List the user IDs (sub claims) that should be allowed.",
|
||||
"jsonforms.oidc.restrictions.allowedUserIds.label": "Allowed User IDs",
|
||||
"jsonforms.oidc.restrictions.allowedUserIds.title": "Allowed User IDs",
|
||||
"jsonforms.oidc.restrictions.help.error.custom": "Review the Simple Authorization lists; each entry must be valid.",
|
||||
"jsonforms.oidc.restrictions.help.label": "Configure simple allow lists for who can sign in.",
|
||||
"jsonforms.oidc.restrictions.title": "Simple Authorization",
|
||||
"jsonforms.oidc.restrictions.title.label": "Simple Authorization",
|
||||
"jsonforms.oidc.restrictions.workspaceDomain.description": "Restrict to users from a specific Google Workspace domain",
|
||||
"jsonforms.oidc.restrictions.workspaceDomain.error.custom": "Enter a valid Google Workspace domain such as example.com.",
|
||||
"jsonforms.oidc.restrictions.workspaceDomain.label": "Google Workspace Domain",
|
||||
"jsonforms.oidc.restrictions.workspaceDomain.title": "Google Workspace Domain",
|
||||
"jsonforms.oidc.rules.claim.description": "JWT claim to check",
|
||||
"jsonforms.oidc.rules.claim.error.custom": "Select the JWT claim to evaluate.",
|
||||
"jsonforms.oidc.rules.claim.label": "JWT Claim",
|
||||
"jsonforms.oidc.rules.claim.title": "JWT Claim",
|
||||
"jsonforms.oidc.rules.collection.description": "Define authorization rules based on claims in the ID token. Rule mode can be configured: OR logic (any rule matches) or AND logic (all rules must match).",
|
||||
"jsonforms.oidc.rules.collection.error.custom": "Ensure every authorization rule entry is complete.",
|
||||
"jsonforms.oidc.rules.collection.label": "Claim Rules",
|
||||
"jsonforms.oidc.rules.collection.title": "Claim Rules",
|
||||
"jsonforms.oidc.rules.description": "Configure advanced authorization rules for fine-grained access control",
|
||||
"jsonforms.oidc.rules.mode.description": "How to evaluate multiple rules: OR (any rule passes) or AND (all rules must pass)",
|
||||
"jsonforms.oidc.rules.mode.error.custom": "Choose how multiple rules should be evaluated (AND or OR).",
|
||||
"jsonforms.oidc.rules.mode.label": "Rule Mode",
|
||||
"jsonforms.oidc.rules.mode.title": "Rule Mode",
|
||||
"jsonforms.oidc.rules.operator.error.custom": "Select a comparison operator.",
|
||||
"jsonforms.oidc.rules.operator.label": "Operator",
|
||||
"jsonforms.oidc.rules.operator.title": "Operator",
|
||||
"jsonforms.oidc.rules.title": "Advanced Authorization Rules",
|
||||
"jsonforms.oidc.rules.title.label": "Advanced Authorization Rules",
|
||||
"jsonforms.oidc.rules.value.description": "Values to match against",
|
||||
"jsonforms.oidc.rules.value.error.custom": "Provide at least one value for this rule.",
|
||||
"jsonforms.oidc.rules.value.label": "Values",
|
||||
"jsonforms.oidc.rules.value.title": "Values",
|
||||
"jsonforms.sso.defaultAllowedOrigins.description": "Additional trusted redirect origins to allow redirects from custom ports, reverse proxies, Tailscale, etc.",
|
||||
"jsonforms.sso.defaultAllowedOrigins.error.custom": "Enter valid origins (protocol + host) separated by commas or new lines.",
|
||||
"jsonforms.sso.defaultAllowedOrigins.label": "Default Allowed Redirect Origins",
|
||||
"jsonforms.sso.defaultAllowedOrigins.title": "Default Allowed Redirect Origins",
|
||||
"jsonforms.sso.providers.description": "Configure OpenID Connect providers for SSO authentication",
|
||||
"jsonforms.sso.providers.error.custom": "Each OIDC provider entry must be valid—resolve the highlighted fields.",
|
||||
"jsonforms.sso.providers.label": "OIDC Providers",
|
||||
"jsonforms.sso.providers.title": "OIDC Providers",
|
||||
"logs.customFilterLabel": "Custom {label}",
|
||||
|
||||
@@ -54,6 +54,7 @@ export const useThemeStore = defineStore('theme', () => {
|
||||
|
||||
const activeColorVariables = ref<ThemeVariables>(defaultColors.white);
|
||||
const hasServerTheme = ref(false);
|
||||
const devOverride = ref(false);
|
||||
|
||||
const { result, onResult, onError } = useQuery<GetThemeQuery>(GET_THEME_QUERY, null, {
|
||||
fetchPolicy: 'cache-and-network',
|
||||
@@ -107,9 +108,9 @@ export const useThemeStore = defineStore('theme', () => {
|
||||
});
|
||||
|
||||
// Actions
|
||||
const setTheme = (data?: Partial<Theme>) => {
|
||||
const setTheme = (data?: Partial<Theme>, force = false) => {
|
||||
if (data) {
|
||||
if (hasServerTheme.value) {
|
||||
if (hasServerTheme.value && !force && !devOverride.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -120,6 +121,10 @@ export const useThemeStore = defineStore('theme', () => {
|
||||
}
|
||||
};
|
||||
|
||||
const setDevOverride = (enabled: boolean) => {
|
||||
devOverride.value = enabled;
|
||||
};
|
||||
|
||||
const setCssVars = () => {
|
||||
const selectedTheme = theme.value.name;
|
||||
|
||||
@@ -238,5 +243,6 @@ export const useThemeStore = defineStore('theme', () => {
|
||||
// actions
|
||||
setTheme,
|
||||
setCssVars,
|
||||
setDevOverride,
|
||||
};
|
||||
});
|
||||
|
||||
18
web/test-pages/layouts/base.njk
Normal file
18
web/test-pages/layouts/base.njk
Normal file
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}Unraid Component Test{% endblock %}</title>
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
{% include "partials/styles.njk" %}
|
||||
{% block pageStyles %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
{% block content %}{% endblock %}
|
||||
|
||||
{% include "partials/scripts.njk" %}
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
80
web/test-pages/pages/api-developer.njk
Normal file
80
web/test-pages/pages/api-developer.njk
Normal file
@@ -0,0 +1,80 @@
|
||||
{% extends "layouts/base.njk" %}
|
||||
|
||||
{% block title %}API & Developer Tools - Unraid Component Test{% endblock %}
|
||||
|
||||
{% block pageStyles %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container padded">
|
||||
<div class="breadcrumb">
|
||||
<a href="/test-pages/">Test Pages</a> / API & Developer Tools
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>API Key Management</h2>
|
||||
<p class="card-description">Manage API keys for programmatic access to your Unraid server</p>
|
||||
<div class="component-mount" data-component="unraid-api-key-manager">
|
||||
<unraid-api-key-manager></unraid-api-key-manager>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>API Key Authorization</h2>
|
||||
<p class="card-description">Authorize API key requests from external applications</p>
|
||||
<div class="component-mount" data-component="unraid-api-key-authorize">
|
||||
<unraid-api-key-authorize></unraid-api-key-authorize>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>API Logs</h2>
|
||||
<p class="card-description">Download and view API access logs</p>
|
||||
<div class="component-mount" data-component="unraid-download-api-logs">
|
||||
<unraid-download-api-logs></unraid-download-api-logs>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Log Viewer</h2>
|
||||
<p class="card-description">View and search through system logs</p>
|
||||
<div class="component-mount" data-component="unraid-log-viewer">
|
||||
<unraid-log-viewer></unraid-log-viewer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<unraid-modals></unraid-modals>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
console.log('API & Developer Tools page loaded');
|
||||
|
||||
$(document).on('unraid:api-key-created', function(e, data) {
|
||||
console.log('API key created:', data);
|
||||
var event = new CustomEvent('unraid:notification', {
|
||||
detail: {
|
||||
title: 'API Key Created',
|
||||
message: 'A new API key has been generated',
|
||||
type: 'success'
|
||||
}
|
||||
});
|
||||
document.dispatchEvent(event);
|
||||
});
|
||||
|
||||
$(document).on('unraid:api-key-deleted', function(e, data) {
|
||||
console.log('API key deleted:', data);
|
||||
var event = new CustomEvent('unraid:notification', {
|
||||
detail: {
|
||||
title: 'API Key Deleted',
|
||||
message: 'The API key has been removed',
|
||||
type: 'info'
|
||||
}
|
||||
});
|
||||
document.dispatchEvent(event);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
143
web/test-pages/pages/authentication.njk
Normal file
143
web/test-pages/pages/authentication.njk
Normal file
@@ -0,0 +1,143 @@
|
||||
{% extends "layouts/base.njk" %}
|
||||
|
||||
{% block title %}Authentication Flow - Unraid Component Test{% endblock %}
|
||||
|
||||
{% block pageStyles %}
|
||||
<style>
|
||||
body {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="auth-container">
|
||||
<div class="auth-card">
|
||||
<h2>🔐 Authentication</h2>
|
||||
<unraid-auth></unraid-auth>
|
||||
<div class="user-info">
|
||||
<strong>Session Status:</strong>
|
||||
<div id="auth-status">
|
||||
<span class="status-badge unauthenticated">Not Authenticated</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="auth-card">
|
||||
<h2>🔗 Single Sign-On</h2>
|
||||
<p style="color: #6b7280; margin-bottom: 20px;">Alternative authentication methods</p>
|
||||
<unraid-sso-button></unraid-sso-button>
|
||||
</div>
|
||||
|
||||
<div class="auth-card">
|
||||
<h2>👤 User Profile</h2>
|
||||
<p style="color: #6b7280; margin-bottom: 20px;">Displays when authenticated</p>
|
||||
<unraid-user-profile id="user-profile"></unraid-user-profile>
|
||||
</div>
|
||||
|
||||
<div class="auth-card">
|
||||
<h2>📝 System Registration</h2>
|
||||
<unraid-registration></unraid-registration>
|
||||
</div>
|
||||
|
||||
<div class="auth-card" style="background: #1f2937; color: white;">
|
||||
<h2 style="color: white;">🧪 Test Controls</h2>
|
||||
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
|
||||
<button id="simulate-login" class="btn">Simulate Login</button>
|
||||
<button id="simulate-logout" class="btn">Simulate Logout</button>
|
||||
<button id="update-profile" class="btn">Update Profile</button>
|
||||
<button id="check-session" class="btn">Check Session</button>
|
||||
</div>
|
||||
<div id="console-output" style="margin-top: 20px; padding: 10px; background: black; border-radius: 4px; font-family: monospace; font-size: 12px; min-height: 80px;">
|
||||
> Ready...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<unraid-modals></unraid-modals>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
const output = $('#console-output');
|
||||
let isAuthenticated = false;
|
||||
|
||||
function log(message) {
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
output.append('\n> [' + timestamp + '] ' + message);
|
||||
output.scrollTop(output[0].scrollHeight);
|
||||
}
|
||||
|
||||
$('#simulate-login').on('click', function() {
|
||||
log('Simulating login...');
|
||||
isAuthenticated = true;
|
||||
|
||||
const userData = {
|
||||
username: 'admin',
|
||||
email: 'admin@unraid.local',
|
||||
name: 'Administrator',
|
||||
avatarUrl: '/webGui/images/default-avatar.png',
|
||||
role: 'admin',
|
||||
sessionId: 'sess_' + Math.random().toString(36).substr(2, 9)
|
||||
};
|
||||
|
||||
$('#user-profile').attr('server', JSON.stringify(userData));
|
||||
$('#auth-status').html('<span class="status-badge authenticated">Authenticated as ' + userData.username + '</span>');
|
||||
|
||||
$(document).trigger('unraid:auth-login', userData);
|
||||
log('Login successful: ' + userData.username);
|
||||
});
|
||||
|
||||
$('#simulate-logout').on('click', function() {
|
||||
log('Simulating logout...');
|
||||
isAuthenticated = false;
|
||||
|
||||
$('#user-profile').attr('server', '{}');
|
||||
$('#auth-status').html('<span class="status-badge unauthenticated">Not Authenticated</span>');
|
||||
|
||||
$(document).trigger('unraid:auth-logout');
|
||||
log('Logged out successfully');
|
||||
});
|
||||
|
||||
$('#update-profile').on('click', function() {
|
||||
if (!isAuthenticated) {
|
||||
log('Error: Not authenticated');
|
||||
return;
|
||||
}
|
||||
|
||||
log('Updating profile...');
|
||||
const updatedData = {
|
||||
username: 'admin',
|
||||
email: 'newemail@unraid.local',
|
||||
name: 'Updated Admin',
|
||||
lastModified: new Date().toISOString()
|
||||
};
|
||||
|
||||
$('#user-profile').attr('server', JSON.stringify(updatedData));
|
||||
log('Profile updated');
|
||||
});
|
||||
|
||||
$('#check-session').on('click', function() {
|
||||
log('Checking session status...');
|
||||
log('Authenticated: ' + (isAuthenticated ? 'Yes' : 'No'));
|
||||
|
||||
if (isAuthenticated) {
|
||||
log('Session valid until: ' + new Date(Date.now() + 3600000).toLocaleTimeString());
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('unraid:auth-required', function() {
|
||||
log('Authentication required by component');
|
||||
});
|
||||
|
||||
$(document).on('unraid:session-expired', function() {
|
||||
log('Session expired - please login again');
|
||||
$('#simulate-logout').click();
|
||||
});
|
||||
|
||||
log('Authentication test page ready');
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
264
web/test-pages/pages/component-mounting.njk
Normal file
264
web/test-pages/pages/component-mounting.njk
Normal file
@@ -0,0 +1,264 @@
|
||||
{% extends "layouts/base.njk" %}
|
||||
|
||||
{% block title %}Component Mounting Test - Unraid Component Test{% endblock %}
|
||||
|
||||
{% block pageStyles %}
|
||||
<style>
|
||||
h1 {
|
||||
color: var(--text-color, #333);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: var(--alt-text-color, #666);
|
||||
margin-top: 0;
|
||||
margin-bottom: 15px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.status {
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 10px;
|
||||
font-family: monospace;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.status.loading {
|
||||
background: #fff3cd;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.status.success {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.status.error {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.test-button {
|
||||
background: #007bff;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.test-button:hover {
|
||||
background: #0056b3;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="teleports"></div>
|
||||
|
||||
<unraid-modals></unraid-modals>
|
||||
|
||||
<div class="container">
|
||||
<h1>🧪 Standalone Vue Apps Test Page</h1>
|
||||
<div id="status" class="status loading">Loading...</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>Test 1: Single Component Mount</h2>
|
||||
<p>Testing single instance of HeaderOsVersion component</p>
|
||||
<div class="mount-target" data-label="HeaderOsVersion Mount">
|
||||
<unraid-header-os-version></unraid-header-os-version>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>Test 2: Multiple Component Mounts (Shared Pinia Store)</h2>
|
||||
<p>Testing that multiple instances share the same Pinia store</p>
|
||||
<div class="multiple-mounts">
|
||||
<div class="mount-target" data-label="Instance 1">
|
||||
<unraid-header-os-version></unraid-header-os-version>
|
||||
</div>
|
||||
<div class="mount-target" data-label="Instance 2">
|
||||
<unraid-header-os-version></unraid-header-os-version>
|
||||
</div>
|
||||
<div class="mount-target" data-label="Instance 3">
|
||||
<unraid-header-os-version></unraid-header-os-version>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>Test 3: Dynamic Component Creation</h2>
|
||||
<p>Test dynamically adding components after page load</p>
|
||||
<button class="test-button" id="addComponent">Add New Component</button>
|
||||
<button class="test-button" id="removeComponent">Remove Last Component</button>
|
||||
<button class="test-button" id="remountAll">Remount All</button>
|
||||
<div id="dynamicContainer" style="margin-top: 20px;"></div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>Test 4: Modal Components</h2>
|
||||
<p>Test modal functionality</p>
|
||||
<button class="test-button" onclick="testTrialModal()">Open Trial Modal</button>
|
||||
<button class="test-button" onclick="testUpdateModal()">Open Update Modal</button>
|
||||
<button class="test-button" onclick="testApiKeyModal()">Open API Key Modal</button>
|
||||
<div style="margin-top: 10px;">
|
||||
<small>Note: Modals require proper store state to display</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>Debug Information</h2>
|
||||
<div class="debug-info" id="debugInfo">
|
||||
Waiting for initialization...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
window.GRAPHQL_ENDPOINT = window.location.port === '3000' ? '/graphql' : 'http://localhost:3001/graphql';
|
||||
window.__WEBGUI_PATH__ = '';
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const status = document.getElementById('status');
|
||||
const debugInfo = document.getElementById('debugInfo');
|
||||
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
if (mutation.type === 'childList') {
|
||||
mutation.addedNodes.forEach((node) => {
|
||||
if (node.nodeName === 'SCRIPT') {
|
||||
console.log('Script loaded:', node.src || 'inline');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
observer.observe(document.head, { childList: true });
|
||||
observer.observe(document.body, { childList: true });
|
||||
|
||||
let checkInterval = setInterval(() => {
|
||||
const mountedElements = document.querySelectorAll('[data-vue-mounted="true"]');
|
||||
let totalComponents = document.querySelectorAll('unraid-header-os-version, unraid-modals').length;
|
||||
let mountedCount = mountedElements.length;
|
||||
|
||||
if (mountedCount > 0) {
|
||||
status.className = 'status success';
|
||||
status.textContent = `✅ Successfully mounted ${mountedCount} component(s)`;
|
||||
|
||||
debugInfo.textContent = `
|
||||
Components Found: ${totalComponents}
|
||||
Components Mounted: ${mountedCount}
|
||||
Unified Vue App: ${window.__unifiedApp ? 'Initialized' : 'Not found'}
|
||||
Mounted Components: ${window.__mountedComponents ? window.__mountedComponents.length : 0}
|
||||
Pinia Store: ${window.globalPinia ? 'Initialized' : 'Not found'}
|
||||
GraphQL Endpoint: ${window.GRAPHQL_ENDPOINT || 'Not configured'}
|
||||
`.trim();
|
||||
|
||||
clearInterval(checkInterval);
|
||||
|
||||
if (window.testLog) {
|
||||
window.testLog(`Mounted ${mountedCount} components successfully`, 'success');
|
||||
}
|
||||
}
|
||||
}, 500);
|
||||
|
||||
setTimeout(() => {
|
||||
if (checkInterval) {
|
||||
clearInterval(checkInterval);
|
||||
if (status.className === 'status loading') {
|
||||
status.className = 'status error';
|
||||
status.textContent = '❌ Failed to mount components (timeout)';
|
||||
}
|
||||
}
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
let dynamicCount = 0;
|
||||
const dynamicContainer = document.getElementById('dynamicContainer');
|
||||
|
||||
document.getElementById('addComponent').addEventListener('click', () => {
|
||||
dynamicCount++;
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'mount-target';
|
||||
wrapper.setAttribute('data-label', `Dynamic Instance ${dynamicCount}`);
|
||||
wrapper.style.marginBottom = '10px';
|
||||
|
||||
const element = document.createElement('unraid-header-os-version');
|
||||
wrapper.appendChild(element);
|
||||
dynamicContainer.appendChild(wrapper);
|
||||
|
||||
console.log('Note: Dynamic components require page reload to mount with the unified app system');
|
||||
|
||||
if (!wrapper.querySelector('.reload-note')) {
|
||||
const note = document.createElement('div');
|
||||
note.className = 'reload-note';
|
||||
note.style.cssText = 'color: #666; font-size: 12px; margin-top: 10px;';
|
||||
note.textContent = 'Reload page to mount this component';
|
||||
wrapper.appendChild(note);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('removeComponent').addEventListener('click', () => {
|
||||
const lastChild = dynamicContainer.lastElementChild;
|
||||
if (lastChild) {
|
||||
const mountedElement = lastChild.querySelector('[data-vue-mounted="true"]');
|
||||
if (mountedElement && window.__mountedComponents) {
|
||||
const componentIndex = window.__mountedComponents.findIndex(c => c.element === mountedElement);
|
||||
if (componentIndex !== -1) {
|
||||
window.__mountedComponents[componentIndex].unmount();
|
||||
window.__mountedComponents.splice(componentIndex, 1);
|
||||
}
|
||||
}
|
||||
dynamicContainer.removeChild(lastChild);
|
||||
dynamicCount = Math.max(0, dynamicCount - 1);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('remountAll').addEventListener('click', () => {
|
||||
console.log('Remounting all components...');
|
||||
location.reload();
|
||||
});
|
||||
});
|
||||
|
||||
window.testTrialModal = function() {
|
||||
console.log('Testing trial modal...');
|
||||
if (window.globalPinia) {
|
||||
const trialStore = window.globalPinia._s.get('trial');
|
||||
if (trialStore) {
|
||||
trialStore.trialModalVisible = true;
|
||||
console.log('Trial modal triggered');
|
||||
} else {
|
||||
console.error('Trial store not found');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.testUpdateModal = function() {
|
||||
console.log('Testing update modal...');
|
||||
if (window.globalPinia) {
|
||||
const updateStore = window.globalPinia._s.get('updateOs');
|
||||
if (updateStore) {
|
||||
updateStore.updateOsModalVisible = true;
|
||||
console.log('Update modal triggered');
|
||||
} else {
|
||||
console.error('Update store not found');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.testApiKeyModal = function() {
|
||||
console.log('Testing API key modal...');
|
||||
if (window.globalPinia) {
|
||||
const apiKeyStore = window.globalPinia._s.get('apiKey');
|
||||
if (apiKeyStore) {
|
||||
apiKeyStore.showCreateModal = true;
|
||||
console.log('API key modal triggered');
|
||||
} else {
|
||||
console.error('API key store not found');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
81
web/test-pages/pages/connect-settings.njk
Normal file
81
web/test-pages/pages/connect-settings.njk
Normal file
@@ -0,0 +1,81 @@
|
||||
{% extends "layouts/base.njk" %}
|
||||
|
||||
{% block title %}Connect Settings - Unraid Component Test{% endblock %}
|
||||
|
||||
{% block pageStyles %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container padded">
|
||||
<div class="breadcrumb">
|
||||
<a href="/test-pages/">Test Pages</a> / Connect Settings
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Unraid API Settings</h2>
|
||||
<p class="card-description">This page mirrors the Connect.page structure from Unraid OS, demonstrating the Connect Settings component in its native context.</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Connect Settings</h2>
|
||||
<div class="component-mount" data-component="unraid-connect-settings">
|
||||
<unraid-connect-settings></unraid-connect-settings>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Remote Access Configuration</h2>
|
||||
<div class="setting-item">
|
||||
<div class="setting-label">WAN IP Check</div>
|
||||
<div class="setting-description">Verify your server's external IP address and port forwarding configuration</div>
|
||||
<div class="component-mount" data-component="unraid-wan-ip-check" style="margin-top: 10px;">
|
||||
<unraid-wan-ip-check php-wan-ip="203.0.113.42"></unraid-wan-ip-check>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Registration</h2>
|
||||
<div class="component-mount" data-component="unraid-registration">
|
||||
<unraid-registration></unraid-registration>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<unraid-modals></unraid-modals>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
setTimeout(function() {
|
||||
var connectSettings = {
|
||||
enabled: true,
|
||||
url: 'https://connect.unraid.net',
|
||||
registered: true,
|
||||
lastSync: new Date().toISOString()
|
||||
};
|
||||
|
||||
$('unraid-connect-settings').attr('initial-settings', JSON.stringify(connectSettings));
|
||||
console.log('Loaded Connect settings via jQuery');
|
||||
}, 1000);
|
||||
|
||||
$(document).on('unraid:settings-saved', function(e, data) {
|
||||
console.log('Connect settings saved:', data);
|
||||
var event = new CustomEvent('unraid:notification', {
|
||||
detail: {
|
||||
title: 'Settings Saved',
|
||||
message: 'Connect settings have been updated successfully',
|
||||
type: 'success'
|
||||
}
|
||||
});
|
||||
document.dispatchEvent(event);
|
||||
});
|
||||
|
||||
setTimeout(function() {
|
||||
$('unraid-wan-ip-check').attr('php-wan-ip', '198.51.100.42');
|
||||
console.log('Updated WAN IP via jQuery');
|
||||
}, 2000);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
77
web/test-pages/pages/header.njk
Normal file
77
web/test-pages/pages/header.njk
Normal file
@@ -0,0 +1,77 @@
|
||||
{% extends "layouts/base.njk" %}
|
||||
|
||||
{% block title %}Header - Unraid Component Test{% endblock %}
|
||||
|
||||
{% block pageStyles %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container padded">
|
||||
<div class="breadcrumb">
|
||||
<a href="/test-pages/">Test Pages</a> / Header
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Unraid Header</h2>
|
||||
<p class="card-description">This page demonstrates the header components as they appear in Unraid OS pages.</p>
|
||||
|
||||
<div class="header" style="margin: 20px 0; padding: 16px 20px; display: flex; justify-content: space-between; align-items: center;">
|
||||
<div class="header-left" style="display: flex; align-items: center; gap: 20px;">
|
||||
<unraid-header-os-version></unraid-header-os-version>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<unraid-user-profile id="header-user-profile"></unraid-user-profile>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Component Details</h2>
|
||||
<div class="setting-item">
|
||||
<div class="setting-label">OS Version Component</div>
|
||||
<div class="setting-description">Displays the current Unraid OS version and update status</div>
|
||||
<div class="component-mount" data-component="unraid-header-os-version" style="margin-top: 10px;">
|
||||
<unraid-header-os-version></unraid-header-os-version>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-item">
|
||||
<div class="setting-label">User Profile Component</div>
|
||||
<div class="setting-description">Shows user information, authentication status, and SSO options</div>
|
||||
<div class="component-mount" data-component="unraid-user-profile" style="margin-top: 10px;">
|
||||
<unraid-user-profile id="standalone-user-profile"></unraid-user-profile>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<unraid-modals></unraid-modals>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
var serverData = {
|
||||
name: 'TestServer',
|
||||
version: '6.12.4',
|
||||
username: 'admin',
|
||||
email: 'admin@unraid.net',
|
||||
avatarUrl: '/webGui/images/default-avatar.png'
|
||||
};
|
||||
|
||||
$('#header-user-profile, #standalone-user-profile').attr('server', JSON.stringify(serverData));
|
||||
|
||||
setTimeout(function() {
|
||||
var updatedData = {
|
||||
name: 'UpdatedServer',
|
||||
version: '6.12.5',
|
||||
username: 'admin',
|
||||
email: 'admin@unraid.net',
|
||||
avatarUrl: '/webGui/images/default-avatar.png'
|
||||
};
|
||||
$('#header-user-profile').attr('server', JSON.stringify(updatedData));
|
||||
console.log('Updated header user profile via jQuery');
|
||||
}, 3000);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
114
web/test-pages/pages/index.njk
Normal file
114
web/test-pages/pages/index.njk
Normal file
@@ -0,0 +1,114 @@
|
||||
{% extends "layouts/base.njk" %}
|
||||
|
||||
{% block title %}Unraid Component Test Pages{% endblock %}
|
||||
|
||||
{% block pageStyles %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>🧪 Unraid Component Test Environment</h1>
|
||||
<p>HTML-based test pages that mimic Unraid OS integration with jQuery and component mounting</p>
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<strong>ℹ️ Testing Mode:</strong> These pages replicate how Unraid OS mounts Vue components into existing HTML/PHP pages using jQuery for interaction. Each page demonstrates real-world integration patterns.
|
||||
</div>
|
||||
|
||||
<div class="category-section">
|
||||
<div class="category-header rounded-top">
|
||||
📄 Page-Specific Test Pages
|
||||
</div>
|
||||
<div class="page-list">
|
||||
<div class="page-item">
|
||||
<div>
|
||||
<h3>Header <span class="badge new">NEW</span></h3>
|
||||
<p>Header components as they appear in Unraid OS: OS version and user profile</p>
|
||||
</div>
|
||||
<a href="/test-pages/header.html">Open →</a>
|
||||
</div>
|
||||
|
||||
<div class="page-item">
|
||||
<div>
|
||||
<h3>Connect Settings <span class="badge new">NEW</span></h3>
|
||||
<p>Unraid API Settings page mirroring Connect.page structure with Connect Settings component</p>
|
||||
</div>
|
||||
<a href="/test-pages/connect-settings.html">Open →</a>
|
||||
</div>
|
||||
|
||||
<div class="page-item">
|
||||
<div>
|
||||
<h3>API & Developer Tools <span class="badge new">NEW</span></h3>
|
||||
<p>API key management, authorization, logs, and developer tools</p>
|
||||
</div>
|
||||
<a href="/test-pages/api-developer.html">Open →</a>
|
||||
</div>
|
||||
|
||||
<div class="page-item">
|
||||
<div>
|
||||
<h3>Authentication Flow</h3>
|
||||
<p>Complete auth workflow with login, SSO, user profile, and registration</p>
|
||||
</div>
|
||||
<a href="/test-pages/authentication.html">Open →</a>
|
||||
</div>
|
||||
|
||||
<div class="page-item">
|
||||
<div>
|
||||
<h3>OS Management</h3>
|
||||
<p>System updates, downgrades, and version management with progress simulation</p>
|
||||
</div>
|
||||
<a href="/test-pages/os-management.html">Open →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="category-section">
|
||||
<div class="category-header rounded-top">
|
||||
🔧 Technical Test Pages
|
||||
</div>
|
||||
<div class="page-list">
|
||||
<div class="page-item">
|
||||
<div>
|
||||
<h3>Update Modal Testing</h3>
|
||||
<p>Test various update scenarios including expired licenses, renewals, and auth requirements</p>
|
||||
</div>
|
||||
<a href="/test-pages/update-modal.html">Open →</a>
|
||||
</div>
|
||||
|
||||
<div class="page-item">
|
||||
<div>
|
||||
<h3>Component Mounting Test</h3>
|
||||
<p>Test single and multiple component mounting with shared Pinia store and dynamic creation</p>
|
||||
</div>
|
||||
<a href="/test-pages/component-mounting.html">Open →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" style="margin-top: 30px;">
|
||||
<h3 style="margin-top: 0;">🛠️ Testing Guidelines</h3>
|
||||
<ul style="color: var(--muted-foreground, var(--alt-text-color)); line-height: 1.8;">
|
||||
<li>Each page loads components using the same mechanism as Unraid OS (manifest-based loading)</li>
|
||||
<li>jQuery is available for simulating PHP/backend interactions</li>
|
||||
<li>Components communicate via DOM attributes and custom events</li>
|
||||
<li>Hot module replacement (HMR) is enabled in dev mode for instant updates</li>
|
||||
<li>Use browser DevTools console to monitor component events and interactions</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('.page-item').on('mouseenter', function() {
|
||||
$(this).find('a').css('transform', 'translateX(2px)');
|
||||
}).on('mouseleave', function() {
|
||||
$(this).find('a').css('transform', 'translateX(0)');
|
||||
});
|
||||
|
||||
$('a').css('transition', 'all 0.2s ease');
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
221
web/test-pages/pages/os-management.njk
Normal file
221
web/test-pages/pages/os-management.njk
Normal file
@@ -0,0 +1,221 @@
|
||||
{% extends "layouts/base.njk" %}
|
||||
|
||||
{% block title %}OS Management - Unraid Component Test{% endblock %}
|
||||
|
||||
{% block pageStyles %}
|
||||
<style>
|
||||
body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: var(--header-background-color, #1f2937);
|
||||
color: var(--header-text-primary, white);
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.test-btn.large {
|
||||
padding: 10px 16px;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="header">
|
||||
<div class="container">
|
||||
<h1>
|
||||
💿 OS Management
|
||||
<unraid-header-os-version></unraid-header-os-version>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="info-bar">
|
||||
<div class="version-info">
|
||||
<div class="version-item">
|
||||
<span class="version-label">Current Version</span>
|
||||
<span class="version-value" id="current-version">6.12.4</span>
|
||||
</div>
|
||||
<div class="version-item">
|
||||
<span class="version-label">Latest Available</span>
|
||||
<span class="version-value" id="latest-version">6.12.5</span>
|
||||
</div>
|
||||
<div class="version-item">
|
||||
<span class="version-label">Update Channel</span>
|
||||
<span class="version-value" id="update-channel">Stable</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-indicator update-available" id="update-status">
|
||||
<span>●</span> Update Available
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="main-grid">
|
||||
<div class="card">
|
||||
<h2>⬆️ System Updates</h2>
|
||||
<p class="card-description">Check for and install Unraid OS updates</p>
|
||||
<unraid-update-os></unraid-update-os>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>⬇️ System Downgrade</h2>
|
||||
<p class="card-description">Rollback to a previous Unraid OS version</p>
|
||||
<unraid-downgrade-os></unraid-downgrade-os>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" style="margin-top: 20px; background: #1f2937;">
|
||||
<h2 style="color: white;">🧪 Test Scenarios</h2>
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 10px;">
|
||||
<button class="test-btn" id="check-updates">Check for Updates</button>
|
||||
<button class="test-btn" id="simulate-update">Simulate Update Available</button>
|
||||
<button class="test-btn" id="simulate-current">Simulate Up-to-date</button>
|
||||
<button class="test-btn" id="change-channel">Switch Channel (Beta)</button>
|
||||
<button class="test-btn" id="simulate-download">Simulate Download Progress</button>
|
||||
<button class="test-btn" id="simulate-install">Simulate Installation</button>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 20px;">
|
||||
<div style="background: black; color: #10b981; padding: 15px; border-radius: 6px; font-family: monospace; font-size: 12px; min-height: 120px; max-height: 200px; overflow-y: auto;" id="console">
|
||||
> OS Management Test Console
|
||||
> Ready for testing...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<unraid-modals></unraid-modals>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
const $console = $('#console');
|
||||
let currentVersion = '6.12.4';
|
||||
let latestVersion = '6.12.5';
|
||||
let updateChannel = 'stable';
|
||||
|
||||
function log(message, type = 'info') {
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
const prefix = type === 'error' ? '❌' : type === 'success' ? '✅' : '>';
|
||||
$console.append('\n' + prefix + ' [' + timestamp + '] ' + message);
|
||||
$console.scrollTop($console[0].scrollHeight);
|
||||
}
|
||||
|
||||
$('#check-updates').on('click', function() {
|
||||
log('Checking for updates...');
|
||||
$('#update-status').removeClass('up-to-date update-available').addClass('checking');
|
||||
$('#update-status').html('<span>⟳</span> Checking...');
|
||||
|
||||
setTimeout(function() {
|
||||
if (currentVersion !== latestVersion) {
|
||||
$('#update-status').removeClass('checking up-to-date').addClass('update-available');
|
||||
$('#update-status').html('<span>●</span> Update Available');
|
||||
log('Update available: ' + latestVersion, 'success');
|
||||
} else {
|
||||
$('#update-status').removeClass('checking update-available').addClass('up-to-date');
|
||||
$('#update-status').html('<span>✓</span> Up to Date');
|
||||
log('System is up to date', 'success');
|
||||
}
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
$('#simulate-update').on('click', function() {
|
||||
latestVersion = '6.12.6';
|
||||
$('#latest-version').text(latestVersion);
|
||||
$('#update-status').removeClass('up-to-date checking').addClass('update-available');
|
||||
$('#update-status').html('<span>●</span> Update Available');
|
||||
log('Simulated update available: ' + latestVersion);
|
||||
|
||||
$('unraid-update-os').attr('latest-version', latestVersion);
|
||||
});
|
||||
|
||||
$('#simulate-current').on('click', function() {
|
||||
currentVersion = latestVersion = '6.12.5';
|
||||
$('#current-version').text(currentVersion);
|
||||
$('#latest-version').text(latestVersion);
|
||||
$('#update-status').removeClass('update-available checking').addClass('up-to-date');
|
||||
$('#update-status').html('<span>✓</span> Up to Date');
|
||||
log('System is now up to date');
|
||||
});
|
||||
|
||||
$('#change-channel').on('click', function() {
|
||||
updateChannel = updateChannel === 'stable' ? 'beta' : 'stable';
|
||||
$('#update-channel').text(updateChannel.charAt(0).toUpperCase() + updateChannel.slice(1));
|
||||
log('Switched to ' + updateChannel + ' channel');
|
||||
|
||||
if (updateChannel === 'beta') {
|
||||
latestVersion = '6.13.0-beta1';
|
||||
$('#latest-version').text(latestVersion);
|
||||
log('Beta version available: ' + latestVersion);
|
||||
} else {
|
||||
latestVersion = '6.12.5';
|
||||
$('#latest-version').text(latestVersion);
|
||||
}
|
||||
});
|
||||
|
||||
$('#simulate-download').on('click', function() {
|
||||
log('Starting download simulation...');
|
||||
|
||||
const $card = $(this).closest('.card');
|
||||
if (!$card.find('.progress-bar').length) {
|
||||
$card.append('<div class="progress-bar"><div class="progress-fill" style="width: 0%">0%</div></div>');
|
||||
}
|
||||
|
||||
let progress = 0;
|
||||
const interval = setInterval(function() {
|
||||
progress += 10;
|
||||
$('.progress-fill').css('width', progress + '%').text(progress + '%');
|
||||
log('Download progress: ' + progress + '%');
|
||||
|
||||
if (progress >= 100) {
|
||||
clearInterval(interval);
|
||||
log('Download complete!', 'success');
|
||||
setTimeout(function() {
|
||||
$('.progress-bar').fadeOut();
|
||||
}, 2000);
|
||||
}
|
||||
}, 500);
|
||||
});
|
||||
|
||||
$('#simulate-install').on('click', function() {
|
||||
log('Preparing installation...');
|
||||
log('Creating backup...');
|
||||
|
||||
setTimeout(function() {
|
||||
log('Backup complete', 'success');
|
||||
log('Installing update...');
|
||||
|
||||
setTimeout(function() {
|
||||
log('Installation complete!', 'success');
|
||||
log('System will restart in 30 seconds...');
|
||||
currentVersion = latestVersion;
|
||||
$('#current-version').text(currentVersion);
|
||||
}, 3000);
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
$(document).on('unraid:update-started', function(e, data) {
|
||||
log('Update started: ' + data.version);
|
||||
});
|
||||
|
||||
$(document).on('unraid:update-complete', function(e, data) {
|
||||
log('Update complete: ' + data.version, 'success');
|
||||
});
|
||||
|
||||
log('OS Management test page initialized');
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
42
web/test-pages/pages/update-modal.njk
Normal file
42
web/test-pages/pages/update-modal.njk
Normal file
@@ -0,0 +1,42 @@
|
||||
{% extends "layouts/base.njk" %}
|
||||
|
||||
{% block title %}Update Modal Test - Unraid Component Test{% endblock %}
|
||||
|
||||
{% block pageStyles %}
|
||||
<style>
|
||||
body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: var(--header-background-color, #1f2937);
|
||||
color: var(--header-text-primary, white);
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="header">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div>
|
||||
<a href="/test-pages/" class="back-link">← Back to Test Pages</a>
|
||||
<h1>🧪 Update Modal Test Scenarios</h1>
|
||||
</div>
|
||||
<div>
|
||||
<unraid-test-theme-switcher></unraid-test-theme-switcher>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<unraid-test-update-modal></unraid-test-update-modal>
|
||||
|
||||
<unraid-modals></unraid-modals>
|
||||
{% endblock %}
|
||||
|
||||
5
web/test-pages/partials/scripts.njk
Normal file
5
web/test-pages/partials/scripts.njk
Normal file
@@ -0,0 +1,5 @@
|
||||
<script src="/test-pages/dev-tools.js"></script>
|
||||
<script src="/test-pages/load-manifest.js"></script>
|
||||
<script src="/test-pages/test-server-state.js"></script>
|
||||
<script src="/test-pages/shared-header.js"></script>
|
||||
|
||||
590
web/test-pages/partials/styles.njk
Normal file
590
web/test-pages/partials/styles.njk
Normal file
@@ -0,0 +1,590 @@
|
||||
{% if mode === 'development' %}
|
||||
{% set activeTheme = query.theme or 'white' %}
|
||||
<link rel="stylesheet" href="/test-pages/unraid-assets/default-fonts.css">
|
||||
<link rel="stylesheet" href="/test-pages/unraid-assets/default-base.css">
|
||||
<link rel="stylesheet" href="/test-pages/unraid-assets/default-cases.css">
|
||||
<link rel="stylesheet" href="/test-pages/unraid-assets/default-color-palette.css">
|
||||
<link rel="stylesheet" href="/test-pages/unraid-assets/default-dynamix.css">
|
||||
<link rel="stylesheet" href="/test-pages/unraid-assets/font-awesome.css">
|
||||
<link rel="stylesheet" href="/test-pages/unraid-assets/jquery.filetree.css">
|
||||
<link rel="stylesheet" href="/test-pages/unraid-assets/jquery.sweetalert.css">
|
||||
<link rel="stylesheet" href="/test-pages/unraid-assets/jquery.switchbutton.css">
|
||||
<link rel="stylesheet" href="/test-pages/unraid-assets/jquery.ui.css">
|
||||
<link rel="stylesheet" href="/test-pages/unraid-assets/context.standalone.css">
|
||||
{% if activeTheme === 'azure' %}
|
||||
<link rel="stylesheet" href="/test-pages/unraid-assets/themes/azure.css" id="dev-theme-css-link">
|
||||
{% elif activeTheme === 'black' %}
|
||||
<link rel="stylesheet" href="/test-pages/unraid-assets/themes/black.css" id="dev-theme-css-link">
|
||||
{% elif activeTheme === 'gray' %}
|
||||
<link rel="stylesheet" href="/test-pages/unraid-assets/themes/gray.css" id="dev-theme-css-link">
|
||||
{% elif activeTheme === 'white' %}
|
||||
<link rel="stylesheet" href="/test-pages/unraid-assets/themes/white.css" id="dev-theme-css-link">
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<link rel="stylesheet" href="/test-pages/unraid-assets/default-fonts.css">
|
||||
<link rel="stylesheet" href="/test-pages/unraid-assets/default-base.css">
|
||||
<link rel="stylesheet" href="/test-pages/unraid-assets/default-cases.css">
|
||||
<link rel="stylesheet" href="/test-pages/unraid-assets/default-color-palette.css">
|
||||
<link rel="stylesheet" href="/test-pages/unraid-assets/default-dynamix.css">
|
||||
<link rel="stylesheet" href="/test-pages/unraid-assets/font-awesome.css">
|
||||
<link rel="stylesheet" href="/test-pages/unraid-assets/jquery.filetree.css">
|
||||
<link rel="stylesheet" href="/test-pages/unraid-assets/jquery.sweetalert.css">
|
||||
<link rel="stylesheet" href="/test-pages/unraid-assets/jquery.switchbutton.css">
|
||||
<link rel="stylesheet" href="/test-pages/unraid-assets/jquery.ui.css">
|
||||
<link rel="stylesheet" href="/test-pages/unraid-assets/context.standalone.css">
|
||||
{% endif %}
|
||||
|
||||
<style>
|
||||
html {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: clear-sans, system-ui, -apple-system, sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: var(--background-color);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.container.padded {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, #1f2937 0%, #374151 100%);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.header p {
|
||||
margin: 0;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.dashboard-header {
|
||||
background: var(--header-background-color, #1f2937);
|
||||
color: var(--header-text-primary, white);
|
||||
padding: 16px 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.main-content.centered {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
padding: 10px 20px;
|
||||
background-color: var(--card, var(--background-color));
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.breadcrumb a {
|
||||
color: #3b82f6;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.category-section {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.category-header {
|
||||
background: var(--header-background-color, #1f2937);
|
||||
color: var(--header-text-primary, white);
|
||||
padding: 10px 15px;
|
||||
border-radius: 4px;
|
||||
margin: 30px 0 15px 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.category-header.rounded-top {
|
||||
padding: 12px 20px;
|
||||
border-radius: 6px 6px 0 0;
|
||||
font-size: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.page-list {
|
||||
background-color: var(--card, var(--background-color));
|
||||
border-radius: 0 0 6px 6px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.page-item {
|
||||
padding: 15px 20px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.page-item:hover {
|
||||
background-color: var(--hover-table-row-background-color, var(--muted));
|
||||
}
|
||||
|
||||
.page-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.page-item h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
color: var(--card-foreground, var(--text-color));
|
||||
}
|
||||
|
||||
.page-item p {
|
||||
margin: 5px 0 0 0;
|
||||
color: var(--muted-foreground, var(--alt-text-color));
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.page-item .badge {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
background: #dbeafe;
|
||||
color: #1e40af;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.page-item .badge.new {
|
||||
background: #d1fae5;
|
||||
color: #065f46;
|
||||
}
|
||||
|
||||
.page-item a {
|
||||
padding: 8px 16px;
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.page-item a:hover {
|
||||
background: #2563eb;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
background: #fef3c7;
|
||||
border: 1px solid #f59e0b;
|
||||
border-radius: 6px;
|
||||
padding: 15px 20px;
|
||||
margin-bottom: 30px;
|
||||
color: #92400e;
|
||||
}
|
||||
|
||||
.info-box strong {
|
||||
color: #78350f;
|
||||
}
|
||||
|
||||
.component-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.component-card {
|
||||
background-color: var(--card, var(--background-color));
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.component-card h3 {
|
||||
margin: 0 0 10px 0;
|
||||
color: var(--card-foreground, var(--text-color));
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.component-card .selector {
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
color: var(--muted-foreground, var(--alt-text-color));
|
||||
margin-bottom: 15px;
|
||||
background: var(--muted);
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.component-mount {
|
||||
min-height: 50px;
|
||||
border: 1px dashed var(--border);
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.component-mount::before {
|
||||
content: attr(data-component);
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
left: 10px;
|
||||
background-color: var(--card, var(--background-color));
|
||||
padding: 0 5px;
|
||||
font-size: 12px;
|
||||
color: var(--muted-foreground, var(--alt-text-color));
|
||||
}
|
||||
|
||||
.status {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #10b981;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: var(--card, var(--background-color));
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
margin-top: 0;
|
||||
color: var(--card-foreground, var(--text-color));
|
||||
}
|
||||
|
||||
.tabs {
|
||||
background-color: var(--card, var(--background-color));
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 0 20px;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 12px 0;
|
||||
border-bottom: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
color: var(--muted-foreground, var(--alt-text-color));
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
border-bottom-color: #3b82f6;
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.settings-section h2 {
|
||||
margin-top: 0;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
color: var(--card-foreground, var(--text-color));
|
||||
}
|
||||
|
||||
.setting-item {
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.setting-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.setting-label {
|
||||
font-weight: 500;
|
||||
color: var(--card-foreground, var(--text-color));
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.setting-description {
|
||||
font-size: 14px;
|
||||
color: var(--muted-foreground, var(--alt-text-color));
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.auth-container {
|
||||
max-width: 600px;
|
||||
margin: 50px auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.auth-card {
|
||||
border-radius: 8px;
|
||||
padding: 30px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.auth-card h2 {
|
||||
margin: 0 0 20px 0;
|
||||
color: var(--card-foreground, var(--text-color));
|
||||
}
|
||||
|
||||
.auth-card {
|
||||
background-color: var(--card, var(--background-color));
|
||||
}
|
||||
|
||||
.user-info {
|
||||
background: var(--muted);
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.status-badge.authenticated {
|
||||
background: #10b981;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.status-badge.unauthenticated {
|
||||
background: #ef4444;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.info-bar {
|
||||
padding: 15px 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.version-info {
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.version-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.version-label {
|
||||
font-size: 12px;
|
||||
color: var(--muted-foreground, var(--alt-text-color));
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.version-value {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: var(--card-foreground, var(--text-color));
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.info-bar {
|
||||
background-color: var(--card, var(--background-color));
|
||||
}
|
||||
|
||||
.main-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.card-description {
|
||||
color: var(--muted-foreground, var(--alt-text-color));
|
||||
margin-bottom: 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-indicator.up-to-date {
|
||||
background: #d1fae5;
|
||||
color: #065f46;
|
||||
}
|
||||
|
||||
.status-indicator.update-available {
|
||||
background: #fed7aa;
|
||||
color: #92400e;
|
||||
}
|
||||
|
||||
.status-indicator.checking {
|
||||
background: #dbeafe;
|
||||
color: #1e40af;
|
||||
}
|
||||
|
||||
.test-btn,
|
||||
.test-button,
|
||||
.btn {
|
||||
padding: 8px 16px;
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.test-btn:hover,
|
||||
.test-button:hover,
|
||||
.btn:hover {
|
||||
background: #2563eb;
|
||||
}
|
||||
|
||||
.test-button:disabled {
|
||||
background: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.test-btn.large {
|
||||
padding: 10px 16px;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
background: var(--muted);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #3b82f6, #2563eb);
|
||||
transition: width 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.test-section {
|
||||
background-color: var(--card, var(--background-color));
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.mount-target {
|
||||
padding: 20px;
|
||||
background: var(--muted);
|
||||
border: 2px dashed var(--border);
|
||||
border-radius: 4px;
|
||||
min-height: 100px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mount-target::before {
|
||||
content: attr(data-label);
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
left: 10px;
|
||||
background-color: var(--card, var(--background-color));
|
||||
padding: 0 5px;
|
||||
color: var(--muted-foreground, var(--alt-text-color));
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.debug-info {
|
||||
margin-top: 20px;
|
||||
padding: 15px;
|
||||
background: var(--muted);
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
white-space: pre-wrap;
|
||||
color: var(--muted-foreground, var(--text-color));
|
||||
}
|
||||
|
||||
.multiple-mounts {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.back-link {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
margin-bottom: 10px;
|
||||
display: inline-block;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.back-link:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.main-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
{% block pageStyles %}{% endblock %}
|
||||
@@ -2,39 +2,163 @@ import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import nunjucks from 'nunjucks';
|
||||
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http';
|
||||
import type { Plugin } from 'vite';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const publicDir = path.join(__dirname, 'public');
|
||||
const templatesDir = path.join(__dirname, 'test-pages');
|
||||
const pagesDir = path.join(templatesDir, 'pages');
|
||||
|
||||
const env = nunjucks.configure(templatesDir, {
|
||||
autoescape: false,
|
||||
noCache: true,
|
||||
throwOnUndefined: false,
|
||||
});
|
||||
|
||||
const GITHUB_RAW_BASE =
|
||||
'https://raw.githubusercontent.com/unraid/webgui/189edb1a690cfaef3358db9d6bef281a5e1231bc/emhttp/plugins/dynamix/styles';
|
||||
|
||||
export function serveStaticHtml(): Plugin {
|
||||
return {
|
||||
name: 'serve-static-html',
|
||||
configureServer(server) {
|
||||
// Serve test pages from public/test-pages
|
||||
server.watcher.add(path.join(templatesDir, '**/*'));
|
||||
|
||||
const handleUnraidAsset = async (res: ServerResponse, assetPath: string) => {
|
||||
if (!assetPath || assetPath === '/') {
|
||||
res.statusCode = 404;
|
||||
res.end('Asset path required');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const assetUrl = `${GITHUB_RAW_BASE}${assetPath}`;
|
||||
const response = await fetch(assetUrl);
|
||||
|
||||
if (!response.ok) {
|
||||
res.statusCode = response.status;
|
||||
res.end(`Failed to fetch asset: ${response.statusText}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const ext = path.extname(assetPath).toLowerCase();
|
||||
|
||||
let contentType = 'text/plain';
|
||||
if (ext === '.css') {
|
||||
contentType = 'text/css';
|
||||
} else if (ext === '.woff') {
|
||||
contentType = 'font/woff';
|
||||
} else if (ext === '.woff2') {
|
||||
contentType = 'font/woff2';
|
||||
}
|
||||
|
||||
let content: string | Buffer;
|
||||
if (ext === '.woff' || ext === '.woff2') {
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
content = Buffer.from(arrayBuffer);
|
||||
} else {
|
||||
content = await response.text();
|
||||
}
|
||||
|
||||
res.setHeader('Content-Type', contentType);
|
||||
res.setHeader('Cache-Control', 'public, max-age=3600');
|
||||
res.end(content);
|
||||
} catch (error) {
|
||||
res.statusCode = 500;
|
||||
res.end(`Error fetching asset: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
}
|
||||
};
|
||||
|
||||
server.middlewares.use(
|
||||
'/test-pages/unraid-assets',
|
||||
async (req: IncomingMessage, res: ServerResponse) => {
|
||||
const url = new URL(req.url || '/', 'http://localhost');
|
||||
const assetPath = url.pathname.replace('/test-pages/unraid-assets', '');
|
||||
await handleUnraidAsset(res, assetPath);
|
||||
}
|
||||
);
|
||||
|
||||
server.middlewares.use('/webGui/styles', async (req: IncomingMessage, res: ServerResponse) => {
|
||||
const url = new URL(req.url || '/', 'http://localhost');
|
||||
const assetPath = url.pathname.replace('/webGui/styles', '');
|
||||
await handleUnraidAsset(res, assetPath);
|
||||
});
|
||||
|
||||
server.middlewares.use((req, res, next) => {
|
||||
// Check if request is for test-pages
|
||||
if (req.url?.startsWith('/test-pages')) {
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const filePath = path.join(__dirname, 'public', req.url);
|
||||
if (!req.url?.startsWith('/test-pages')) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
// If it's a directory, serve index.html
|
||||
let targetPath = filePath;
|
||||
if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) {
|
||||
targetPath = path.join(filePath, 'index.html');
|
||||
const requestUrl = new URL(req.url, 'http://localhost');
|
||||
let pathname = requestUrl.pathname;
|
||||
|
||||
if (pathname.endsWith('/')) {
|
||||
pathname = `${pathname}index.html`;
|
||||
}
|
||||
|
||||
const extension = path.extname(pathname);
|
||||
|
||||
if (!extension) {
|
||||
pathname = `${pathname}.html`;
|
||||
}
|
||||
|
||||
const relativePath = pathname.replace(/^\/test-pages\/?/, '');
|
||||
const templatePath = path.join(pagesDir, relativePath.replace(/\.html$/, '.njk'));
|
||||
|
||||
if (extension === '' || extension === '.html') {
|
||||
if (fs.existsSync(templatePath)) {
|
||||
try {
|
||||
const templateName = `pages/${relativePath.replace(/\.html$/, '.njk')}`.replace(
|
||||
/\\/g,
|
||||
'/'
|
||||
);
|
||||
|
||||
const html = env.render(templateName, {
|
||||
url: requestUrl.pathname,
|
||||
query: Object.fromEntries(requestUrl.searchParams.entries()),
|
||||
mode: server.config.mode,
|
||||
});
|
||||
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.end(html);
|
||||
return;
|
||||
} catch (error) {
|
||||
res.statusCode = 500;
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.end(
|
||||
`Failed to render template: ${relativePath}\n\n${
|
||||
error instanceof Error ? error.stack : error
|
||||
}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no extension, try adding .html
|
||||
if (!path.extname(targetPath) && !fs.existsSync(targetPath)) {
|
||||
targetPath = targetPath + '.html';
|
||||
}
|
||||
const filePath = path.join(publicDir, pathname);
|
||||
|
||||
// Serve the file if it exists
|
||||
if (fs.existsSync(targetPath)) {
|
||||
const content = fs.readFileSync(targetPath, 'utf-8');
|
||||
if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) {
|
||||
const indexPath = path.join(filePath, 'index.html');
|
||||
if (fs.existsSync(indexPath)) {
|
||||
const content = fs.readFileSync(indexPath, 'utf-8');
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.end(content);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (fs.existsSync(filePath)) {
|
||||
const contentType = path.extname(filePath) === '.js' ? 'application/javascript' : 'text/html';
|
||||
res.setHeader('Content-Type', contentType);
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
res.end(content);
|
||||
return;
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user