@intranefr/superbackend 1.5.3 → 1.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/cookies.txt +6 -0
  2. package/cookies1.txt +6 -0
  3. package/cookies2.txt +6 -0
  4. package/cookies3.txt +6 -0
  5. package/cookies4.txt +5 -0
  6. package/cookies_old.txt +5 -0
  7. package/cookies_old_test.txt +6 -0
  8. package/cookies_super.txt +5 -0
  9. package/cookies_super_test.txt +6 -0
  10. package/cookies_test.txt +6 -0
  11. package/index.js +7 -0
  12. package/package.json +3 -1
  13. package/plugins/core-waiting-list-migration/README.md +118 -0
  14. package/plugins/core-waiting-list-migration/index.js +438 -0
  15. package/plugins/global-settings-presets/index.js +20 -0
  16. package/plugins/hello-cli/index.js +17 -0
  17. package/plugins/ui-components-seeder/components/suiAlert.js +212 -0
  18. package/plugins/ui-components-seeder/components/suiToast.js +186 -0
  19. package/plugins/ui-components-seeder/index.js +31 -0
  20. package/public/js/admin-ui-components-preview.js +281 -0
  21. package/public/js/admin-ui-components.js +408 -0
  22. package/public/js/llm-provider-model-picker.js +193 -0
  23. package/public/test-iframe-fix.html +63 -0
  24. package/public/test-iframe.html +14 -0
  25. package/src/admin/endpointRegistry.js +68 -0
  26. package/src/controllers/admin.controller.js +25 -5
  27. package/src/controllers/adminDataCleanup.controller.js +45 -0
  28. package/src/controllers/adminLlm.controller.js +0 -8
  29. package/src/controllers/adminLogin.controller.js +269 -0
  30. package/src/controllers/adminPlugins.controller.js +55 -0
  31. package/src/controllers/adminRegistry.controller.js +106 -0
  32. package/src/controllers/adminStats.controller.js +4 -4
  33. package/src/controllers/registry.controller.js +32 -0
  34. package/src/controllers/waitingList.controller.js +52 -74
  35. package/src/middleware/auth.js +71 -1
  36. package/src/middleware/rbac.js +62 -0
  37. package/src/middleware.js +480 -156
  38. package/src/models/GlobalSetting.js +11 -1
  39. package/src/models/UiComponent.js +2 -0
  40. package/src/models/User.js +1 -1
  41. package/src/routes/admin.routes.js +3 -3
  42. package/src/routes/adminAgents.routes.js +2 -2
  43. package/src/routes/adminAssets.routes.js +11 -11
  44. package/src/routes/adminBlog.routes.js +2 -2
  45. package/src/routes/adminBlogAi.routes.js +2 -2
  46. package/src/routes/adminBlogAutomation.routes.js +2 -2
  47. package/src/routes/adminCache.routes.js +2 -2
  48. package/src/routes/adminConsoleManager.routes.js +2 -2
  49. package/src/routes/adminCrons.routes.js +2 -2
  50. package/src/routes/adminDataCleanup.routes.js +26 -0
  51. package/src/routes/adminDbBrowser.routes.js +2 -2
  52. package/src/routes/adminEjsVirtual.routes.js +2 -2
  53. package/src/routes/adminFeatureFlags.routes.js +6 -6
  54. package/src/routes/adminHeadless.routes.js +2 -2
  55. package/src/routes/adminHealthChecks.routes.js +2 -2
  56. package/src/routes/adminI18n.routes.js +2 -2
  57. package/src/routes/adminJsonConfigs.routes.js +8 -8
  58. package/src/routes/adminLlm.routes.js +8 -8
  59. package/src/routes/adminLogin.routes.js +23 -0
  60. package/src/routes/adminMarkdowns.routes.js +3 -9
  61. package/src/routes/adminMigration.routes.js +12 -12
  62. package/src/routes/adminPages.routes.js +2 -2
  63. package/src/routes/adminPlugins.routes.js +15 -0
  64. package/src/routes/adminProxy.routes.js +2 -2
  65. package/src/routes/adminRateLimits.routes.js +8 -8
  66. package/src/routes/adminRbac.routes.js +2 -2
  67. package/src/routes/adminRegistry.routes.js +24 -0
  68. package/src/routes/adminScripts.routes.js +2 -2
  69. package/src/routes/adminSeoConfig.routes.js +10 -10
  70. package/src/routes/adminTelegram.routes.js +2 -2
  71. package/src/routes/adminTerminals.routes.js +2 -2
  72. package/src/routes/adminUiComponents.routes.js +2 -2
  73. package/src/routes/adminUploadNamespaces.routes.js +7 -7
  74. package/src/routes/blogInternal.routes.js +2 -2
  75. package/src/routes/experiments.routes.js +2 -2
  76. package/src/routes/formsAdmin.routes.js +6 -6
  77. package/src/routes/globalSettings.routes.js +8 -8
  78. package/src/routes/internalExperiments.routes.js +2 -2
  79. package/src/routes/notificationAdmin.routes.js +7 -7
  80. package/src/routes/orgAdmin.routes.js +16 -16
  81. package/src/routes/pages.routes.js +3 -3
  82. package/src/routes/registry.routes.js +11 -0
  83. package/src/routes/stripeAdmin.routes.js +12 -12
  84. package/src/routes/userAdmin.routes.js +7 -7
  85. package/src/routes/waitingListAdmin.routes.js +2 -2
  86. package/src/routes/workflows.routes.js +3 -3
  87. package/src/services/dataCleanup.service.js +286 -0
  88. package/src/services/jsonConfigs.service.js +262 -0
  89. package/src/services/plugins.service.js +348 -0
  90. package/src/services/registry.service.js +452 -0
  91. package/src/services/uiComponents.service.js +180 -0
  92. package/src/services/waitingListJson.service.js +401 -0
  93. package/src/utils/rbac/rightsRegistry.js +118 -0
  94. package/test-access.js +63 -0
  95. package/test-iframe-fix.html +63 -0
  96. package/test-iframe.html +14 -0
  97. package/views/admin-403.ejs +92 -0
  98. package/views/admin-dashboard-home.ejs +52 -2
  99. package/views/admin-dashboard.ejs +143 -2
  100. package/views/admin-data-cleanup.ejs +357 -0
  101. package/views/admin-login.ejs +286 -0
  102. package/views/admin-plugins-system.ejs +223 -0
  103. package/views/admin-ui-components.ejs +82 -402
  104. package/views/admin-users.ejs +207 -11
  105. package/views/partials/dashboard/nav-items.ejs +2 -0
  106. package/views/partials/llm-provider-model-picker.ejs +0 -161
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>UI Components - Admin</title>
7
7
  <script src="https://cdn.tailwindcss.com"></script>
8
- <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
8
+ <script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
9
9
  </head>
10
10
  <body class="bg-gray-100">
11
11
  <div id="app" class="min-h-screen">
@@ -332,410 +332,90 @@ curl "{{ baseUrl }}/api/ui-components/manifest/prj_yourproject" \
332
332
  </div>
333
333
  </div>
334
334
  </div>
335
+
336
+ <div class="bg-white rounded-lg shadow p-4 space-y-4" ref="previewContainerRef">
337
+ <div class="flex flex-wrap items-center justify-between gap-2">
338
+ <div>
339
+ <h2 class="font-semibold text-gray-800">Preview & Test</h2>
340
+ <p class="text-xs text-gray-500">Test current editor content without saving. Default mode: iframe.</p>
341
+ </div>
342
+ <div class="flex items-center gap-2">
343
+ <button @click="runPreview" class="text-xs px-3 py-2 rounded bg-blue-600 hover:bg-blue-700 text-white">Run Preview</button>
344
+ <button @click="resetPreview" class="text-xs px-3 py-2 rounded bg-gray-100 hover:bg-gray-200">Reset</button>
345
+ <button @click="togglePreviewFullscreen" class="text-xs px-3 py-2 rounded bg-gray-800 hover:bg-gray-900 text-white">
346
+ {{ previewFullscreen ? 'Exit Fullscreen (Esc)' : 'Fullscreen' }}
347
+ </button>
348
+ </div>
349
+ </div>
350
+
351
+ <div class="grid grid-cols-1 lg:grid-cols-4 gap-3">
352
+ <div>
353
+ <label class="block text-xs font-medium text-gray-600 mb-1">Mode</label>
354
+ <select v-model="previewMode" class="w-full border rounded px-2 py-2 text-sm">
355
+ <option value="iframe">iframe (dynamic)</option>
356
+ <option value="top">top-frame (isolated)</option>
357
+ <option value="top-no-isolation">top-frame (not isolated)</option>
358
+ </select>
359
+ </div>
360
+ <div>
361
+ <label class="block text-xs font-medium text-gray-600 mb-1">CSS Isolation</label>
362
+ <select v-model="previewCssIsolation" class="w-full border rounded px-2 py-2 text-sm">
363
+ <option value="scoped">scoped</option>
364
+ <option value="shadow">shadow</option>
365
+ </select>
366
+ </div>
367
+ <div class="lg:col-span-2">
368
+ <label class="block text-xs font-medium text-gray-600 mb-1">Props JSON</label>
369
+ <input v-model="previewPropsJson" class="w-full border rounded px-2 py-2 text-sm font-mono" placeholder='{"message":"Hello"}' />
370
+ </div>
371
+ </div>
372
+
373
+ <div class="text-xs text-gray-500">
374
+ Status: <span class="font-medium text-gray-700">{{ previewStatus }}</span>
375
+ <span class="ml-2">Use the API runner below with flexible JS commands.</span>
376
+ </div>
377
+
378
+ <div class="border border-gray-200 rounded overflow-hidden">
379
+ <div v-show="previewMode !== 'iframe'" class="h-[380px] bg-gray-50">
380
+ <div ref="previewTopMountRef" class="h-full w-full"></div>
381
+ </div>
382
+ <div v-show="previewMode === 'iframe'" class="h-[380px] bg-gray-50">
383
+ <iframe
384
+ ref="previewIframeRef"
385
+ title="UI Component Preview"
386
+ class="w-full h-full border-0"
387
+ sandbox="allow-scripts allow-same-origin"
388
+ ></iframe>
389
+ </div>
390
+ </div>
391
+
392
+ <div class="border border-gray-200 rounded p-3 space-y-2">
393
+ <div class="flex items-center justify-between">
394
+ <h3 class="font-semibold text-sm text-gray-800">JS API Runner</h3>
395
+ <div class="flex gap-2">
396
+ <button @click="setPreviewCommandExample('create')" class="text-[11px] px-2 py-1 rounded bg-gray-100 hover:bg-gray-200">Example: create</button>
397
+ <button @click="setPreviewCommandExample('call')" class="text-[11px] px-2 py-1 rounded bg-gray-100 hover:bg-gray-200">Example: call method</button>
398
+ <button @click="setPreviewCommandExample('destroy')" class="text-[11px] px-2 py-1 rounded bg-gray-100 hover:bg-gray-200">Example: destroy</button>
399
+ </div>
400
+ </div>
401
+ <textarea v-model="previewCommand" rows="4" class="w-full border rounded px-2 py-2 text-xs font-mono" placeholder="await uiCmp.create({ message: 'Hi from preview' })"></textarea>
402
+ <div class="flex gap-2">
403
+ <button @click="runPreviewCommand" class="text-xs px-3 py-2 rounded bg-purple-600 hover:bg-purple-700 text-white">Run command</button>
404
+ <button @click="clearPreviewLogs" class="text-xs px-3 py-2 rounded bg-gray-100 hover:bg-gray-200">Clear logs</button>
405
+ </div>
406
+ <pre class="text-[11px] bg-gray-50 border border-gray-200 rounded p-2 whitespace-pre-wrap break-words max-h-56 overflow-auto">{{ previewLogsText }}</pre>
407
+ </div>
408
+ </div>
335
409
  </div>
336
410
  </div>
337
-
338
411
  <script>
339
- const { createApp, ref, onMounted } = Vue;
340
-
341
- createApp({
342
- setup() {
343
- const baseUrl = '<%= baseUrl %>';
344
- const adminPath = '<%= adminPath %>';
345
- const API_BASE = window.location.origin + baseUrl;
346
-
347
- const STORAGE_KEYS = {
348
- providerKey: 'uiComponents.ai.providerKey',
349
- model: 'uiComponents.ai.model',
350
- helpOpen: 'uiComponents.help.open',
351
- };
352
-
353
- const toast = ref({ show: false, message: '', type: 'success' });
354
- let toastTimer = null;
355
-
356
- function showToast(message, type) {
357
- toast.value = { show: true, message, type: type || 'success' };
358
- if (toastTimer) clearTimeout(toastTimer);
359
- toastTimer = setTimeout(() => {
360
- toast.value.show = false;
361
- }, 2500);
362
- }
363
-
364
- async function api(path, options) {
365
- const res = await fetch(baseUrl + path, {
366
- method: (options && options.method) || 'GET',
367
- headers: {
368
- 'Content-Type': 'application/json',
369
- },
370
- body: options && options.body ? JSON.stringify(options.body) : undefined,
371
- });
372
-
373
- const text = await res.text();
374
- let data = null;
375
- try { data = text ? JSON.parse(text) : null; } catch { data = null; }
376
-
377
- if (!res.ok) {
378
- const msg = (data && data.error) ? data.error : ('Request failed: ' + res.status);
379
- throw new Error(msg);
380
- }
381
-
382
- return data;
383
- }
384
-
385
- const helpOpen = ref(false);
386
-
387
- const projects = ref([]);
388
- const components = ref([]);
389
- const selectedProject = ref(null);
390
- const assignments = ref([]);
391
- const lastGeneratedKey = ref('');
392
-
393
- const ai = ref({
394
- providerKey: localStorage.getItem(STORAGE_KEYS.providerKey) || '',
395
- model: localStorage.getItem(STORAGE_KEYS.model) || '',
396
- prompt: '',
397
- mode: 'minimal',
398
- targets: { html: true, css: true, js: true, usageMarkdown: true },
399
- });
400
- const aiLoading = ref(false);
401
- const aiProposal = ref(null);
402
- const aiWarnings = ref([]);
403
-
404
- const newProject = ref({ name: '', projectId: '', isPublic: true });
405
-
406
- const componentEditor = ref({ code: '', name: '', html: '', js: '', css: '', usageMarkdown: '' });
407
-
408
- function loadHelpState() {
409
- try {
410
- const raw = localStorage.getItem(STORAGE_KEYS.helpOpen);
411
- if (raw === '1') helpOpen.value = true;
412
- if (raw === '0') helpOpen.value = false;
413
- } catch {}
414
- }
415
-
416
- function persistHelpState() {
417
- try {
418
- localStorage.setItem(STORAGE_KEYS.helpOpen, helpOpen.value ? '1' : '0');
419
- } catch {}
420
- }
421
-
422
- function toggleHelp() {
423
- helpOpen.value = !helpOpen.value;
424
- persistHelpState();
425
- }
426
-
427
- function syncAiPickerToVue() {
428
- const providerEl = document.getElementById('uiComponentsAiProviderKey');
429
- const modelEl = document.getElementById('uiComponentsAiModel');
430
- if (providerEl) providerEl.value = String(ai.value.providerKey || '');
431
- if (modelEl) modelEl.value = String(ai.value.model || '');
432
- }
433
-
434
- function wireAiPickerListeners() {
435
- const providerEl = document.getElementById('uiComponentsAiProviderKey');
436
- const modelEl = document.getElementById('uiComponentsAiModel');
437
- if (!providerEl || !modelEl) return;
438
-
439
- if (providerEl.dataset.wired === '1') return;
440
- providerEl.dataset.wired = '1';
441
- modelEl.dataset.wired = '1';
442
-
443
- providerEl.addEventListener('input', () => {
444
- ai.value.providerKey = String(providerEl.value || '');
445
- persistAiSettings();
446
- });
447
- providerEl.addEventListener('change', () => {
448
- ai.value.providerKey = String(providerEl.value || '');
449
- persistAiSettings();
450
- });
451
-
452
- modelEl.addEventListener('input', () => {
453
- ai.value.model = String(modelEl.value || '');
454
- persistAiSettings();
455
- });
456
- modelEl.addEventListener('change', () => {
457
- ai.value.model = String(modelEl.value || '');
458
- persistAiSettings();
459
- });
460
- }
461
-
462
- async function initAiPicker() {
463
- if (!window.__llmProviderModelPicker || !window.__llmProviderModelPicker.init) return;
464
- await window.__llmProviderModelPicker.init({
465
- apiBase: API_BASE,
466
- providerInputId: 'uiComponentsAiProviderKey',
467
- modelInputId: 'uiComponentsAiModel',
468
- });
469
- syncAiPickerToVue();
470
- wireAiPickerListeners();
471
- }
472
-
473
- async function loadLlmConfig() {
474
- try {
475
- await initAiPicker();
476
- showToast('LLM config reloaded', 'success');
477
- } catch (e) {
478
- showToast(e.message, 'error');
479
- }
480
- }
481
-
482
- function persistAiSettings() {
483
- try {
484
- localStorage.setItem(STORAGE_KEYS.providerKey, String(ai.value.providerKey || ''));
485
- localStorage.setItem(STORAGE_KEYS.model, String(ai.value.model || ''));
486
- } catch {}
487
- }
488
-
489
- async function aiPropose() {
490
- try {
491
- const code = String(componentEditor.value.code || '').trim().toLowerCase();
492
- if (!code) throw new Error('Select or enter a component code first');
493
- const prompt = String(ai.value.prompt || '').trim();
494
- if (!prompt) throw new Error('Prompt is required');
495
-
496
- persistAiSettings();
497
- aiLoading.value = true;
498
- aiProposal.value = null;
499
- aiWarnings.value = [];
500
-
501
- const payload = {
502
- prompt,
503
- providerKey: ai.value.providerKey || undefined,
504
- model: ai.value.model || undefined,
505
- targets: ai.value.targets,
506
- mode: ai.value.mode,
507
- };
508
-
509
- const data = await api('/api/admin/ui-components/ai/components/' + encodeURIComponent(code) + '/propose', {
510
- method: 'POST',
511
- body: payload,
512
- });
513
-
514
- aiProposal.value = data && data.proposal ? data.proposal : null;
515
- aiWarnings.value = (aiProposal.value && Array.isArray(aiProposal.value.warnings)) ? aiProposal.value.warnings : [];
516
- showToast('AI proposal ready', 'success');
517
- } catch (e) {
518
- showToast(e.message, 'error');
519
- } finally {
520
- aiLoading.value = false;
521
- }
522
- }
523
-
524
- function aiApply() {
525
- try {
526
- if (!aiProposal.value || !aiProposal.value.fields) return;
527
- const f = aiProposal.value.fields;
528
- if (f.html !== undefined) componentEditor.value.html = f.html;
529
- if (f.css !== undefined) componentEditor.value.css = f.css;
530
- if (f.js !== undefined) componentEditor.value.js = f.js;
531
- if (f.usageMarkdown !== undefined) componentEditor.value.usageMarkdown = f.usageMarkdown;
532
- showToast('Applied proposal into editor (click Save to persist)', 'success');
533
- } catch (e) {
534
- showToast(e.message, 'error');
535
- }
536
- }
537
-
538
- async function refreshProjects() {
539
- const data = await api('/api/admin/ui-components/projects');
540
- projects.value = (data && data.items) ? data.items : [];
541
- }
542
-
543
- async function refreshComponents() {
544
- const data = await api('/api/admin/ui-components/components');
545
- components.value = (data && data.items) ? data.items : [];
546
- }
547
-
548
- async function refreshAssignments(projectId) {
549
- const data = await api('/api/admin/ui-components/projects/' + encodeURIComponent(projectId) + '/components');
550
- assignments.value = (data && data.items) ? data.items : [];
551
- }
552
-
553
- async function refreshAll() {
554
- try {
555
- await Promise.all([refreshProjects(), refreshComponents()]);
556
- if (selectedProject.value) {
557
- await refreshAssignments(selectedProject.value.projectId);
558
- }
559
- showToast('Refreshed', 'success');
560
- } catch (e) {
561
- showToast(e.message, 'error');
562
- }
563
- }
564
-
565
- async function createProject() {
566
- try {
567
- lastGeneratedKey.value = '';
568
- const payload = {
569
- name: newProject.value.name,
570
- projectId: newProject.value.projectId || undefined,
571
- isPublic: Boolean(newProject.value.isPublic),
572
- };
573
- const data = await api('/api/admin/ui-components/projects', { method: 'POST', body: payload });
574
- if (data && data.apiKey) lastGeneratedKey.value = data.apiKey;
575
- newProject.value = { name: '', projectId: '', isPublic: true };
576
- await refreshProjects();
577
- showToast('Project created', 'success');
578
- } catch (e) {
579
- showToast(e.message, 'error');
580
- }
581
- }
582
-
583
- async function selectProject(p) {
584
- try {
585
- lastGeneratedKey.value = '';
586
- selectedProject.value = { ...p };
587
- await refreshAssignments(p.projectId);
588
- } catch (e) {
589
- showToast(e.message, 'error');
590
- }
591
- }
592
-
593
- function isAssigned(code) {
594
- return assignments.value.some((a) => a.componentCode === code && a.enabled);
595
- }
596
-
597
- async function toggleAssignment(code, enabled) {
598
- try {
599
- if (!selectedProject.value) return;
600
- if (enabled) {
601
- await api(
602
- '/api/admin/ui-components/projects/' + encodeURIComponent(selectedProject.value.projectId) + '/components/' + encodeURIComponent(code),
603
- { method: 'POST', body: { enabled: true } },
604
- );
605
- } else {
606
- await api(
607
- '/api/admin/ui-components/projects/' + encodeURIComponent(selectedProject.value.projectId) + '/components/' + encodeURIComponent(code),
608
- { method: 'DELETE' },
609
- );
610
- }
611
- await refreshAssignments(selectedProject.value.projectId);
612
- showToast('Updated assignment', 'success');
613
- } catch (e) {
614
- showToast(e.message, 'error');
615
- }
616
- }
617
-
618
- async function toggleProjectPublic() {
619
- try {
620
- lastGeneratedKey.value = '';
621
- const data = await api('/api/admin/ui-components/projects/' + encodeURIComponent(selectedProject.value.projectId), {
622
- method: 'PUT',
623
- body: { isPublic: Boolean(selectedProject.value.isPublic) },
624
- });
625
- if (data && data.apiKey) lastGeneratedKey.value = data.apiKey;
626
- await refreshProjects();
627
- await refreshAssignments(selectedProject.value.projectId);
628
- showToast('Project updated', 'success');
629
- } catch (e) {
630
- showToast(e.message, 'error');
631
- }
632
- }
633
-
634
- async function rotateKey() {
635
- try {
636
- lastGeneratedKey.value = '';
637
- const data = await api('/api/admin/ui-components/projects/' + encodeURIComponent(selectedProject.value.projectId) + '/rotate-key', { method: 'POST' });
638
- if (data && data.apiKey) lastGeneratedKey.value = data.apiKey;
639
- showToast('Key rotated', 'success');
640
- } catch (e) {
641
- showToast(e.message, 'error');
642
- }
643
- }
644
-
645
- function clearComponentEditor() {
646
- componentEditor.value = { code: '', name: '', html: '', js: '', css: '', usageMarkdown: '' };
647
- }
648
-
649
- async function loadComponentIntoEditor(code) {
650
- try {
651
- const data = await api('/api/admin/ui-components/components/' + encodeURIComponent(code));
652
- const c = data && data.item ? data.item : null;
653
- if (!c) return;
654
-
655
- aiProposal.value = null;
656
- aiWarnings.value = [];
657
-
658
- componentEditor.value = {
659
- code: c.code || '',
660
- name: c.name || '',
661
- html: c.html || '',
662
- js: c.js || '',
663
- css: c.css || '',
664
- usageMarkdown: c.usageMarkdown || '',
665
- };
666
- } catch (e) {
667
- showToast(e.message, 'error');
668
- }
669
- }
670
-
671
- async function saveComponent() {
672
- try {
673
- const code = String(componentEditor.value.code || '').trim().toLowerCase();
674
- if (!code) throw new Error('code is required');
675
- const payload = {
676
- code,
677
- name: componentEditor.value.name,
678
- html: componentEditor.value.html,
679
- js: componentEditor.value.js,
680
- css: componentEditor.value.css,
681
- usageMarkdown: componentEditor.value.usageMarkdown,
682
- };
683
-
684
- const existing = components.value.find((c) => c.code === code);
685
- if (existing) {
686
- await api('/api/admin/ui-components/components/' + encodeURIComponent(code), { method: 'PUT', body: payload });
687
- } else {
688
- await api('/api/admin/ui-components/components', { method: 'POST', body: payload });
689
- }
690
-
691
- await refreshComponents();
692
- if (selectedProject.value) await refreshAssignments(selectedProject.value.projectId);
693
- showToast('Component saved', 'success');
694
- } catch (e) {
695
- showToast(e.message, 'error');
696
- }
697
- }
698
-
699
- onMounted(async () => {
700
- try {
701
- loadHelpState();
702
- await refreshAll();
703
- await initAiPicker();
704
- } catch (e) {
705
- showToast(e.message, 'error');
706
- }
707
- });
708
-
709
- return {
710
- baseUrl,
711
- adminPath,
712
- toast,
713
- helpOpen,
714
- projects,
715
- components,
716
- selectedProject,
717
- assignments,
718
- lastGeneratedKey,
719
- newProject,
720
- componentEditor,
721
- toggleHelp,
722
- ai,
723
- aiLoading,
724
- aiProposal,
725
- aiWarnings,
726
- refreshAll,
727
- createProject,
728
- selectProject,
729
- isAssigned,
730
- toggleAssignment,
731
- toggleProjectPublic,
732
- rotateKey,
733
- clearComponentEditor,
734
- loadComponentIntoEditor,
735
- saveComponent,
736
- };
737
- },
738
- }).mount('#app');
412
+ window.__adminUiComponentsConfig = {
413
+ baseUrl: '<%= baseUrl %>',
414
+ adminPath: '<%= adminPath %>',
415
+ };
739
416
  </script>
417
+ <script src="<%= baseUrl %>/public/js/admin-ui-components-preview.js"></script>
418
+ <script src="<%= baseUrl %>/public/js/llm-provider-model-picker.js"></script>
419
+ <script src="<%= baseUrl %>/public/js/admin-ui-components.js"></script>
740
420
  </body>
741
421
  </html>