@intranefr/superbackend 1.5.2 → 1.6.3
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 +9 -0
- package/manage.js +745 -0
- package/package.json +6 -2
- 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 +36 -10
- package/src/controllers/adminAgents.controller.js +37 -0
- package/src/controllers/adminDataCleanup.controller.js +45 -0
- package/src/controllers/adminLlm.controller.js +19 -8
- package/src/controllers/adminLogin.controller.js +269 -0
- package/src/controllers/adminMarkdowns.controller.js +157 -0
- package/src/controllers/adminPlugins.controller.js +55 -0
- package/src/controllers/adminRegistry.controller.js +106 -0
- package/src/controllers/adminScripts.controller.js +138 -0
- package/src/controllers/adminStats.controller.js +4 -4
- package/src/controllers/adminTelegram.controller.js +72 -0
- package/src/controllers/markdowns.controller.js +42 -0
- package/src/controllers/registry.controller.js +32 -0
- package/src/controllers/waitingList.controller.js +52 -74
- package/src/helpers/mongooseHelper.js +6 -6
- package/src/helpers/scriptBase.js +2 -2
- package/src/middleware/auth.js +71 -1
- package/src/middleware/rbac.js +62 -0
- package/src/middleware.js +584 -176
- package/src/models/Agent.js +105 -0
- package/src/models/AgentMessage.js +82 -0
- package/src/models/GlobalSetting.js +11 -1
- package/src/models/Markdown.js +75 -0
- package/src/models/ScriptRun.js +8 -0
- package/src/models/TelegramBot.js +42 -0
- 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 +13 -0
- 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 -7
- package/src/routes/adminLogin.routes.js +23 -0
- package/src/routes/adminMarkdowns.routes.js +10 -0
- 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 +6 -3
- package/src/routes/adminSeoConfig.routes.js +10 -10
- package/src/routes/adminTelegram.routes.js +14 -0
- 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/markdowns.routes.js +16 -0
- 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/agent.service.js +546 -0
- package/src/services/agentHistory.service.js +345 -0
- package/src/services/agentTools.service.js +578 -0
- package/src/services/dataCleanup.service.js +286 -0
- package/src/services/jsonConfigs.service.js +284 -10
- package/src/services/llm.service.js +219 -6
- package/src/services/markdowns.service.js +522 -0
- package/src/services/plugins.service.js +348 -0
- package/src/services/registry.service.js +452 -0
- package/src/services/scriptsRunner.service.js +328 -37
- package/src/services/telegram.service.js +130 -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-agents.ejs +273 -0
- package/views/admin-coolify-deploy.ejs +8 -8
- package/views/admin-dashboard-home.ejs +52 -2
- package/views/admin-dashboard.ejs +179 -7
- package/views/admin-data-cleanup.ejs +357 -0
- package/views/admin-experiments.ejs +1 -1
- package/views/admin-login.ejs +286 -0
- package/views/admin-markdowns.ejs +905 -0
- package/views/admin-plugins-system.ejs +223 -0
- package/views/admin-scripts.ejs +221 -4
- package/views/admin-telegram.ejs +269 -0
- package/views/admin-ui-components.ejs +82 -402
- package/views/admin-users.ejs +207 -11
- package/views/partials/dashboard/nav-items.ejs +5 -0
- package/views/partials/llm-provider-model-picker.ejs +0 -161
- package/analysis-only.skill +0 -0
|
@@ -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
|
+
})();
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
// Only initialize if Vue has indicated it's ready (prevents premature execution in Vue contexts)
|
|
3
|
+
if (typeof window !== 'undefined' && window.__llmProviderModelPickerReady === true) {
|
|
4
|
+
initializeLlmProviderModelPicker();
|
|
5
|
+
} else if (typeof window !== 'undefined') {
|
|
6
|
+
// Queue initialization for when Vue indicates readiness
|
|
7
|
+
window.__llmProviderModelPickerQueue = window.__llmProviderModelPickerQueue || [];
|
|
8
|
+
window.__llmProviderModelPickerQueue.push(initializeLlmProviderModelPicker);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function initializeLlmProviderModelPicker() {
|
|
12
|
+
if (!window.__llmProviderModelPicker) {
|
|
13
|
+
window.__llmProviderModelPicker = { instances: {} };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function safeJsonParse(raw, fallback) {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(raw);
|
|
19
|
+
} catch (_) {
|
|
20
|
+
return fallback;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function fetchJson(url) {
|
|
25
|
+
const res = await fetch(url);
|
|
26
|
+
const data = await res.json();
|
|
27
|
+
if (!res.ok) {
|
|
28
|
+
throw new Error(data?.error || 'Request failed');
|
|
29
|
+
}
|
|
30
|
+
return data;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function setDatalistOptions(datalistEl, items) {
|
|
34
|
+
datalistEl.innerHTML = '';
|
|
35
|
+
const uniq = Array.from(new Set((items || []).filter(Boolean)));
|
|
36
|
+
for (const item of uniq) {
|
|
37
|
+
const opt = document.createElement('option');
|
|
38
|
+
opt.value = String(item);
|
|
39
|
+
datalistEl.appendChild(opt);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function trim(v) {
|
|
44
|
+
return String(v || '').trim();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isOpenRouterProvider({ providerKey, providerConfig }) {
|
|
48
|
+
const pk = String(providerKey || '').trim().toLowerCase();
|
|
49
|
+
if (pk === 'openrouter') return true;
|
|
50
|
+
|
|
51
|
+
const baseUrl = providerConfig && typeof providerConfig === 'object'
|
|
52
|
+
? String(providerConfig.baseUrl || providerConfig.baseURL || '').trim().toLowerCase()
|
|
53
|
+
: '';
|
|
54
|
+
|
|
55
|
+
return Boolean(baseUrl && baseUrl.includes('openrouter'));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getInstanceKey({ providerInputId, modelInputId }) {
|
|
59
|
+
return `${String(providerInputId || '').trim()}::${String(modelInputId || '').trim()}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function getOrCreateInstance(opts) {
|
|
63
|
+
const key = getInstanceKey(opts);
|
|
64
|
+
const existing = window.__llmProviderModelPicker.instances[key];
|
|
65
|
+
if (existing) {
|
|
66
|
+
console.log('[LLM Picker Debug] Using existing instance:', key, 'apiBase:', existing.apiBase);
|
|
67
|
+
return existing;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const apiBase = opts.apiBase !== undefined ? opts.apiBase : (window.__llmProviderModelPicker.defaultApiBase || null);
|
|
71
|
+
console.log('[LLM Picker Debug] Creating new instance:', key, 'opts.apiBase:', opts.apiBase, 'defaultApiBase:', window.__llmProviderModelPicker.defaultApiBase, 'final apiBase:', apiBase);
|
|
72
|
+
|
|
73
|
+
const inst = {
|
|
74
|
+
apiBase: apiBase,
|
|
75
|
+
providerInputId: opts.providerInputId,
|
|
76
|
+
modelInputId: opts.modelInputId,
|
|
77
|
+
providers: {},
|
|
78
|
+
providerModels: {},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
window.__llmProviderModelPicker.instances[key] = inst;
|
|
82
|
+
return inst;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function loadConfig(inst) {
|
|
86
|
+
const url = `${inst.apiBase}/api/admin/llm/config`;
|
|
87
|
+
console.log('[LLM Picker Debug] loadConfig called with apiBase:', inst.apiBase, 'full URL:', url);
|
|
88
|
+
const data = await fetchJson(url);
|
|
89
|
+
inst.providers = data.providers || {};
|
|
90
|
+
inst.providerModels = data.providerModels || {};
|
|
91
|
+
return data;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function renderProviderOptions(inst) {
|
|
95
|
+
const providerInput = document.getElementById(inst.providerInputId);
|
|
96
|
+
const providerList = document.getElementById(`${inst.providerInputId}__datalist`);
|
|
97
|
+
if (!providerInput || !providerList) return;
|
|
98
|
+
|
|
99
|
+
const providerKeys = Object.keys(inst.providers || {}).sort();
|
|
100
|
+
setDatalistOptions(providerList, providerKeys);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function renderModelOptions(inst) {
|
|
104
|
+
const providerInput = document.getElementById(inst.providerInputId);
|
|
105
|
+
const modelList = document.getElementById(`${inst.modelInputId}__datalist`);
|
|
106
|
+
if (!providerInput || !modelList) return;
|
|
107
|
+
|
|
108
|
+
const providerKey = trim(providerInput.value);
|
|
109
|
+
const models = providerKey && inst.providerModels && typeof inst.providerModels === 'object'
|
|
110
|
+
? inst.providerModels[providerKey]
|
|
111
|
+
: null;
|
|
112
|
+
|
|
113
|
+
setDatalistOptions(modelList, Array.isArray(models) ? models : []);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function maybeAutoFetchOpenRouterModels(inst) {
|
|
117
|
+
try {
|
|
118
|
+
const providerInput = document.getElementById(inst.providerInputId);
|
|
119
|
+
if (!providerInput) return;
|
|
120
|
+
|
|
121
|
+
const providerKey = trim(providerInput.value);
|
|
122
|
+
const providerConfig = inst.providers && typeof inst.providers === 'object' ? inst.providers[providerKey] : null;
|
|
123
|
+
if (!isOpenRouterProvider({ providerKey, providerConfig })) return;
|
|
124
|
+
|
|
125
|
+
const existing = inst.providerModels && typeof inst.providerModels === 'object' ? inst.providerModels.openrouter : null;
|
|
126
|
+
if (Array.isArray(existing) && existing.length > 0) return;
|
|
127
|
+
|
|
128
|
+
await fetchOpenRouterModels({
|
|
129
|
+
apiBase: inst.apiBase,
|
|
130
|
+
providerInputId: inst.providerInputId,
|
|
131
|
+
modelInputId: inst.modelInputId,
|
|
132
|
+
});
|
|
133
|
+
} catch {
|
|
134
|
+
// ignore
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function fetchOpenRouterModels(opts) {
|
|
139
|
+
console.log('[LLM Picker Debug] fetchOpenRouterModels called with opts:', opts);
|
|
140
|
+
const inst = getOrCreateInstance(opts || {});
|
|
141
|
+
console.log('[LLM Picker Debug] fetchOpenRouterModels - inst.apiBase before update:', inst.apiBase);
|
|
142
|
+
inst.apiBase = (opts && opts.apiBase !== undefined) ? opts.apiBase : (inst.apiBase !== undefined ? inst.apiBase : (window.__llmProviderModelPicker.defaultApiBase || null));
|
|
143
|
+
console.log('[LLM Picker Debug] fetchOpenRouterModels - inst.apiBase after update:', inst.apiBase);
|
|
144
|
+
if (inst.apiBase == null) {
|
|
145
|
+
console.error('[LLM Picker] No apiBase available for fetchOpenRouterModels');
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const data = await fetchJson(`${inst.apiBase}/api/admin/llm/openrouter/models`);
|
|
150
|
+
const models = Array.isArray(data?.models) ? data.models : [];
|
|
151
|
+
|
|
152
|
+
inst.providerModels = inst.providerModels && typeof inst.providerModels === 'object' ? inst.providerModels : {};
|
|
153
|
+
inst.providerModels.openrouter = models;
|
|
154
|
+
renderModelOptions(inst);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function init(opts) {
|
|
158
|
+
console.log('[LLM Picker Debug] init called with opts:', opts);
|
|
159
|
+
const inst = getOrCreateInstance(opts || {});
|
|
160
|
+
|
|
161
|
+
if (opts && opts.apiBase) {
|
|
162
|
+
console.log('[LLM Picker Debug] Setting apiBase to:', opts.apiBase);
|
|
163
|
+
window.__llmProviderModelPicker.defaultApiBase = opts.apiBase;
|
|
164
|
+
// Update apiBase for this instance and all existing instances
|
|
165
|
+
inst.apiBase = opts.apiBase;
|
|
166
|
+
// Update all existing instances to have the correct apiBase
|
|
167
|
+
Object.values(window.__llmProviderModelPicker.instances).forEach(existingInst => {
|
|
168
|
+
console.log('[LLM Picker Debug] Updating existing instance apiBase from', existingInst.apiBase, 'to', opts.apiBase);
|
|
169
|
+
existingInst.apiBase = opts.apiBase;
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
console.log('[LLM Picker Debug] About to call loadConfig, inst.apiBase:', inst.apiBase);
|
|
174
|
+
await loadConfig(inst);
|
|
175
|
+
renderProviderOptions(inst);
|
|
176
|
+
renderModelOptions(inst);
|
|
177
|
+
await maybeAutoFetchOpenRouterModels(inst);
|
|
178
|
+
|
|
179
|
+
const providerInput = document.getElementById(inst.providerInputId);
|
|
180
|
+
if (providerInput) {
|
|
181
|
+
providerInput.addEventListener('change', async () => {
|
|
182
|
+
renderModelOptions(inst);
|
|
183
|
+
await maybeAutoFetchOpenRouterModels(inst);
|
|
184
|
+
});
|
|
185
|
+
providerInput.addEventListener('input', () => renderModelOptions(inst));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
window.__llmProviderModelPicker.init = init;
|
|
190
|
+
window.__llmProviderModelPicker.fetchOpenRouterModels = fetchOpenRouterModels;
|
|
191
|
+
window.__llmProviderModelPicker._util = { safeJsonParse };
|
|
192
|
+
}
|
|
193
|
+
})();
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>Iframe Fix Test</title>
|
|
5
|
+
<style>
|
|
6
|
+
body { font-family: Arial, sans-serif; padding: 20px; }
|
|
7
|
+
.test-container { margin: 20px 0; }
|
|
8
|
+
iframe { width: 100%; height: 400px; border: 2px solid #ccc; }
|
|
9
|
+
.success { color: green; font-weight: bold; }
|
|
10
|
+
.error { color: red; font-weight: bold; }
|
|
11
|
+
</style>
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<h1>Iframe Authentication Fix Test</h1>
|
|
15
|
+
|
|
16
|
+
<div class="test-container">
|
|
17
|
+
<h2>Test 1: Iframe with Token (Should Work)</h2>
|
|
18
|
+
<iframe src="/admin/stats/dashboard-home?iframe_token=authenticated"></iframe>
|
|
19
|
+
<p id="test1-result">Loading...</p>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div class="test-container">
|
|
23
|
+
<h2>Test 2: Iframe without Token (Should Redirect to Login)</h2>
|
|
24
|
+
<iframe src="/admin/stats/dashboard-home"></iframe>
|
|
25
|
+
<p id="test2-result">Loading...</p>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<script>
|
|
29
|
+
// Test if iframe loads correctly
|
|
30
|
+
setTimeout(() => {
|
|
31
|
+
const iframes = document.querySelectorAll('iframe');
|
|
32
|
+
|
|
33
|
+
// Test 1 - should show Command Center
|
|
34
|
+
iframes[0].contentDocument && iframes[0].contentDocument.body) {
|
|
35
|
+
const content = iframes[0].contentDocument.body.innerText;
|
|
36
|
+
if (content.includes('Command Center')) {
|
|
37
|
+
document.getElementById('test1-result').innerHTML = '<span class="success">✅ SUCCESS: Iframe with token loads correctly</span>';
|
|
38
|
+
} else if (content.includes('login') || content.includes('Login')) {
|
|
39
|
+
document.getElementById('test1-result').innerHTML = '<span class="error">❌ FAILED: Iframe with token redirected to login</span>';
|
|
40
|
+
} else {
|
|
41
|
+
document.getElementById('test1-result').innerHTML = '<span class="error">❌ UNKNOWN: Could not determine iframe content</span>';
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
document.getElementById('test1-result').innerHTML = '<span class="error">❌ FAILED: Could not access iframe content (cross-origin)</span>';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Test 2 - should redirect to login
|
|
48
|
+
if (iframes[1].contentDocument && iframes[1].contentDocument.body) {
|
|
49
|
+
const content = iframes[1].contentDocument.body.innerText;
|
|
50
|
+
if (content.includes('login') || content.includes('Login')) {
|
|
51
|
+
document.getElementById('test2-result').innerHTML = '<span class="success">✅ SUCCESS: Iframe without token correctly redirects to login</span>';
|
|
52
|
+
} else if (content.includes('Command Center')) {
|
|
53
|
+
document.getElementById('test2-result').innerHTML = '<span class="error">❌ FAILED: Iframe without token loaded content (security issue)</span>';
|
|
54
|
+
} else {
|
|
55
|
+
document.getElementById('test2-result').innerHTML = '<span class="error">❌ UNKNOWN: Could not determine iframe content</span>';
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
document.getElementById('test2-result').innerHTML = '<span class="error">❌ FAILED: Could not access iframe content (cross-origin)</span>';
|
|
59
|
+
}
|
|
60
|
+
}, 3000);
|
|
61
|
+
</script>
|
|
62
|
+
</body>
|
|
63
|
+
</html>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>Iframe Test</title>
|
|
5
|
+
</head>
|
|
6
|
+
<body>
|
|
7
|
+
<h1>Iframe Test</h1>
|
|
8
|
+
<p>Testing iframe loading of admin dashboard content...</p>
|
|
9
|
+
|
|
10
|
+
<iframe src="/admin/stats/dashboard-home" width="100%" height="500" style="border: 1px solid #ccc;"></iframe>
|
|
11
|
+
|
|
12
|
+
<p>If you see the admin dashboard content above, iframes work. If you see a login page, there's a cookie/session issue.</p>
|
|
13
|
+
</body>
|
|
14
|
+
</html>
|