@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,223 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Plugins system</title>
|
|
7
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
8
|
+
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
|
9
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/dist/tabler-icons.min.css">
|
|
10
|
+
</head>
|
|
11
|
+
<body class="bg-gray-100">
|
|
12
|
+
<div id="app" class="min-h-screen p-6">
|
|
13
|
+
<div class="max-w-6xl mx-auto space-y-6">
|
|
14
|
+
<header class="bg-white rounded-xl border border-gray-200 p-6">
|
|
15
|
+
<h1 class="text-2xl font-bold text-gray-900 flex items-center gap-2">
|
|
16
|
+
<i class="ti ti-puzzle text-blue-600"></i>
|
|
17
|
+
Plugins system
|
|
18
|
+
</h1>
|
|
19
|
+
<p class="text-gray-600 mt-2">Enable, disable, and install local CommonJS plugins from <code class="text-sm bg-gray-100 px-1.5 py-0.5 rounded">cwd/plugins/*</code>.</p>
|
|
20
|
+
</header>
|
|
21
|
+
|
|
22
|
+
<section class="bg-white rounded-xl border border-gray-200 p-6">
|
|
23
|
+
<div class="flex items-center justify-between mb-4">
|
|
24
|
+
<h2 class="text-lg font-semibold text-gray-800">Available plugins</h2>
|
|
25
|
+
<button @click="loadPlugins" class="px-3 py-2 rounded-lg bg-blue-600 text-white hover:bg-blue-700 text-sm">
|
|
26
|
+
Refresh
|
|
27
|
+
</button>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<p v-if="error" class="mb-4 text-sm text-red-600">{{ error }}</p>
|
|
31
|
+
|
|
32
|
+
<div v-if="loading" class="text-sm text-gray-500">Loading plugins...</div>
|
|
33
|
+
|
|
34
|
+
<div v-else-if="plugins.length === 0" class="text-sm text-gray-500 border border-dashed border-gray-300 rounded-lg p-4">
|
|
35
|
+
No plugins discovered. Create folders under <code>plugins/</code> with an <code>index.js</code> CommonJS export.
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<div v-else class="space-y-3">
|
|
39
|
+
<article v-for="plugin in plugins" :key="plugin.id" class="border border-gray-200 rounded-lg p-4 bg-gray-50">
|
|
40
|
+
<div class="flex items-start justify-between gap-4">
|
|
41
|
+
<div>
|
|
42
|
+
<h3 class="font-semibold text-gray-900">{{ plugin.name }} <span class="text-xs text-gray-500">({{ plugin.id }})</span></h3>
|
|
43
|
+
<p class="text-sm text-gray-600 mt-1">{{ plugin.description || 'No description provided.' }}</p>
|
|
44
|
+
<div class="mt-2 flex flex-wrap gap-2 text-xs">
|
|
45
|
+
<span class="px-2 py-1 rounded bg-white border border-gray-200">version: {{ plugin.version }}</span>
|
|
46
|
+
<span class="px-2 py-1 rounded bg-white border border-gray-200">bootstrap: {{ plugin.hooks.bootstrap ? 'yes' : 'no' }}</span>
|
|
47
|
+
<span class="px-2 py-1 rounded bg-white border border-gray-200">install: {{ plugin.hooks.install ? 'yes' : 'no' }}</span>
|
|
48
|
+
<span class="px-2 py-1 rounded" :class="plugin.enabled ? 'bg-green-100 text-green-800 border border-green-200' : 'bg-gray-200 text-gray-700 border border-gray-300'">
|
|
49
|
+
{{ plugin.enabled ? 'enabled' : 'disabled' }}
|
|
50
|
+
</span>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<div class="flex flex-col gap-2 min-w-[170px]">
|
|
55
|
+
<button
|
|
56
|
+
v-if="!plugin.enabled"
|
|
57
|
+
@click="enablePlugin(plugin.id)"
|
|
58
|
+
class="px-3 py-2 rounded-lg bg-green-600 text-white hover:bg-green-700 text-sm"
|
|
59
|
+
>Enable</button>
|
|
60
|
+
<button
|
|
61
|
+
v-else
|
|
62
|
+
@click="disablePlugin(plugin.id)"
|
|
63
|
+
class="px-3 py-2 rounded-lg bg-gray-700 text-white hover:bg-gray-800 text-sm"
|
|
64
|
+
>Disable</button>
|
|
65
|
+
<button
|
|
66
|
+
@click="installPlugin(plugin.id)"
|
|
67
|
+
class="px-3 py-2 rounded-lg bg-indigo-600 text-white hover:bg-indigo-700 text-sm"
|
|
68
|
+
>Run install</button>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</article>
|
|
72
|
+
</div>
|
|
73
|
+
</section>
|
|
74
|
+
|
|
75
|
+
<section class="bg-white rounded-xl border border-gray-200 p-6 space-y-3">
|
|
76
|
+
<h2 class="text-lg font-semibold text-gray-800">How to create and use plugins</h2>
|
|
77
|
+
|
|
78
|
+
<details class="border border-gray-200 rounded-lg p-4">
|
|
79
|
+
<summary class="cursor-pointer font-medium text-gray-900">Add a new plugin</summary>
|
|
80
|
+
<pre class="mt-3 text-xs bg-gray-900 text-gray-100 p-3 rounded overflow-auto">plugins/my-plugin/index.js
|
|
81
|
+
|
|
82
|
+
module.exports = {
|
|
83
|
+
meta: {
|
|
84
|
+
id: 'my-plugin',
|
|
85
|
+
name: 'My Plugin',
|
|
86
|
+
version: '1.0.0',
|
|
87
|
+
description: 'Example plugin',
|
|
88
|
+
tags: ['example']
|
|
89
|
+
},
|
|
90
|
+
hooks: {
|
|
91
|
+
bootstrap(ctx) {
|
|
92
|
+
console.log('[my-plugin] bootstrap', Object.keys(ctx.services || {}));
|
|
93
|
+
},
|
|
94
|
+
install(ctx) {
|
|
95
|
+
console.log('[my-plugin] install');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
};</pre>
|
|
99
|
+
</details>
|
|
100
|
+
|
|
101
|
+
<details class="border border-gray-200 rounded-lg p-4">
|
|
102
|
+
<summary class="cursor-pointer font-medium text-gray-900">Permissive contract supported</summary>
|
|
103
|
+
<pre class="mt-3 text-xs bg-gray-900 text-gray-100 p-3 rounded overflow-auto">module.exports = {
|
|
104
|
+
id: 'top-level-contract',
|
|
105
|
+
name: 'Top level contract plugin',
|
|
106
|
+
bootstrap(ctx) {
|
|
107
|
+
console.log('bootstrap hook with fallback contract');
|
|
108
|
+
},
|
|
109
|
+
install(ctx) {
|
|
110
|
+
console.log('install hook with fallback contract');
|
|
111
|
+
}
|
|
112
|
+
};</pre>
|
|
113
|
+
</details>
|
|
114
|
+
|
|
115
|
+
<details class="border border-gray-200 rounded-lg p-4">
|
|
116
|
+
<summary class="cursor-pointer font-medium text-gray-900">Expose helpers/services programmatically</summary>
|
|
117
|
+
<pre class="mt-3 text-xs bg-gray-900 text-gray-100 p-3 rounded overflow-auto">module.exports = {
|
|
118
|
+
meta: { id: 'tools', name: 'Tools plugin' },
|
|
119
|
+
services: {
|
|
120
|
+
myService() {
|
|
121
|
+
return 'hello';
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
helpers: {
|
|
125
|
+
formatLabel(input) {
|
|
126
|
+
return String(input || '').toUpperCase();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// then available in runtime after enable/bootstrap:
|
|
132
|
+
// superbackend.services.pluginsRuntime.myService()
|
|
133
|
+
// superbackend.helpers.pluginsRuntime.formatLabel('demo')</pre>
|
|
134
|
+
</details>
|
|
135
|
+
</section>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
<script>
|
|
140
|
+
const { createApp, ref, onMounted } = Vue;
|
|
141
|
+
|
|
142
|
+
createApp({
|
|
143
|
+
setup() {
|
|
144
|
+
const baseUrl = '<%= baseUrl %>';
|
|
145
|
+
const plugins = ref([]);
|
|
146
|
+
const loading = ref(false);
|
|
147
|
+
const error = ref('');
|
|
148
|
+
|
|
149
|
+
const loadPlugins = async () => {
|
|
150
|
+
loading.value = true;
|
|
151
|
+
error.value = '';
|
|
152
|
+
try {
|
|
153
|
+
const response = await fetch(baseUrl + '/api/admin/plugins');
|
|
154
|
+
const payload = await response.json();
|
|
155
|
+
if (!response.ok) throw new Error(payload.error || 'Failed to load plugins');
|
|
156
|
+
plugins.value = payload.items || [];
|
|
157
|
+
} catch (err) {
|
|
158
|
+
error.value = err.message || 'Failed to load plugins';
|
|
159
|
+
} finally {
|
|
160
|
+
loading.value = false;
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const enablePlugin = async (id) => {
|
|
165
|
+
error.value = '';
|
|
166
|
+
try {
|
|
167
|
+
const response = await fetch(baseUrl + '/api/admin/plugins/' + encodeURIComponent(id) + '/enable', {
|
|
168
|
+
method: 'POST',
|
|
169
|
+
headers: { 'Content-Type': 'application/json' }
|
|
170
|
+
});
|
|
171
|
+
const payload = await response.json();
|
|
172
|
+
if (!response.ok) throw new Error(payload.error || 'Enable failed');
|
|
173
|
+
await loadPlugins();
|
|
174
|
+
} catch (err) {
|
|
175
|
+
error.value = err.message || 'Enable failed';
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const disablePlugin = async (id) => {
|
|
180
|
+
error.value = '';
|
|
181
|
+
try {
|
|
182
|
+
const response = await fetch(baseUrl + '/api/admin/plugins/' + encodeURIComponent(id) + '/disable', {
|
|
183
|
+
method: 'POST',
|
|
184
|
+
headers: { 'Content-Type': 'application/json' }
|
|
185
|
+
});
|
|
186
|
+
const payload = await response.json();
|
|
187
|
+
if (!response.ok) throw new Error(payload.error || 'Disable failed');
|
|
188
|
+
await loadPlugins();
|
|
189
|
+
} catch (err) {
|
|
190
|
+
error.value = err.message || 'Disable failed';
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const installPlugin = async (id) => {
|
|
195
|
+
error.value = '';
|
|
196
|
+
try {
|
|
197
|
+
const response = await fetch(baseUrl + '/api/admin/plugins/' + encodeURIComponent(id) + '/install', {
|
|
198
|
+
method: 'POST',
|
|
199
|
+
headers: { 'Content-Type': 'application/json' }
|
|
200
|
+
});
|
|
201
|
+
const payload = await response.json();
|
|
202
|
+
if (!response.ok) throw new Error(payload.error || 'Install failed');
|
|
203
|
+
} catch (err) {
|
|
204
|
+
error.value = err.message || 'Install failed';
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
onMounted(loadPlugins);
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
plugins,
|
|
212
|
+
loading,
|
|
213
|
+
error,
|
|
214
|
+
loadPlugins,
|
|
215
|
+
enablePlugin,
|
|
216
|
+
disablePlugin,
|
|
217
|
+
installPlugin,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
}).mount('#app');
|
|
221
|
+
</script>
|
|
222
|
+
</body>
|
|
223
|
+
</html>
|
package/views/admin-scripts.ejs
CHANGED
|
@@ -549,11 +549,44 @@ const recentRuns = await ScriptRun.find()
|
|
|
549
549
|
</div>
|
|
550
550
|
|
|
551
551
|
<div class="mt-4 bg-white border border-gray-200 rounded-lg">
|
|
552
|
-
<div class="p-3 border-b border-gray-200
|
|
553
|
-
<div class="
|
|
554
|
-
|
|
552
|
+
<div class="p-3 border-b border-gray-200">
|
|
553
|
+
<div class="flex items-center justify-between">
|
|
554
|
+
<nav class="flex space-x-4">
|
|
555
|
+
<button class="output-tab active" data-tab="output">Output</button>
|
|
556
|
+
<button class="output-tab" data-tab="full-logs">Full Console Logs</button>
|
|
557
|
+
</nav>
|
|
558
|
+
<div class="flex items-center gap-2">
|
|
559
|
+
<button id="btn-download-logs" class="text-sm text-gray-600 hover:underline">Download</button>
|
|
560
|
+
<button id="btn-clear-output" class="text-sm text-gray-600 hover:underline">Clear</button>
|
|
561
|
+
</div>
|
|
562
|
+
</div>
|
|
563
|
+
</div>
|
|
564
|
+
|
|
565
|
+
<!-- Output Tab (JSON Result) -->
|
|
566
|
+
<div id="output-tab-content" class="output-tab-content">
|
|
567
|
+
<pre id="output" class="p-3 text-xs font-mono whitespace-pre-wrap max-h-[40vh] overflow-auto"></pre>
|
|
568
|
+
</div>
|
|
569
|
+
|
|
570
|
+
<!-- Full Console Logs Tab -->
|
|
571
|
+
<div id="full-logs-tab-content" class="output-tab-content hidden">
|
|
572
|
+
<!-- Search Bar -->
|
|
573
|
+
<div class="p-3 border-b border-gray-200">
|
|
574
|
+
<div class="flex items-center gap-2">
|
|
575
|
+
<input type="text" id="log-search" placeholder="Search logs..." class="flex-1 px-2 py-1 border rounded text-sm">
|
|
576
|
+
<select id="log-filter" class="px-2 py-1 border rounded text-sm">
|
|
577
|
+
<option value="all">All Logs</option>
|
|
578
|
+
<option value="stdout">Stdout</option>
|
|
579
|
+
<option value="stderr">Stderr</option>
|
|
580
|
+
</select>
|
|
581
|
+
<button id="btn-auto-scroll" class="px-2 py-1 bg-gray-100 rounded text-sm">Auto-scroll</button>
|
|
582
|
+
</div>
|
|
583
|
+
</div>
|
|
584
|
+
|
|
585
|
+
<!-- Full Logs Display -->
|
|
586
|
+
<div class="relative">
|
|
587
|
+
<pre id="full-logs-content" class="p-3 text-xs font-mono whitespace-pre-wrap" style="height: 60vh; overflow: auto;"></pre>
|
|
588
|
+
</div>
|
|
555
589
|
</div>
|
|
556
|
-
<pre id="output" class="p-3 text-xs font-mono whitespace-pre-wrap max-h-[40vh] overflow-auto"></pre>
|
|
557
590
|
</div>
|
|
558
591
|
</div>
|
|
559
592
|
</div>
|
|
@@ -571,6 +604,8 @@ const recentRuns = await ScriptRun.find()
|
|
|
571
604
|
es: null,
|
|
572
605
|
};
|
|
573
606
|
|
|
607
|
+
let currentRunId = null;
|
|
608
|
+
|
|
574
609
|
function qs(id) {
|
|
575
610
|
return document.getElementById(id);
|
|
576
611
|
}
|
|
@@ -772,8 +807,12 @@ const recentRuns = await ScriptRun.find()
|
|
|
772
807
|
<div class="text-xs text-gray-500 font-mono">${String(r._id || '').slice(0, 10)} · ${String(r.createdAt || '').replace(/</g,'<')}</div>
|
|
773
808
|
`;
|
|
774
809
|
btn.addEventListener('click', () => {
|
|
810
|
+
currentRunId = r._id; // Set the current run ID when clicking on a previous run
|
|
775
811
|
setOutput('');
|
|
776
812
|
if (r.outputTail) setOutput(r.outputTail, true);
|
|
813
|
+
|
|
814
|
+
// Load programmatic output for the selected run
|
|
815
|
+
loadProgrammaticOutput(r._id);
|
|
777
816
|
});
|
|
778
817
|
list.appendChild(btn);
|
|
779
818
|
});
|
|
@@ -894,8 +933,16 @@ const recentRuns = await ScriptRun.find()
|
|
|
894
933
|
|
|
895
934
|
setOutput('');
|
|
896
935
|
const res = await api('/api/admin/scripts/' + encodeURIComponent(state.selectedId) + '/run', { method: 'POST' });
|
|
936
|
+
currentRunId = res.runId; // Set the current run ID
|
|
897
937
|
startSse(res.runId);
|
|
898
938
|
await loadRuns();
|
|
939
|
+
|
|
940
|
+
// Load programmatic output after script starts
|
|
941
|
+
setTimeout(() => {
|
|
942
|
+
if (currentRunId) {
|
|
943
|
+
loadProgrammaticOutput(currentRunId);
|
|
944
|
+
}
|
|
945
|
+
}, 1000); // Wait a bit for the script to start producing output
|
|
899
946
|
}
|
|
900
947
|
|
|
901
948
|
qs('btn-refresh').addEventListener('click', loadScripts);
|
|
@@ -1080,6 +1127,176 @@ const recentRuns = await ScriptRun.find()
|
|
|
1080
1127
|
});
|
|
1081
1128
|
}
|
|
1082
1129
|
|
|
1130
|
+
// Tab switching functionality
|
|
1131
|
+
function switchOutputTab(tabName) {
|
|
1132
|
+
// Hide all tab contents
|
|
1133
|
+
document.querySelectorAll('.output-tab-content').forEach(content => {
|
|
1134
|
+
content.classList.add('hidden');
|
|
1135
|
+
});
|
|
1136
|
+
|
|
1137
|
+
// Remove active class from all tabs
|
|
1138
|
+
document.querySelectorAll('.output-tab').forEach(tab => {
|
|
1139
|
+
tab.classList.remove('active', 'border-blue-500', 'text-blue-600');
|
|
1140
|
+
tab.classList.add('border-transparent', 'text-gray-500');
|
|
1141
|
+
});
|
|
1142
|
+
|
|
1143
|
+
// Show selected tab content
|
|
1144
|
+
document.getElementById(`${tabName}-tab-content`).classList.remove('hidden');
|
|
1145
|
+
|
|
1146
|
+
// Activate selected tab
|
|
1147
|
+
const activeTab = document.querySelector(`[data-tab="${tabName}"]`);
|
|
1148
|
+
activeTab.classList.add('active', 'border-blue-500', 'text-blue-600');
|
|
1149
|
+
activeTab.classList.remove('border-transparent', 'text-gray-500');
|
|
1150
|
+
|
|
1151
|
+
// Load full logs if switching to full-logs tab
|
|
1152
|
+
if (tabName === 'full-logs' && currentRunId) {
|
|
1153
|
+
loadFullLogs(currentRunId);
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
// Load programmatic output if switching to output tab
|
|
1157
|
+
if (tabName === 'output' && currentRunId) {
|
|
1158
|
+
loadProgrammaticOutput(currentRunId);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// Load programmatic output from API
|
|
1163
|
+
async function loadProgrammaticOutput(runId) {
|
|
1164
|
+
try {
|
|
1165
|
+
const response = await fetch(`${window.BASE_URL}/api/admin/scripts/runs/${runId}/programmatic-output`);
|
|
1166
|
+
const data = await response.json();
|
|
1167
|
+
|
|
1168
|
+
displayProgrammaticOutput(data);
|
|
1169
|
+
} catch (error) {
|
|
1170
|
+
console.error('Failed to load programmatic output:', error);
|
|
1171
|
+
document.getElementById('output').textContent = 'Error loading programmatic output';
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// Display programmatic output with smart formatting
|
|
1176
|
+
function displayProgrammaticOutput(data) {
|
|
1177
|
+
const outputElement = document.getElementById('output');
|
|
1178
|
+
|
|
1179
|
+
if (data.isJson && data.parsedResult) {
|
|
1180
|
+
// Format JSON for clean display
|
|
1181
|
+
outputElement.textContent = JSON.stringify(data.parsedResult, null, 2);
|
|
1182
|
+
outputElement.className = 'p-3 text-xs font-mono whitespace-pre-wrap max-h-[40vh] overflow-auto json-output';
|
|
1183
|
+
} else {
|
|
1184
|
+
// Display as plain text
|
|
1185
|
+
outputElement.textContent = data.programmaticOutput;
|
|
1186
|
+
outputElement.className = 'p-3 text-xs font-mono whitespace-pre-wrap max-h-[40vh] overflow-auto';
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// Load full logs from API
|
|
1191
|
+
async function loadFullLogs(runId) {
|
|
1192
|
+
try {
|
|
1193
|
+
const response = await fetch(`${window.BASE_URL}/api/admin/scripts/runs/${runId}/full-output`);
|
|
1194
|
+
const data = await response.json();
|
|
1195
|
+
|
|
1196
|
+
if (data.fullOutput) {
|
|
1197
|
+
displayFullLogs(data.fullOutput);
|
|
1198
|
+
} else {
|
|
1199
|
+
document.getElementById('full-logs-content').textContent = 'No full logs available';
|
|
1200
|
+
}
|
|
1201
|
+
} catch (error) {
|
|
1202
|
+
console.error('Failed to load full logs:', error);
|
|
1203
|
+
document.getElementById('full-logs-content').textContent = 'Error loading full logs';
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// Display full logs with filtering
|
|
1208
|
+
function displayFullLogs(logs) {
|
|
1209
|
+
const content = document.getElementById('full-logs-content');
|
|
1210
|
+
const searchTerm = document.getElementById('log-search').value.toLowerCase();
|
|
1211
|
+
const filterType = document.getElementById('log-filter').value;
|
|
1212
|
+
|
|
1213
|
+
let filteredLogs = logs;
|
|
1214
|
+
|
|
1215
|
+
// Apply search filter
|
|
1216
|
+
if (searchTerm) {
|
|
1217
|
+
filteredLogs = logs.split('\n').filter(line =>
|
|
1218
|
+
line.toLowerCase().includes(searchTerm)
|
|
1219
|
+
).join('\n');
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
// Apply type filter
|
|
1223
|
+
if (filterType !== 'all') {
|
|
1224
|
+
const lines = filteredLogs.split('\n');
|
|
1225
|
+
filteredLogs = lines.filter(line => {
|
|
1226
|
+
// Simple heuristic to determine log type
|
|
1227
|
+
if (filterType === 'stderr') {
|
|
1228
|
+
return line.includes('❌') || line.includes('🚨') || line.includes('Error') || line.includes('error');
|
|
1229
|
+
}
|
|
1230
|
+
return true; // stdout
|
|
1231
|
+
}).join('\n');
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
content.textContent = filteredLogs || 'No logs match the current filters';
|
|
1235
|
+
|
|
1236
|
+
// Auto-scroll if enabled
|
|
1237
|
+
if (document.getElementById('btn-auto-scroll').classList.contains('bg-blue-500')) {
|
|
1238
|
+
content.scrollTop = content.scrollHeight;
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
// Download logs functionality
|
|
1243
|
+
async function downloadLogs() {
|
|
1244
|
+
if (!currentRunId) {
|
|
1245
|
+
alert('No script run selected');
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
try {
|
|
1250
|
+
const response = await fetch(`${window.BASE_URL}/api/admin/scripts/runs/${currentRunId}/download`);
|
|
1251
|
+
const blob = await response.blob();
|
|
1252
|
+
|
|
1253
|
+
const url = window.URL.createObjectURL(blob);
|
|
1254
|
+
const a = document.createElement('a');
|
|
1255
|
+
a.href = url;
|
|
1256
|
+
a.download = response.headers.get('Content-Disposition')?.split('filename=')[1]?.replace(/"/g, '') || 'script-output.txt';
|
|
1257
|
+
document.body.appendChild(a);
|
|
1258
|
+
a.click();
|
|
1259
|
+
document.body.removeChild(a);
|
|
1260
|
+
window.URL.revokeObjectURL(url);
|
|
1261
|
+
} catch (error) {
|
|
1262
|
+
console.error('Failed to download logs:', error);
|
|
1263
|
+
alert('Failed to download logs');
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
// Add event listeners for new functionality
|
|
1268
|
+
document.querySelectorAll('.output-tab').forEach(tab => {
|
|
1269
|
+
tab.addEventListener('click', () => {
|
|
1270
|
+
switchOutputTab(tab.dataset.tab);
|
|
1271
|
+
});
|
|
1272
|
+
});
|
|
1273
|
+
|
|
1274
|
+
document.getElementById('btn-download-logs').addEventListener('click', downloadLogs);
|
|
1275
|
+
|
|
1276
|
+
document.getElementById('log-search').addEventListener('input', () => {
|
|
1277
|
+
if (document.getElementById('full-logs-tab-content').classList.contains('hidden') === false) {
|
|
1278
|
+
const content = document.getElementById('full-logs-content');
|
|
1279
|
+
const currentLogs = content.dataset.originalLogs || content.textContent;
|
|
1280
|
+
content.dataset.originalLogs = currentLogs;
|
|
1281
|
+
displayFullLogs(currentLogs);
|
|
1282
|
+
}
|
|
1283
|
+
});
|
|
1284
|
+
|
|
1285
|
+
document.getElementById('log-filter').addEventListener('change', () => {
|
|
1286
|
+
if (document.getElementById('full-logs-tab-content').classList.contains('hidden') === false) {
|
|
1287
|
+
const content = document.getElementById('full-logs-content');
|
|
1288
|
+
const currentLogs = content.dataset.originalLogs || content.textContent;
|
|
1289
|
+
content.dataset.originalLogs = currentLogs;
|
|
1290
|
+
displayFullLogs(currentLogs);
|
|
1291
|
+
}
|
|
1292
|
+
});
|
|
1293
|
+
|
|
1294
|
+
document.getElementById('btn-auto-scroll').addEventListener('click', function() {
|
|
1295
|
+
this.classList.toggle('bg-blue-500');
|
|
1296
|
+
this.classList.toggle('bg-gray-100');
|
|
1297
|
+
this.textContent = this.classList.contains('bg-blue-500') ? 'Auto-scroll ON' : 'Auto-scroll';
|
|
1298
|
+
});
|
|
1299
|
+
|
|
1083
1300
|
(async function init() {
|
|
1084
1301
|
clearForm();
|
|
1085
1302
|
normalizeRunnerOptions();
|