@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.
- package/cookies.txt +6 -0
- package/cookies1.txt +6 -0
- package/cookies2.txt +6 -0
- package/cookies3.txt +6 -0
- package/cookies4.txt +5 -0
- package/cookies_old.txt +5 -0
- package/cookies_old_test.txt +6 -0
- package/cookies_super.txt +5 -0
- package/cookies_super_test.txt +6 -0
- package/cookies_test.txt +6 -0
- package/index.js +7 -0
- package/package.json +3 -1
- package/plugins/core-waiting-list-migration/README.md +118 -0
- package/plugins/core-waiting-list-migration/index.js +438 -0
- package/plugins/global-settings-presets/index.js +20 -0
- package/plugins/hello-cli/index.js +17 -0
- package/plugins/ui-components-seeder/components/suiAlert.js +212 -0
- package/plugins/ui-components-seeder/components/suiToast.js +186 -0
- package/plugins/ui-components-seeder/index.js +31 -0
- package/public/js/admin-ui-components-preview.js +281 -0
- package/public/js/admin-ui-components.js +408 -0
- package/public/js/llm-provider-model-picker.js +193 -0
- package/public/test-iframe-fix.html +63 -0
- package/public/test-iframe.html +14 -0
- package/src/admin/endpointRegistry.js +68 -0
- package/src/controllers/admin.controller.js +25 -5
- package/src/controllers/adminDataCleanup.controller.js +45 -0
- package/src/controllers/adminLlm.controller.js +0 -8
- package/src/controllers/adminLogin.controller.js +269 -0
- package/src/controllers/adminPlugins.controller.js +55 -0
- package/src/controllers/adminRegistry.controller.js +106 -0
- package/src/controllers/adminStats.controller.js +4 -4
- package/src/controllers/registry.controller.js +32 -0
- package/src/controllers/waitingList.controller.js +52 -74
- package/src/middleware/auth.js +71 -1
- package/src/middleware/rbac.js +62 -0
- package/src/middleware.js +480 -156
- package/src/models/GlobalSetting.js +11 -1
- package/src/models/UiComponent.js +2 -0
- package/src/models/User.js +1 -1
- package/src/routes/admin.routes.js +3 -3
- package/src/routes/adminAgents.routes.js +2 -2
- package/src/routes/adminAssets.routes.js +11 -11
- package/src/routes/adminBlog.routes.js +2 -2
- package/src/routes/adminBlogAi.routes.js +2 -2
- package/src/routes/adminBlogAutomation.routes.js +2 -2
- package/src/routes/adminCache.routes.js +2 -2
- package/src/routes/adminConsoleManager.routes.js +2 -2
- package/src/routes/adminCrons.routes.js +2 -2
- package/src/routes/adminDataCleanup.routes.js +26 -0
- package/src/routes/adminDbBrowser.routes.js +2 -2
- package/src/routes/adminEjsVirtual.routes.js +2 -2
- package/src/routes/adminFeatureFlags.routes.js +6 -6
- package/src/routes/adminHeadless.routes.js +2 -2
- package/src/routes/adminHealthChecks.routes.js +2 -2
- package/src/routes/adminI18n.routes.js +2 -2
- package/src/routes/adminJsonConfigs.routes.js +8 -8
- package/src/routes/adminLlm.routes.js +8 -8
- package/src/routes/adminLogin.routes.js +23 -0
- package/src/routes/adminMarkdowns.routes.js +3 -9
- package/src/routes/adminMigration.routes.js +12 -12
- package/src/routes/adminPages.routes.js +2 -2
- package/src/routes/adminPlugins.routes.js +15 -0
- package/src/routes/adminProxy.routes.js +2 -2
- package/src/routes/adminRateLimits.routes.js +8 -8
- package/src/routes/adminRbac.routes.js +2 -2
- package/src/routes/adminRegistry.routes.js +24 -0
- package/src/routes/adminScripts.routes.js +2 -2
- package/src/routes/adminSeoConfig.routes.js +10 -10
- package/src/routes/adminTelegram.routes.js +2 -2
- package/src/routes/adminTerminals.routes.js +2 -2
- package/src/routes/adminUiComponents.routes.js +2 -2
- package/src/routes/adminUploadNamespaces.routes.js +7 -7
- package/src/routes/blogInternal.routes.js +2 -2
- package/src/routes/experiments.routes.js +2 -2
- package/src/routes/formsAdmin.routes.js +6 -6
- package/src/routes/globalSettings.routes.js +8 -8
- package/src/routes/internalExperiments.routes.js +2 -2
- package/src/routes/notificationAdmin.routes.js +7 -7
- package/src/routes/orgAdmin.routes.js +16 -16
- package/src/routes/pages.routes.js +3 -3
- package/src/routes/registry.routes.js +11 -0
- package/src/routes/stripeAdmin.routes.js +12 -12
- package/src/routes/userAdmin.routes.js +7 -7
- package/src/routes/waitingListAdmin.routes.js +2 -2
- package/src/routes/workflows.routes.js +3 -3
- package/src/services/dataCleanup.service.js +286 -0
- package/src/services/jsonConfigs.service.js +262 -0
- package/src/services/plugins.service.js +348 -0
- package/src/services/registry.service.js +452 -0
- package/src/services/uiComponents.service.js +180 -0
- package/src/services/waitingListJson.service.js +401 -0
- package/src/utils/rbac/rightsRegistry.js +118 -0
- package/test-access.js +63 -0
- package/test-iframe-fix.html +63 -0
- package/test-iframe.html +14 -0
- package/views/admin-403.ejs +92 -0
- package/views/admin-dashboard-home.ejs +52 -2
- package/views/admin-dashboard.ejs +143 -2
- package/views/admin-data-cleanup.ejs +357 -0
- package/views/admin-login.ejs +286 -0
- package/views/admin-plugins-system.ejs +223 -0
- package/views/admin-ui-components.ejs +82 -402
- package/views/admin-users.ejs +207 -11
- package/views/partials/dashboard/nav-items.ejs +2 -0
- package/views/partials/llm-provider-model-picker.ejs +0 -161
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
function createManager(opts) {
|
|
3
|
+
const {
|
|
4
|
+
nextTick,
|
|
5
|
+
previewMode,
|
|
6
|
+
previewCssIsolation,
|
|
7
|
+
previewPropsJson,
|
|
8
|
+
previewStatus,
|
|
9
|
+
previewLogs,
|
|
10
|
+
previewContainerRef,
|
|
11
|
+
previewTopMountRef,
|
|
12
|
+
previewIframeRef,
|
|
13
|
+
componentEditor,
|
|
14
|
+
showToast,
|
|
15
|
+
} = opts;
|
|
16
|
+
|
|
17
|
+
let runtime = null;
|
|
18
|
+
let topHost = null;
|
|
19
|
+
|
|
20
|
+
function pushLog(...parts) {
|
|
21
|
+
const line = parts.map((p) => (typeof p === 'string' ? p : JSON.stringify(p, null, 2))).join(' ');
|
|
22
|
+
previewLogs.value = [...previewLogs.value, line].slice(-200);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function clearLogs() {
|
|
26
|
+
previewLogs.value = [];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function parseProps() {
|
|
30
|
+
const raw = String(previewPropsJson.value || '').trim();
|
|
31
|
+
if (!raw) return {};
|
|
32
|
+
return JSON.parse(raw);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getEditorComponent() {
|
|
36
|
+
const code = String(componentEditor.value.code || '').trim().toLowerCase() || 'preview_component';
|
|
37
|
+
return {
|
|
38
|
+
code,
|
|
39
|
+
html: String(componentEditor.value.html || ''),
|
|
40
|
+
css: String(componentEditor.value.css || ''),
|
|
41
|
+
js: String(componentEditor.value.js || ''),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function buildRuntime(targetWindow, mountEl, cssIsolation, code, html, css, js) {
|
|
46
|
+
let instance = null;
|
|
47
|
+
let styleEl = null;
|
|
48
|
+
let localStyles = [];
|
|
49
|
+
|
|
50
|
+
const created = {
|
|
51
|
+
async create(props, options) {
|
|
52
|
+
created.destroy();
|
|
53
|
+
const doc = targetWindow.document;
|
|
54
|
+
const root = (options && options.mountEl) || mountEl;
|
|
55
|
+
const tpl = doc.createElement('template');
|
|
56
|
+
tpl.innerHTML = String(html || '');
|
|
57
|
+
|
|
58
|
+
const host = doc.createElement('div');
|
|
59
|
+
host.dataset.previewCode = code;
|
|
60
|
+
host.appendChild(tpl.content.cloneNode(true));
|
|
61
|
+
|
|
62
|
+
// Check if we're already inside a shadow DOM (top-frame mode)
|
|
63
|
+
const existingShadowRoot = mountEl.getRootNode?.();
|
|
64
|
+
const isInShadow = existingShadowRoot instanceof targetWindow.ShadowRoot;
|
|
65
|
+
|
|
66
|
+
if (css && ((options && options.cssIsolation) === 'shadow' || cssIsolation === 'shadow')) {
|
|
67
|
+
if (isInShadow) {
|
|
68
|
+
// We're already in a shadow DOM (top-frame mode), inject CSS there
|
|
69
|
+
const style = doc.createElement('style');
|
|
70
|
+
style.textContent = String(css || '');
|
|
71
|
+
existingShadowRoot.appendChild(style);
|
|
72
|
+
localStyles.push(style);
|
|
73
|
+
root.appendChild(host);
|
|
74
|
+
instance = { rootEl: host, templateRootEl: host };
|
|
75
|
+
} else {
|
|
76
|
+
// Create new shadow DOM (iframe mode)
|
|
77
|
+
const shadowHost = doc.createElement('div');
|
|
78
|
+
const shadow = shadowHost.attachShadow({ mode: 'open' });
|
|
79
|
+
const style = doc.createElement('style');
|
|
80
|
+
style.textContent = String(css || '');
|
|
81
|
+
shadow.appendChild(style);
|
|
82
|
+
shadow.appendChild(host);
|
|
83
|
+
root.appendChild(shadowHost);
|
|
84
|
+
instance = { rootEl: shadowHost, templateRootEl: shadow };
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
if (css) {
|
|
88
|
+
if (isInShadow) {
|
|
89
|
+
// Inject CSS into existing shadow DOM for top-frame non-shadow mode
|
|
90
|
+
const style = doc.createElement('style');
|
|
91
|
+
style.textContent = String(css || '');
|
|
92
|
+
existingShadowRoot.appendChild(style);
|
|
93
|
+
localStyles.push(style);
|
|
94
|
+
} else {
|
|
95
|
+
// Inject into document head for iframe mode
|
|
96
|
+
styleEl = doc.createElement('style');
|
|
97
|
+
styleEl.textContent = String(css || '');
|
|
98
|
+
doc.head.appendChild(styleEl);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
root.appendChild(host);
|
|
102
|
+
instance = { rootEl: host, templateRootEl: host };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const api = {
|
|
106
|
+
unmount: created.destroy,
|
|
107
|
+
mountEl: root,
|
|
108
|
+
hostEl: instance.rootEl,
|
|
109
|
+
shadowRoot: instance.templateRootEl instanceof targetWindow.ShadowRoot ? instance.templateRootEl : null,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const fn = new targetWindow.Function('api', 'templateRootEl', 'props', String(js || ''));
|
|
113
|
+
const exported = fn(api, instance.templateRootEl, props || {}) || {};
|
|
114
|
+
instance.api = api;
|
|
115
|
+
instance.exported = exported;
|
|
116
|
+
return Object.assign({ api }, exported);
|
|
117
|
+
},
|
|
118
|
+
destroy() {
|
|
119
|
+
if (instance && instance.rootEl && instance.rootEl.remove) instance.rootEl.remove();
|
|
120
|
+
if (styleEl && styleEl.remove) styleEl.remove();
|
|
121
|
+
localStyles.forEach((s) => {
|
|
122
|
+
if (s && s.remove) s.remove();
|
|
123
|
+
});
|
|
124
|
+
localStyles = [];
|
|
125
|
+
instance = null;
|
|
126
|
+
styleEl = null;
|
|
127
|
+
},
|
|
128
|
+
get instance() {
|
|
129
|
+
return instance;
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
return created;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function cleanup() {
|
|
137
|
+
if (runtime) runtime.destroy();
|
|
138
|
+
runtime = null;
|
|
139
|
+
if (previewTopMountRef.value) previewTopMountRef.value.innerHTML = '';
|
|
140
|
+
if (previewIframeRef.value && previewMode.value === 'iframe') {
|
|
141
|
+
const doc = previewIframeRef.value.contentDocument;
|
|
142
|
+
if (doc && doc.body) doc.body.innerHTML = '';
|
|
143
|
+
}
|
|
144
|
+
topHost = null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function buildTopRuntime() {
|
|
148
|
+
await nextTick();
|
|
149
|
+
if (!previewTopMountRef.value) throw new Error('Top preview mount not ready');
|
|
150
|
+
|
|
151
|
+
previewTopMountRef.value.innerHTML = '';
|
|
152
|
+
topHost = document.createElement('div');
|
|
153
|
+
topHost.className = 'h-full w-full';
|
|
154
|
+
const shadow = topHost.attachShadow({ mode: 'open' });
|
|
155
|
+
const wrapper = document.createElement('div');
|
|
156
|
+
wrapper.style.height = '100%';
|
|
157
|
+
wrapper.style.padding = '12px';
|
|
158
|
+
wrapper.style.overflow = 'auto';
|
|
159
|
+
const mount = document.createElement('div');
|
|
160
|
+
wrapper.appendChild(mount);
|
|
161
|
+
shadow.appendChild(wrapper);
|
|
162
|
+
previewTopMountRef.value.appendChild(topHost);
|
|
163
|
+
|
|
164
|
+
const c = getEditorComponent();
|
|
165
|
+
runtime = buildRuntime(window, mount, previewCssIsolation.value, c.code, c.html, c.css, c.js);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function buildTopNoIsolationRuntime() {
|
|
169
|
+
await nextTick();
|
|
170
|
+
if (!previewTopMountRef.value) throw new Error('Top preview mount not ready');
|
|
171
|
+
|
|
172
|
+
previewTopMountRef.value.innerHTML = '';
|
|
173
|
+
const mount = document.createElement('div');
|
|
174
|
+
mount.className = 'h-full w-full p-3 overflow-auto';
|
|
175
|
+
previewTopMountRef.value.appendChild(mount);
|
|
176
|
+
|
|
177
|
+
const c = getEditorComponent();
|
|
178
|
+
runtime = buildRuntime(window, mount, previewCssIsolation.value, c.code, c.html, c.css, c.js);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function buildIframeRuntime() {
|
|
182
|
+
await nextTick();
|
|
183
|
+
const iframe = previewIframeRef.value;
|
|
184
|
+
if (!iframe) throw new Error('Iframe preview not ready');
|
|
185
|
+
|
|
186
|
+
iframe.srcdoc = '<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"></head><body style="margin:0;padding:12px;background:#f9fafb;"><div id="preview-root"></div></body></html>';
|
|
187
|
+
await new Promise((resolve) => { iframe.onload = () => resolve(); });
|
|
188
|
+
|
|
189
|
+
const win = iframe.contentWindow;
|
|
190
|
+
const doc = iframe.contentDocument;
|
|
191
|
+
const mount = doc.getElementById('preview-root');
|
|
192
|
+
const c = getEditorComponent();
|
|
193
|
+
runtime = buildRuntime(win, mount, previewCssIsolation.value, c.code, c.html, c.css, c.js);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function run() {
|
|
197
|
+
try {
|
|
198
|
+
previewStatus.value = 'running';
|
|
199
|
+
cleanup();
|
|
200
|
+
|
|
201
|
+
if (!String(componentEditor.value.html || '').trim() && !String(componentEditor.value.js || '').trim()) {
|
|
202
|
+
previewStatus.value = 'idle';
|
|
203
|
+
pushLog('[preview] editor empty');
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (previewMode.value === 'top') {
|
|
208
|
+
await buildTopRuntime();
|
|
209
|
+
} else if (previewMode.value === 'top-no-isolation') {
|
|
210
|
+
await buildTopNoIsolationRuntime();
|
|
211
|
+
} else {
|
|
212
|
+
await buildIframeRuntime();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const instance = await runtime.create(parseProps(), { cssIsolation: previewCssIsolation.value });
|
|
216
|
+
pushLog('[preview] mounted', { mode: previewMode.value, code: getEditorComponent().code, methods: Object.keys(instance || {}) });
|
|
217
|
+
previewStatus.value = 'ready';
|
|
218
|
+
} catch (e) {
|
|
219
|
+
previewStatus.value = 'error';
|
|
220
|
+
pushLog('[preview:error]', e.message);
|
|
221
|
+
showToast(e.message, 'error');
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function reset() {
|
|
226
|
+
cleanup();
|
|
227
|
+
previewStatus.value = 'idle';
|
|
228
|
+
pushLog('[preview] reset');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function apiFacade() {
|
|
232
|
+
return {
|
|
233
|
+
create: async (props, options) => {
|
|
234
|
+
if (!runtime) throw new Error('Preview runtime not initialized. Click Run Preview first.');
|
|
235
|
+
return runtime.create(props || {}, options || { cssIsolation: previewCssIsolation.value });
|
|
236
|
+
},
|
|
237
|
+
destroy: () => {
|
|
238
|
+
if (runtime) runtime.destroy();
|
|
239
|
+
return true;
|
|
240
|
+
},
|
|
241
|
+
getInstance: () => (runtime ? runtime.instance : null),
|
|
242
|
+
mode: previewMode.value,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function runCommand(commandText) {
|
|
247
|
+
if (!runtime) await run();
|
|
248
|
+
const cmd = String(commandText || '').trim();
|
|
249
|
+
if (!cmd) return undefined;
|
|
250
|
+
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
|
|
251
|
+
const runner = new AsyncFunction('uiCmp', 'log', 'state', cmd);
|
|
252
|
+
const result = await runner(apiFacade(), (...args) => pushLog('[cmd]', ...args), {
|
|
253
|
+
mode: previewMode.value,
|
|
254
|
+
cssIsolation: previewCssIsolation.value,
|
|
255
|
+
});
|
|
256
|
+
if (result !== undefined) pushLog('[result]', result);
|
|
257
|
+
return result;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function toggleFullscreen() {
|
|
261
|
+
const el = previewContainerRef.value;
|
|
262
|
+
if (!el) return;
|
|
263
|
+
if (!document.fullscreenElement) await el.requestFullscreen();
|
|
264
|
+
else await document.exitFullscreen();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
run,
|
|
269
|
+
reset,
|
|
270
|
+
cleanup,
|
|
271
|
+
clearLogs,
|
|
272
|
+
pushLog,
|
|
273
|
+
runCommand,
|
|
274
|
+
toggleFullscreen,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
window.AdminUiComponentsPreview = {
|
|
279
|
+
createManager,
|
|
280
|
+
};
|
|
281
|
+
})();
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
const { createApp, ref, computed, onMounted, onBeforeUnmount, nextTick, watch } = Vue;
|
|
3
|
+
|
|
4
|
+
function withToast(fn, showToast) {
|
|
5
|
+
return async (...args) => {
|
|
6
|
+
try {
|
|
7
|
+
return await fn(...args);
|
|
8
|
+
} catch (e) {
|
|
9
|
+
showToast(e.message, 'error');
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
createApp({
|
|
16
|
+
setup() {
|
|
17
|
+
const cfg = window.__adminUiComponentsConfig || {};
|
|
18
|
+
const baseUrl = String(cfg.baseUrl || '');
|
|
19
|
+
const adminPath = String(cfg.adminPath || '/admin');
|
|
20
|
+
const API_BASE = window.location.origin + baseUrl;
|
|
21
|
+
|
|
22
|
+
const STORAGE_KEYS = {
|
|
23
|
+
providerKey: 'uiComponents.ai.providerKey',
|
|
24
|
+
model: 'uiComponents.ai.model',
|
|
25
|
+
helpOpen: 'uiComponents.help.open',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const toast = ref({ show: false, message: '', type: 'success' });
|
|
29
|
+
let toastTimer = null;
|
|
30
|
+
const showToast = (message, type) => {
|
|
31
|
+
toast.value = { show: true, message: String(message || ''), type: type || 'success' };
|
|
32
|
+
if (toastTimer) clearTimeout(toastTimer);
|
|
33
|
+
toastTimer = setTimeout(() => {
|
|
34
|
+
toast.value.show = false;
|
|
35
|
+
}, 2500);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const api = async (path, options) => {
|
|
39
|
+
const res = await fetch(baseUrl + path, {
|
|
40
|
+
method: (options && options.method) || 'GET',
|
|
41
|
+
headers: { 'Content-Type': 'application/json' },
|
|
42
|
+
body: options && options.body ? JSON.stringify(options.body) : undefined,
|
|
43
|
+
});
|
|
44
|
+
const text = await res.text();
|
|
45
|
+
let data = null;
|
|
46
|
+
try { data = text ? JSON.parse(text) : null; } catch { data = null; }
|
|
47
|
+
if (!res.ok) throw new Error((data && data.error) || ('Request failed: ' + res.status));
|
|
48
|
+
return data;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const helpOpen = ref(false);
|
|
52
|
+
const projects = ref([]);
|
|
53
|
+
const components = ref([]);
|
|
54
|
+
const selectedProject = ref(null);
|
|
55
|
+
const assignments = ref([]);
|
|
56
|
+
const lastGeneratedKey = ref('');
|
|
57
|
+
const newProject = ref({ name: '', projectId: '', isPublic: true });
|
|
58
|
+
const componentEditor = ref({ code: '', name: '', html: '', js: '', css: '', usageMarkdown: '' });
|
|
59
|
+
|
|
60
|
+
const ai = ref({
|
|
61
|
+
providerKey: localStorage.getItem(STORAGE_KEYS.providerKey) || '',
|
|
62
|
+
model: localStorage.getItem(STORAGE_KEYS.model) || '',
|
|
63
|
+
prompt: '',
|
|
64
|
+
mode: 'minimal',
|
|
65
|
+
targets: { html: true, css: true, js: true, usageMarkdown: true },
|
|
66
|
+
});
|
|
67
|
+
const aiLoading = ref(false);
|
|
68
|
+
const aiProposal = ref(null);
|
|
69
|
+
const aiWarnings = ref([]);
|
|
70
|
+
|
|
71
|
+
const previewMode = ref('iframe');
|
|
72
|
+
const previewCssIsolation = ref('scoped');
|
|
73
|
+
const previewPropsJson = ref('{"message":"Hello from preview"}');
|
|
74
|
+
const previewStatus = ref('idle');
|
|
75
|
+
const previewLogs = ref([]);
|
|
76
|
+
const previewCommand = ref('const i = await uiCmp.create({ message: "Hello" });\nlog("instance", i);');
|
|
77
|
+
const previewFullscreen = ref(false);
|
|
78
|
+
const previewContainerRef = ref(null);
|
|
79
|
+
const previewTopMountRef = ref(null);
|
|
80
|
+
const previewIframeRef = ref(null);
|
|
81
|
+
const previewLogsText = computed(() => previewLogs.value.join('\n'));
|
|
82
|
+
|
|
83
|
+
const previewManager = window.AdminUiComponentsPreview.createManager({
|
|
84
|
+
nextTick,
|
|
85
|
+
previewMode,
|
|
86
|
+
previewCssIsolation,
|
|
87
|
+
previewPropsJson,
|
|
88
|
+
previewStatus,
|
|
89
|
+
previewLogs,
|
|
90
|
+
previewContainerRef,
|
|
91
|
+
previewTopMountRef,
|
|
92
|
+
previewIframeRef,
|
|
93
|
+
componentEditor,
|
|
94
|
+
showToast,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const loadHelpState = () => {
|
|
98
|
+
try {
|
|
99
|
+
const raw = localStorage.getItem(STORAGE_KEYS.helpOpen);
|
|
100
|
+
if (raw === '1') helpOpen.value = true;
|
|
101
|
+
if (raw === '0') helpOpen.value = false;
|
|
102
|
+
} catch {}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const toggleHelp = () => {
|
|
106
|
+
helpOpen.value = !helpOpen.value;
|
|
107
|
+
try { localStorage.setItem(STORAGE_KEYS.helpOpen, helpOpen.value ? '1' : '0'); } catch {}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const persistAiSettings = () => {
|
|
111
|
+
try {
|
|
112
|
+
localStorage.setItem(STORAGE_KEYS.providerKey, String(ai.value.providerKey || ''));
|
|
113
|
+
localStorage.setItem(STORAGE_KEYS.model, String(ai.value.model || ''));
|
|
114
|
+
} catch {}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const syncAiPickerToVue = () => {
|
|
118
|
+
const providerEl = document.getElementById('uiComponentsAiProviderKey');
|
|
119
|
+
const modelEl = document.getElementById('uiComponentsAiModel');
|
|
120
|
+
if (providerEl) providerEl.value = String(ai.value.providerKey || '');
|
|
121
|
+
if (modelEl) modelEl.value = String(ai.value.model || '');
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const wireAiPickerListeners = () => {
|
|
125
|
+
const providerEl = document.getElementById('uiComponentsAiProviderKey');
|
|
126
|
+
const modelEl = document.getElementById('uiComponentsAiModel');
|
|
127
|
+
if (!providerEl || !modelEl || providerEl.dataset.wired === '1') return;
|
|
128
|
+
providerEl.dataset.wired = '1';
|
|
129
|
+
modelEl.dataset.wired = '1';
|
|
130
|
+
const onProvider = () => { ai.value.providerKey = String(providerEl.value || ''); persistAiSettings(); };
|
|
131
|
+
const onModel = () => { ai.value.model = String(modelEl.value || ''); persistAiSettings(); };
|
|
132
|
+
providerEl.addEventListener('input', onProvider);
|
|
133
|
+
providerEl.addEventListener('change', onProvider);
|
|
134
|
+
modelEl.addEventListener('input', onModel);
|
|
135
|
+
modelEl.addEventListener('change', onModel);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const initAiPicker = async () => {
|
|
139
|
+
if (!window.__llmProviderModelPicker || !window.__llmProviderModelPicker.init) return;
|
|
140
|
+
await window.__llmProviderModelPicker.init({
|
|
141
|
+
apiBase: baseUrl,
|
|
142
|
+
providerInputId: 'uiComponentsAiProviderKey',
|
|
143
|
+
modelInputId: 'uiComponentsAiModel',
|
|
144
|
+
});
|
|
145
|
+
syncAiPickerToVue();
|
|
146
|
+
wireAiPickerListeners();
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const loadLlmConfig = withToast(async () => {
|
|
150
|
+
await initAiPicker();
|
|
151
|
+
showToast('LLM config reloaded', 'success');
|
|
152
|
+
}, showToast);
|
|
153
|
+
|
|
154
|
+
const refreshProjects = async () => {
|
|
155
|
+
const data = await api('/api/admin/ui-components/projects');
|
|
156
|
+
projects.value = data && data.items ? data.items : [];
|
|
157
|
+
};
|
|
158
|
+
const refreshComponents = async () => {
|
|
159
|
+
const data = await api('/api/admin/ui-components/components');
|
|
160
|
+
components.value = data && data.items ? data.items : [];
|
|
161
|
+
};
|
|
162
|
+
const refreshAssignments = async (projectId) => {
|
|
163
|
+
const data = await api('/api/admin/ui-components/projects/' + encodeURIComponent(projectId) + '/components');
|
|
164
|
+
assignments.value = data && data.items ? data.items : [];
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const refreshAllCore = async () => {
|
|
168
|
+
await Promise.all([refreshProjects(), refreshComponents()]);
|
|
169
|
+
if (selectedProject.value) await refreshAssignments(selectedProject.value.projectId);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const refreshAll = withToast(async () => {
|
|
173
|
+
await refreshAllCore();
|
|
174
|
+
showToast('Refreshed', 'success');
|
|
175
|
+
}, showToast);
|
|
176
|
+
|
|
177
|
+
const createProject = withToast(async () => {
|
|
178
|
+
lastGeneratedKey.value = '';
|
|
179
|
+
const data = await api('/api/admin/ui-components/projects', {
|
|
180
|
+
method: 'POST',
|
|
181
|
+
body: {
|
|
182
|
+
name: newProject.value.name,
|
|
183
|
+
projectId: newProject.value.projectId || undefined,
|
|
184
|
+
isPublic: Boolean(newProject.value.isPublic),
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
if (data && data.apiKey) lastGeneratedKey.value = data.apiKey;
|
|
188
|
+
newProject.value = { name: '', projectId: '', isPublic: true };
|
|
189
|
+
await refreshProjects();
|
|
190
|
+
showToast('Project created', 'success');
|
|
191
|
+
}, showToast);
|
|
192
|
+
|
|
193
|
+
const selectProject = withToast(async (p) => {
|
|
194
|
+
lastGeneratedKey.value = '';
|
|
195
|
+
selectedProject.value = { ...p };
|
|
196
|
+
await refreshAssignments(p.projectId);
|
|
197
|
+
}, showToast);
|
|
198
|
+
|
|
199
|
+
const isAssigned = (code) => assignments.value.some((a) => a.componentCode === code && a.enabled);
|
|
200
|
+
|
|
201
|
+
const toggleAssignment = withToast(async (code, enabled) => {
|
|
202
|
+
if (!selectedProject.value) return;
|
|
203
|
+
const url = '/api/admin/ui-components/projects/' + encodeURIComponent(selectedProject.value.projectId) + '/components/' + encodeURIComponent(code);
|
|
204
|
+
if (enabled) await api(url, { method: 'POST', body: { enabled: true } });
|
|
205
|
+
else await api(url, { method: 'DELETE' });
|
|
206
|
+
await refreshAssignments(selectedProject.value.projectId);
|
|
207
|
+
showToast('Updated assignment', 'success');
|
|
208
|
+
}, showToast);
|
|
209
|
+
|
|
210
|
+
const toggleProjectPublic = withToast(async () => {
|
|
211
|
+
lastGeneratedKey.value = '';
|
|
212
|
+
const data = await api('/api/admin/ui-components/projects/' + encodeURIComponent(selectedProject.value.projectId), {
|
|
213
|
+
method: 'PUT',
|
|
214
|
+
body: { isPublic: Boolean(selectedProject.value.isPublic) },
|
|
215
|
+
});
|
|
216
|
+
if (data && data.apiKey) lastGeneratedKey.value = data.apiKey;
|
|
217
|
+
await refreshAllCore();
|
|
218
|
+
showToast('Project updated', 'success');
|
|
219
|
+
}, showToast);
|
|
220
|
+
|
|
221
|
+
const rotateKey = withToast(async () => {
|
|
222
|
+
lastGeneratedKey.value = '';
|
|
223
|
+
const data = await api('/api/admin/ui-components/projects/' + encodeURIComponent(selectedProject.value.projectId) + '/rotate-key', { method: 'POST' });
|
|
224
|
+
if (data && data.apiKey) lastGeneratedKey.value = data.apiKey;
|
|
225
|
+
showToast('Key rotated', 'success');
|
|
226
|
+
}, showToast);
|
|
227
|
+
|
|
228
|
+
const clearComponentEditor = () => {
|
|
229
|
+
componentEditor.value = { code: '', name: '', html: '', js: '', css: '', usageMarkdown: '' };
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const loadComponentIntoEditor = withToast(async (code) => {
|
|
233
|
+
const data = await api('/api/admin/ui-components/components/' + encodeURIComponent(code));
|
|
234
|
+
const c = data && data.item ? data.item : null;
|
|
235
|
+
if (!c) return;
|
|
236
|
+
aiProposal.value = null;
|
|
237
|
+
aiWarnings.value = [];
|
|
238
|
+
componentEditor.value = {
|
|
239
|
+
code: c.code || '',
|
|
240
|
+
name: c.name || '',
|
|
241
|
+
html: c.html || '',
|
|
242
|
+
js: c.js || '',
|
|
243
|
+
css: c.css || '',
|
|
244
|
+
usageMarkdown: c.usageMarkdown || '',
|
|
245
|
+
};
|
|
246
|
+
await previewManager.run();
|
|
247
|
+
}, showToast);
|
|
248
|
+
|
|
249
|
+
const saveComponent = withToast(async () => {
|
|
250
|
+
const code = String(componentEditor.value.code || '').trim().toLowerCase();
|
|
251
|
+
if (!code) throw new Error('code is required');
|
|
252
|
+
const payload = {
|
|
253
|
+
code,
|
|
254
|
+
name: componentEditor.value.name,
|
|
255
|
+
html: componentEditor.value.html,
|
|
256
|
+
js: componentEditor.value.js,
|
|
257
|
+
css: componentEditor.value.css,
|
|
258
|
+
usageMarkdown: componentEditor.value.usageMarkdown,
|
|
259
|
+
};
|
|
260
|
+
if (components.value.find((c) => c.code === code)) {
|
|
261
|
+
await api('/api/admin/ui-components/components/' + encodeURIComponent(code), { method: 'PUT', body: payload });
|
|
262
|
+
} else {
|
|
263
|
+
await api('/api/admin/ui-components/components', { method: 'POST', body: payload });
|
|
264
|
+
}
|
|
265
|
+
await refreshAllCore();
|
|
266
|
+
showToast('Component saved', 'success');
|
|
267
|
+
}, showToast);
|
|
268
|
+
|
|
269
|
+
const aiPropose = withToast(async () => {
|
|
270
|
+
const code = String(componentEditor.value.code || '').trim().toLowerCase();
|
|
271
|
+
const prompt = String(ai.value.prompt || '').trim();
|
|
272
|
+
if (!code) throw new Error('Select or enter a component code first');
|
|
273
|
+
if (!prompt) throw new Error('Prompt is required');
|
|
274
|
+
persistAiSettings();
|
|
275
|
+
aiLoading.value = true;
|
|
276
|
+
aiProposal.value = null;
|
|
277
|
+
aiWarnings.value = [];
|
|
278
|
+
const data = await api('/api/admin/ui-components/ai/components/' + encodeURIComponent(code) + '/propose', {
|
|
279
|
+
method: 'POST',
|
|
280
|
+
body: {
|
|
281
|
+
prompt,
|
|
282
|
+
providerKey: ai.value.providerKey || undefined,
|
|
283
|
+
model: ai.value.model || undefined,
|
|
284
|
+
targets: ai.value.targets,
|
|
285
|
+
mode: ai.value.mode,
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
aiProposal.value = data && data.proposal ? data.proposal : null;
|
|
289
|
+
aiWarnings.value = aiProposal.value && Array.isArray(aiProposal.value.warnings) ? aiProposal.value.warnings : [];
|
|
290
|
+
showToast('AI proposal ready', 'success');
|
|
291
|
+
aiLoading.value = false;
|
|
292
|
+
}, showToast);
|
|
293
|
+
|
|
294
|
+
const aiApply = () => {
|
|
295
|
+
if (!aiProposal.value || !aiProposal.value.fields) return;
|
|
296
|
+
const f = aiProposal.value.fields;
|
|
297
|
+
if (f.html !== undefined) componentEditor.value.html = f.html;
|
|
298
|
+
if (f.css !== undefined) componentEditor.value.css = f.css;
|
|
299
|
+
if (f.js !== undefined) componentEditor.value.js = f.js;
|
|
300
|
+
if (f.usageMarkdown !== undefined) componentEditor.value.usageMarkdown = f.usageMarkdown;
|
|
301
|
+
showToast('Applied proposal into editor (click Save to persist)', 'success');
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const runPreview = withToast(() => previewManager.run(), showToast);
|
|
305
|
+
const resetPreview = () => previewManager.reset();
|
|
306
|
+
const clearPreviewLogs = () => previewManager.clearLogs();
|
|
307
|
+
const runPreviewCommand = withToast(() => previewManager.runCommand(previewCommand.value), showToast);
|
|
308
|
+
const togglePreviewFullscreen = withToast(() => previewManager.toggleFullscreen(), showToast);
|
|
309
|
+
|
|
310
|
+
const setPreviewCommandExample = (kind) => {
|
|
311
|
+
const code = String(componentEditor.value.code || '').trim().toLowerCase();
|
|
312
|
+
const component = components.value.find((c) => c.code === code);
|
|
313
|
+
|
|
314
|
+
console.log('setPreviewCommandExample',{
|
|
315
|
+
component,
|
|
316
|
+
kind
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
if (kind === 'create') {
|
|
320
|
+
if (component?.previewExample) {
|
|
321
|
+
previewCommand.value = component.previewExample;
|
|
322
|
+
} else {
|
|
323
|
+
previewCommand.value = 'const instance = await uiCmp.create({ message: "Hello from command" });\nlog("created", Object.keys(instance || {}));';
|
|
324
|
+
}
|
|
325
|
+
} else if (kind === 'call') {
|
|
326
|
+
if (component?.code === 'sui_alert') {
|
|
327
|
+
previewCommand.value = 'const i = uiCmp.getInstance();\nif (!i || !i.exported) { log("No instance"); return; }\nif (typeof i.exported.show === "function") i.exported.show("Title", "Message");\nlog("called show() if present");';
|
|
328
|
+
} else if (component?.code === 'sui_toast') {
|
|
329
|
+
previewCommand.value = 'const i = uiCmp.getInstance();\nif (!i || !i.exported) { log("No instance"); return; }\nif (typeof i.exported.info === "function") i.exported.info("Info", "Message");\nlog("called info() if present");';
|
|
330
|
+
} else {
|
|
331
|
+
previewCommand.value = 'const i = uiCmp.getInstance();\nif (!i || !i.exported) { log("No instance"); return; }\nif (typeof i.exported.show === "function") i.exported.show();\nlog("called show() if present");';
|
|
332
|
+
}
|
|
333
|
+
} else {
|
|
334
|
+
previewCommand.value = 'uiCmp.destroy();\nlog("destroyed")';
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
const onFullscreenChange = () => {
|
|
339
|
+
previewFullscreen.value = Boolean(document.fullscreenElement && document.fullscreenElement === previewContainerRef.value);
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
watch(previewMode, async () => {
|
|
343
|
+
resetPreview();
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
onMounted(async () => {
|
|
347
|
+
await refreshAllCore();
|
|
348
|
+
await initAiPicker();
|
|
349
|
+
// Indicate that Vue is ready and process any queued LLM picker initializations
|
|
350
|
+
window.__llmProviderModelPickerReady = true;
|
|
351
|
+
if (window.__llmProviderModelPickerQueue && Array.isArray(window.__llmProviderModelPickerQueue)) {
|
|
352
|
+
window.__llmProviderModelPickerQueue.forEach(fn => {
|
|
353
|
+
try { fn(); } catch (_) {}
|
|
354
|
+
});
|
|
355
|
+
window.__llmProviderModelPickerQueue = [];
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
return {
|
|
360
|
+
baseUrl,
|
|
361
|
+
adminPath,
|
|
362
|
+
toast,
|
|
363
|
+
helpOpen,
|
|
364
|
+
projects,
|
|
365
|
+
components,
|
|
366
|
+
selectedProject,
|
|
367
|
+
assignments,
|
|
368
|
+
lastGeneratedKey,
|
|
369
|
+
newProject,
|
|
370
|
+
componentEditor,
|
|
371
|
+
toggleHelp,
|
|
372
|
+
ai,
|
|
373
|
+
aiLoading,
|
|
374
|
+
aiProposal,
|
|
375
|
+
aiWarnings,
|
|
376
|
+
refreshAll,
|
|
377
|
+
createProject,
|
|
378
|
+
selectProject,
|
|
379
|
+
isAssigned,
|
|
380
|
+
toggleAssignment,
|
|
381
|
+
toggleProjectPublic,
|
|
382
|
+
rotateKey,
|
|
383
|
+
clearComponentEditor,
|
|
384
|
+
loadComponentIntoEditor,
|
|
385
|
+
saveComponent,
|
|
386
|
+
aiPropose,
|
|
387
|
+
aiApply,
|
|
388
|
+
loadLlmConfig,
|
|
389
|
+
previewMode,
|
|
390
|
+
previewCssIsolation,
|
|
391
|
+
previewPropsJson,
|
|
392
|
+
previewStatus,
|
|
393
|
+
previewCommand,
|
|
394
|
+
previewLogsText,
|
|
395
|
+
previewFullscreen,
|
|
396
|
+
previewContainerRef,
|
|
397
|
+
previewTopMountRef,
|
|
398
|
+
previewIframeRef,
|
|
399
|
+
runPreview,
|
|
400
|
+
resetPreview,
|
|
401
|
+
runPreviewCommand,
|
|
402
|
+
clearPreviewLogs,
|
|
403
|
+
setPreviewCommandExample,
|
|
404
|
+
togglePreviewFullscreen,
|
|
405
|
+
};
|
|
406
|
+
},
|
|
407
|
+
}).mount('#app');
|
|
408
|
+
})();
|