@intranefr/superbackend 1.5.1 → 1.5.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/.env.example +10 -0
- package/index.js +2 -0
- package/manage.js +745 -0
- package/package.json +5 -2
- package/src/controllers/admin.controller.js +79 -6
- package/src/controllers/adminAgents.controller.js +37 -0
- package/src/controllers/adminExperiments.controller.js +200 -0
- package/src/controllers/adminLlm.controller.js +19 -0
- package/src/controllers/adminMarkdowns.controller.js +157 -0
- package/src/controllers/adminScripts.controller.js +243 -74
- package/src/controllers/adminTelegram.controller.js +72 -0
- package/src/controllers/experiments.controller.js +85 -0
- package/src/controllers/internalExperiments.controller.js +17 -0
- package/src/controllers/markdowns.controller.js +42 -0
- package/src/helpers/mongooseHelper.js +258 -0
- package/src/helpers/scriptBase.js +230 -0
- package/src/helpers/scriptRunner.js +335 -0
- package/src/middleware.js +195 -34
- package/src/models/Agent.js +105 -0
- package/src/models/AgentMessage.js +82 -0
- package/src/models/CacheEntry.js +1 -1
- package/src/models/ConsoleLog.js +1 -1
- package/src/models/Experiment.js +75 -0
- package/src/models/ExperimentAssignment.js +23 -0
- package/src/models/ExperimentEvent.js +26 -0
- package/src/models/ExperimentMetricBucket.js +30 -0
- package/src/models/GlobalSetting.js +1 -2
- package/src/models/Markdown.js +75 -0
- package/src/models/RateLimitCounter.js +1 -1
- package/src/models/ScriptDefinition.js +1 -0
- package/src/models/ScriptRun.js +8 -0
- package/src/models/TelegramBot.js +42 -0
- package/src/models/Webhook.js +2 -0
- package/src/routes/admin.routes.js +2 -0
- package/src/routes/adminAgents.routes.js +13 -0
- package/src/routes/adminConsoleManager.routes.js +1 -1
- package/src/routes/adminExperiments.routes.js +29 -0
- package/src/routes/adminLlm.routes.js +1 -0
- package/src/routes/adminMarkdowns.routes.js +16 -0
- package/src/routes/adminScripts.routes.js +4 -1
- package/src/routes/adminTelegram.routes.js +14 -0
- package/src/routes/blogInternal.routes.js +2 -2
- package/src/routes/experiments.routes.js +30 -0
- package/src/routes/internalExperiments.routes.js +15 -0
- package/src/routes/markdowns.routes.js +16 -0
- 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/blogCronsBootstrap.service.js +7 -6
- package/src/services/consoleManager.service.js +56 -18
- package/src/services/consoleOverride.service.js +1 -0
- package/src/services/experiments.service.js +273 -0
- package/src/services/experimentsAggregation.service.js +308 -0
- package/src/services/experimentsCronsBootstrap.service.js +118 -0
- package/src/services/experimentsRetention.service.js +43 -0
- package/src/services/experimentsWs.service.js +134 -0
- package/src/services/globalSettings.service.js +15 -0
- package/src/services/jsonConfigs.service.js +24 -12
- package/src/services/llm.service.js +219 -6
- package/src/services/markdowns.service.js +522 -0
- package/src/services/scriptsRunner.service.js +514 -23
- package/src/services/telegram.service.js +130 -0
- package/src/utils/rbac/rightsRegistry.js +4 -0
- package/views/admin-agents.ejs +273 -0
- package/views/admin-coolify-deploy.ejs +8 -8
- package/views/admin-dashboard.ejs +63 -12
- package/views/admin-experiments.ejs +91 -0
- package/views/admin-markdowns.ejs +905 -0
- package/views/admin-scripts.ejs +817 -6
- package/views/admin-telegram.ejs +269 -0
- package/views/partials/dashboard/nav-items.ejs +4 -0
- package/views/partials/dashboard/palette.ejs +5 -3
- package/src/middleware/internalCronAuth.js +0 -29
package/views/admin-scripts.ejs
CHANGED
|
@@ -5,6 +5,13 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Admin Scripts</title>
|
|
7
7
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
8
|
+
<style>
|
|
9
|
+
.search-highlight {
|
|
10
|
+
background-color: #fef3c7;
|
|
11
|
+
padding: 2px 4px;
|
|
12
|
+
border-radius: 3px;
|
|
13
|
+
}
|
|
14
|
+
</style>
|
|
8
15
|
</head>
|
|
9
16
|
<body class="bg-gray-50">
|
|
10
17
|
<div class="max-w-7xl mx-auto px-6 py-6">
|
|
@@ -21,6 +28,434 @@
|
|
|
21
28
|
</div>
|
|
22
29
|
</div>
|
|
23
30
|
|
|
31
|
+
<!-- Documentation Section -->
|
|
32
|
+
<div class="mb-6">
|
|
33
|
+
<div class="bg-white border border-gray-200 rounded-lg">
|
|
34
|
+
<div class="p-3 border-b border-gray-200 flex items-center justify-between cursor-pointer hover:bg-gray-50" id="docs-toggle">
|
|
35
|
+
<div class="text-sm font-medium text-gray-800">📚 Documentation</div>
|
|
36
|
+
<div id="docs-chevron" class="text-gray-400 transition-transform duration-200">▼</div>
|
|
37
|
+
</div>
|
|
38
|
+
<div id="docs-content" class="hidden">
|
|
39
|
+
<div class="border-b border-gray-200">
|
|
40
|
+
<div class="px-4 py-3 flex items-center justify-between">
|
|
41
|
+
<nav class="flex space-x-8" aria-label="Tabs">
|
|
42
|
+
<button class="docs-tab py-3 px-1 border-b-2 border-blue-500 font-medium text-sm text-blue-600" data-tab="quick-start">
|
|
43
|
+
Quick Start
|
|
44
|
+
</button>
|
|
45
|
+
<button class="docs-tab py-3 px-1 border-b-2 border-transparent font-medium text-sm text-gray-500 hover:text-gray-700 hover:border-gray-300" data-tab="script-types">
|
|
46
|
+
Script Types
|
|
47
|
+
</button>
|
|
48
|
+
<button class="docs-tab py-3 px-1 border-b-2 border-transparent font-medium text-sm text-gray-500 hover:text-gray-700 hover:border-gray-300" data-tab="api-reference">
|
|
49
|
+
API Reference
|
|
50
|
+
</button>
|
|
51
|
+
<button class="docs-tab py-3 px-1 border-b-2 border-transparent font-medium text-sm text-gray-500 hover:text-gray-700 hover:border-gray-300" data-tab="examples">
|
|
52
|
+
Examples
|
|
53
|
+
</button>
|
|
54
|
+
</nav>
|
|
55
|
+
<div class="flex items-center space-x-2">
|
|
56
|
+
<input type="text" id="docs-search" placeholder="Search documentation..." class="text-sm border rounded px-2 py-1 w-48" />
|
|
57
|
+
<button id="clear-search" class="text-xs text-gray-500 hover:text-gray-700">Clear</button>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
<div class="p-4">
|
|
62
|
+
<!-- Quick Start Tab -->
|
|
63
|
+
<div id="tab-quick-start" class="docs-tab-content">
|
|
64
|
+
<div class="prose prose-sm max-w-none">
|
|
65
|
+
<h3 class="text-lg font-semibold text-gray-900 mb-4">Quick Start Guide</h3>
|
|
66
|
+
|
|
67
|
+
<h4 class="text-md font-medium text-gray-800 mt-4 mb-2">Creating Your First Script</h4>
|
|
68
|
+
<ol class="list-decimal list-inside space-y-2 text-sm text-gray-600">
|
|
69
|
+
<li>Click the "New" button to create a new script</li>
|
|
70
|
+
<li>Fill in the basic information:
|
|
71
|
+
<ul class="list-disc list-inside ml-4 mt-1">
|
|
72
|
+
<li><strong>Name</strong>: Human-readable name for your script</li>
|
|
73
|
+
<li><strong>Code</strong>: Unique identifier (auto-normalized)</li>
|
|
74
|
+
<li><strong>Type</strong>: Choose between bash, node, or browser</li>
|
|
75
|
+
<li><strong>Runner</strong>: Execution environment (host, vm2, browser)</li>
|
|
76
|
+
</ul>
|
|
77
|
+
</li>
|
|
78
|
+
<li>Write your script code in the script editor</li>
|
|
79
|
+
<li>Configure environment variables if needed</li>
|
|
80
|
+
<li>Set timeout and working directory options</li>
|
|
81
|
+
<li>Click "Save" to save your script</li>
|
|
82
|
+
<li>Click "Run" to execute the script</li>
|
|
83
|
+
</ol>
|
|
84
|
+
|
|
85
|
+
<h4 class="text-md font-medium text-gray-800 mt-4 mb-2">Common Use Cases</h4>
|
|
86
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-2">
|
|
87
|
+
<div class="bg-gray-50 p-3 rounded">
|
|
88
|
+
<h5 class="font-medium text-gray-800 mb-1">🔧 System Administration</h5>
|
|
89
|
+
<p class="text-sm text-gray-600">Deployments, backups, log rotation, system updates</p>
|
|
90
|
+
</div>
|
|
91
|
+
<div class="bg-gray-50 p-3 rounded">
|
|
92
|
+
<h5 class="font-medium text-gray-800 mb-1">📊 Data Processing</h5>
|
|
93
|
+
<p class="text-sm text-gray-600">Database migrations, data imports, report generation</p>
|
|
94
|
+
</div>
|
|
95
|
+
<div class="bg-gray-50 p-3 rounded">
|
|
96
|
+
<h5 class="font-medium text-gray-800 mb-1">🔍 Health Checks</h5>
|
|
97
|
+
<p class="text-sm text-gray-600">Service monitoring, connectivity tests, performance checks</p>
|
|
98
|
+
</div>
|
|
99
|
+
<div class="bg-gray-50 p-3 rounded">
|
|
100
|
+
<h5 class="font-medium text-gray-800 mb-1">🚀 Automation</h5>
|
|
101
|
+
<p class="text-sm text-gray-600">Scheduled tasks, cleanup operations, notifications</p>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<div class="bg-amber-50 border border-amber-200 rounded p-3 mt-4">
|
|
106
|
+
<h5 class="font-medium text-amber-800 mb-1">⚠️ Security Considerations</h5>
|
|
107
|
+
<ul class="list-disc list-inside text-sm text-amber-700 space-y-1">
|
|
108
|
+
<li>Host runner scripts have full system access</li>
|
|
109
|
+
<li>VM2 runner provides sandboxing but has limitations</li>
|
|
110
|
+
<li>Never expose sensitive data in script code</li>
|
|
111
|
+
<li>Use environment variables for configuration</li>
|
|
112
|
+
<li>Set appropriate timeouts to prevent hanging</li>
|
|
113
|
+
</ul>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<!-- Script Types Tab -->
|
|
119
|
+
<div id="tab-script-types" class="docs-tab-content hidden">
|
|
120
|
+
<div class="prose prose-sm max-w-none">
|
|
121
|
+
<h3 class="text-lg font-semibold text-gray-900 mb-4">Script Types & Runners</h3>
|
|
122
|
+
|
|
123
|
+
<div class="space-y-4">
|
|
124
|
+
<div class="border border-gray-200 rounded-lg p-4">
|
|
125
|
+
<div class="flex items-center justify-between mb-2">
|
|
126
|
+
<h4 class="text-md font-medium text-gray-800">🐚 Bash + Host Runner</h4>
|
|
127
|
+
<span class="text-xs bg-green-100 text-green-800 px-2 py-1 rounded">Full System Access</span>
|
|
128
|
+
</div>
|
|
129
|
+
<p class="text-sm text-gray-600 mb-2">Execute bash scripts directly on the server host with full system access.</p>
|
|
130
|
+
<div class="bg-gray-900 text-gray-100 p-3 rounded text-xs font-mono mb-2 overflow-x-auto">
|
|
131
|
+
<pre>#!/bin/bash
|
|
132
|
+
echo "System uptime: $(uptime)"
|
|
133
|
+
echo "Disk usage:"
|
|
134
|
+
df -h | grep -E '^/dev/'
|
|
135
|
+
echo "Memory usage:"
|
|
136
|
+
free -h</pre>
|
|
137
|
+
</div>
|
|
138
|
+
<div class="bg-gray-50 p-2 rounded text-xs font-mono">
|
|
139
|
+
<div><strong>Use Case:</strong> System administration, file operations, deployments</div>
|
|
140
|
+
<div><strong>Security:</strong> High - full system access</div>
|
|
141
|
+
<div><strong>Limitations:</strong> None - can execute any bash command</div>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<div class="border border-gray-200 rounded-lg p-4">
|
|
146
|
+
<div class="flex items-center justify-between mb-2">
|
|
147
|
+
<h4 class="text-md font-medium text-gray-800">🟢 Node.js + Host Runner</h4>
|
|
148
|
+
<span class="text-xs bg-green-100 text-green-800 px-2 py-1 rounded">Full System Access</span>
|
|
149
|
+
</div>
|
|
150
|
+
<p class="text-sm text-gray-600 mb-2">Run Node.js scripts with full access to system resources and Node.js APIs.</p>
|
|
151
|
+
<div class="bg-gray-900 text-gray-100 p-3 rounded text-xs font-mono mb-2 overflow-x-auto">
|
|
152
|
+
<pre>const fs = require('fs');
|
|
153
|
+
const path = require('path');
|
|
154
|
+
|
|
155
|
+
// Read package.json and analyze dependencies
|
|
156
|
+
const packagePath = path.join(process.cwd(), 'package.json');
|
|
157
|
+
const packageData = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
158
|
+
|
|
159
|
+
const depCount = Object.keys(packageData.dependencies || {}).length;
|
|
160
|
+
const devDepCount = Object.keys(packageData.devDependencies || {}).length;
|
|
161
|
+
|
|
162
|
+
// Output results (captured in script output)
|
|
163
|
+
console.log(`Dependencies: ${depCount}`);
|
|
164
|
+
console.log(`Dev Dependencies: ${devDepCount}`);
|
|
165
|
+
console.log(`Total: ${depCount + devDepCount}`);
|
|
166
|
+
|
|
167
|
+
// Output structured data as JSON for parsing
|
|
168
|
+
const result = {
|
|
169
|
+
dependencies: depCount,
|
|
170
|
+
devDependencies: devDepCount,
|
|
171
|
+
total: depCount + devDepCount,
|
|
172
|
+
name: packageData.name,
|
|
173
|
+
version: packageData.version
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
console.log(JSON.stringify(result));</pre>
|
|
177
|
+
</div>
|
|
178
|
+
<div class="bg-gray-50 p-2 rounded text-xs font-mono">
|
|
179
|
+
<div><strong>Use Case:</strong> Complex data processing, API integrations, database operations</div>
|
|
180
|
+
<div><strong>Security:</strong> High - full system and Node.js API access</div>
|
|
181
|
+
<div><strong>Limitations:</strong> None - can use any Node.js module/API</div>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
<div class="border border-gray-200 rounded-lg p-4">
|
|
186
|
+
<div class="flex items-center justify-between mb-2">
|
|
187
|
+
<h4 class="text-md font-medium text-gray-800">🔒 Node.js + VM2 Runner</h4>
|
|
188
|
+
<span class="text-xs bg-yellow-100 text-yellow-800 px-2 py-1 rounded">Sandboxed</span>
|
|
189
|
+
</div>
|
|
190
|
+
<p class="text-sm text-gray-600 mb-2">Run Node.js scripts in a secure VM2 sandbox with limited API access.</p>
|
|
191
|
+
<div class="bg-gray-900 text-gray-100 p-3 rounded text-xs font-mono mb-2 overflow-x-auto">
|
|
192
|
+
<pre>// Safe data processing in sandbox
|
|
193
|
+
const data = [
|
|
194
|
+
{ name: 'Alice', score: 85 },
|
|
195
|
+
{ name: 'Bob', score: 92 },
|
|
196
|
+
{ name: 'Charlie', score: 78 }
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
// Calculate statistics
|
|
200
|
+
const total = data.reduce((sum, item) => sum + item.score, 0);
|
|
201
|
+
const average = total / data.length;
|
|
202
|
+
const topStudent = data.reduce((max, item) =>
|
|
203
|
+
item.score > max.score ? item : max
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
// Output human-readable results
|
|
207
|
+
console.log(`Average score: ${average.toFixed(2)}`);
|
|
208
|
+
console.log(`Top student: ${topStudent.name} (${topStudent.score})`);
|
|
209
|
+
|
|
210
|
+
// Output structured data as JSON for parsing
|
|
211
|
+
const result = {
|
|
212
|
+
average: parseFloat(average.toFixed(2)),
|
|
213
|
+
topStudent: {
|
|
214
|
+
name: topStudent.name,
|
|
215
|
+
score: topStudent.score
|
|
216
|
+
},
|
|
217
|
+
totalStudents: data.length,
|
|
218
|
+
processed: new Date().toISOString()
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
console.log(JSON.stringify(result));</pre>
|
|
222
|
+
</div>
|
|
223
|
+
<div class="bg-gray-50 p-2 rounded text-xs font-mono">
|
|
224
|
+
<div><strong>Use Case:</strong> User-submitted code, untrusted scripts, testing</div>
|
|
225
|
+
<div><strong>Security:</strong> Medium - sandboxed environment</div>
|
|
226
|
+
<div><strong>Limitations:</strong> No file system, network, or most Node.js APIs</div>
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
|
|
230
|
+
<div class="border border-gray-200 rounded-lg p-4">
|
|
231
|
+
<div class="flex items-center justify-between mb-2">
|
|
232
|
+
<h4 class="text-md font-medium text-gray-800">🌐 Browser Scripts</h4>
|
|
233
|
+
<span class="text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded">Client-side Only</span>
|
|
234
|
+
</div>
|
|
235
|
+
<p class="text-sm text-gray-600 mb-2">Execute JavaScript directly in the browser for UI interactions and client-side tasks.</p>
|
|
236
|
+
<div class="bg-gray-900 text-gray-100 p-3 rounded text-xs font-mono mb-2 overflow-x-auto">
|
|
237
|
+
<pre>// UI automation and form validation
|
|
238
|
+
const forms = document.querySelectorAll('form');
|
|
239
|
+
let totalForms = forms.length;
|
|
240
|
+
|
|
241
|
+
// Count input fields across all forms
|
|
242
|
+
let totalInputs = 0;
|
|
243
|
+
forms.forEach(form => {
|
|
244
|
+
const inputs = form.querySelectorAll('input, select, textarea');
|
|
245
|
+
totalInputs += inputs.length;
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Check for required fields and validation
|
|
249
|
+
let requiredFields = 0;
|
|
250
|
+
let emptyRequiredFields = [];
|
|
251
|
+
document.querySelectorAll('[required]').forEach(field => {
|
|
252
|
+
requiredFields++;
|
|
253
|
+
if (!field.value.trim()) {
|
|
254
|
+
emptyRequiredFields.push({
|
|
255
|
+
name: field.name || field.type || 'unnamed',
|
|
256
|
+
type: field.type,
|
|
257
|
+
id: field.id
|
|
258
|
+
});
|
|
259
|
+
field.style.borderColor = '#ef4444';
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
console.log(`Forms found: ${totalForms}`);
|
|
264
|
+
console.log(`Total input fields: ${totalInputs}`);
|
|
265
|
+
console.log(`Required fields: ${requiredFields}`);
|
|
266
|
+
console.log(`Empty required fields: ${emptyRequiredFields.length}`);
|
|
267
|
+
|
|
268
|
+
// Output structured data as JSON for parsing
|
|
269
|
+
const result = {
|
|
270
|
+
forms: totalForms,
|
|
271
|
+
totalInputs: totalInputs,
|
|
272
|
+
requiredFields: requiredFields,
|
|
273
|
+
emptyRequiredFields: emptyRequiredFields,
|
|
274
|
+
validationPassed: emptyRequiredFields.length === 0,
|
|
275
|
+
timestamp: new Date().toISOString()
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
console.log(JSON.stringify(result));</pre>
|
|
279
|
+
</div>
|
|
280
|
+
<div class="bg-gray-50 p-2 rounded text-xs font-mono">
|
|
281
|
+
<div><strong>Use Case:</strong> UI automation, form manipulation, client-side validation</div>
|
|
282
|
+
<div><strong>Security:</strong> Low - browser sandbox</div>
|
|
283
|
+
<div><strong>Limitations:</strong> No server access, browser APIs only</div>
|
|
284
|
+
</div>
|
|
285
|
+
</div>
|
|
286
|
+
</div>
|
|
287
|
+
|
|
288
|
+
<div class="bg-blue-50 border border-blue-200 rounded p-3 mt-4">
|
|
289
|
+
<h5 class="font-medium text-blue-800 mb-1">💡 Recommendation</h5>
|
|
290
|
+
<p class="text-sm text-blue-700">Use Host Runner for trusted system scripts, VM2 for untrusted code, and Browser for UI tasks.</p>
|
|
291
|
+
</div>
|
|
292
|
+
</div>
|
|
293
|
+
</div>
|
|
294
|
+
|
|
295
|
+
<!-- API Reference Tab -->
|
|
296
|
+
<div id="tab-api-reference" class="docs-tab-content hidden">
|
|
297
|
+
<div class="prose prose-sm max-w-none">
|
|
298
|
+
<h3 class="text-lg font-semibold text-gray-900 mb-4">API Reference</h3>
|
|
299
|
+
|
|
300
|
+
<h4 class="text-md font-medium text-gray-800 mt-4 mb-2">Script Management</h4>
|
|
301
|
+
<div class="space-y-2">
|
|
302
|
+
<div class="bg-gray-50 p-3 rounded">
|
|
303
|
+
<div class="font-mono text-sm font-medium text-gray-800">GET /api/admin/scripts</div>
|
|
304
|
+
<p class="text-sm text-gray-600 mt-1">List all script definitions</p>
|
|
305
|
+
</div>
|
|
306
|
+
<div class="bg-gray-50 p-3 rounded">
|
|
307
|
+
<div class="font-mono text-sm font-medium text-gray-800">POST /api/admin/scripts</div>
|
|
308
|
+
<p class="text-sm text-gray-600 mt-1">Create a new script definition</p>
|
|
309
|
+
</div>
|
|
310
|
+
<div class="bg-gray-50 p-3 rounded">
|
|
311
|
+
<div class="font-mono text-sm font-medium text-gray-800">GET /api/admin/scripts/:id</div>
|
|
312
|
+
<p class="text-sm text-gray-600 mt-1">Get a specific script definition</p>
|
|
313
|
+
</div>
|
|
314
|
+
<div class="bg-gray-50 p-3 rounded">
|
|
315
|
+
<div class="font-mono text-sm font-medium text-gray-800">PUT /api/admin/scripts/:id</div>
|
|
316
|
+
<p class="text-sm text-gray-600 mt-1">Update a script definition</p>
|
|
317
|
+
</div>
|
|
318
|
+
<div class="bg-gray-50 p-3 rounded">
|
|
319
|
+
<div class="font-mono text-sm font-medium text-gray-800">DELETE /api/admin/scripts/:id</div>
|
|
320
|
+
<p class="text-sm text-gray-600 mt-1">Delete a script definition</p>
|
|
321
|
+
</div>
|
|
322
|
+
</div>
|
|
323
|
+
|
|
324
|
+
<h4 class="text-md font-medium text-gray-800 mt-4 mb-2">Execution Control</h4>
|
|
325
|
+
<div class="space-y-2">
|
|
326
|
+
<div class="bg-gray-50 p-3 rounded">
|
|
327
|
+
<div class="font-mono text-sm font-medium text-gray-800">POST /api/admin/scripts/:id/run</div>
|
|
328
|
+
<p class="text-sm text-gray-600 mt-1">Execute a script and return run ID</p>
|
|
329
|
+
</div>
|
|
330
|
+
<div class="bg-gray-50 p-3 rounded">
|
|
331
|
+
<div class="font-mono text-sm font-medium text-gray-800">GET /api/admin/scripts/runs/:runId</div>
|
|
332
|
+
<p class="text-sm text-gray-600 mt-1">Get execution results and status</p>
|
|
333
|
+
</div>
|
|
334
|
+
<div class="bg-gray-50 p-3 rounded">
|
|
335
|
+
<div class="font-mono text-sm font-medium text-gray-800">GET /api/admin/scripts/runs</div>
|
|
336
|
+
<p class="text-sm text-gray-600 mt-1">List all script runs (optional scriptId filter)</p>
|
|
337
|
+
</div>
|
|
338
|
+
</div>
|
|
339
|
+
|
|
340
|
+
<h4 class="text-md font-medium text-gray-800 mt-4 mb-2">Real-time Streaming</h4>
|
|
341
|
+
<div class="bg-gray-50 p-3 rounded">
|
|
342
|
+
<div class="font-mono text-sm font-medium text-gray-800">GET /api/admin/scripts/runs/:runId/stream</div>
|
|
343
|
+
<p class="text-sm text-gray-600 mt-1">Server-Sent Events stream for live output</p>
|
|
344
|
+
<div class="mt-2 text-xs text-gray-500">
|
|
345
|
+
<div>Events: log, status, done, error</div>
|
|
346
|
+
<div>Query: ?since=N to get events after sequence N</div>
|
|
347
|
+
</div>
|
|
348
|
+
</div>
|
|
349
|
+
|
|
350
|
+
<div class="bg-green-50 border border-green-200 rounded p-3 mt-4">
|
|
351
|
+
<h5 class="font-medium text-green-800 mb-1">✅ Authentication</h5>
|
|
352
|
+
<p class="text-sm text-green-700">All endpoints require basic authentication with admin credentials.</p>
|
|
353
|
+
</div>
|
|
354
|
+
</div>
|
|
355
|
+
</div>
|
|
356
|
+
|
|
357
|
+
<!-- Examples Tab -->
|
|
358
|
+
<div id="tab-examples" class="docs-tab-content hidden">
|
|
359
|
+
<div class="prose prose-sm max-w-none">
|
|
360
|
+
<h3 class="text-lg font-semibold text-gray-900 mb-4">Programmatic Examples</h3>
|
|
361
|
+
|
|
362
|
+
<h4 class="text-md font-medium text-gray-800 mt-4 mb-2">Direct Service Call</h4>
|
|
363
|
+
<div class="relative">
|
|
364
|
+
<button class="copy-btn absolute top-2 right-2 bg-gray-700 text-white text-xs px-2 py-1 rounded hover:bg-gray-600" data-copy="service-call">📋 Copy</button>
|
|
365
|
+
<div class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto">
|
|
366
|
+
<pre class="text-sm"><code id="service-call">const { startRun } = require('./services/scriptsRunner.service');
|
|
367
|
+
const ScriptDefinition = require('./models/ScriptDefinition');
|
|
368
|
+
|
|
369
|
+
// Get script definition
|
|
370
|
+
const script = await ScriptDefinition.findById(scriptId);
|
|
371
|
+
if (!script || !script.enabled) throw new Error('Script not found or disabled');
|
|
372
|
+
|
|
373
|
+
// Run script programmatically
|
|
374
|
+
const runDoc = await startRun(script, {
|
|
375
|
+
trigger: 'api',
|
|
376
|
+
meta: { actorType: 'system', customData: '...' }
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
console.log('Run ID:', runDoc._id);</code></pre>
|
|
380
|
+
</div>
|
|
381
|
+
</div>
|
|
382
|
+
|
|
383
|
+
<h4 class="text-md font-medium text-gray-800 mt-4 mb-2">HTTP API Call</h4>
|
|
384
|
+
<div class="relative">
|
|
385
|
+
<button class="copy-btn absolute top-2 right-2 bg-gray-700 text-white text-xs px-2 py-1 rounded hover:bg-gray-600" data-copy="http-api">📋 Copy</button>
|
|
386
|
+
<div class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto">
|
|
387
|
+
<pre class="text-sm"><code id="http-api">// Execute script via HTTP API
|
|
388
|
+
const response = await fetch('/api/admin/scripts/scriptId/run', {
|
|
389
|
+
method: 'POST',
|
|
390
|
+
headers: {
|
|
391
|
+
'Authorization': 'Basic ' + btoa('username:password'),
|
|
392
|
+
'Content-Type': 'application/json'
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
const { runId } = await response.json();
|
|
396
|
+
|
|
397
|
+
// Get results
|
|
398
|
+
const runResult = await fetch('/api/admin/scripts/runs/' + runId);
|
|
399
|
+
const run = await runResult.json();
|
|
400
|
+
console.log('Status:', run.status, 'Exit Code:', run.exitCode);</code></pre>
|
|
401
|
+
</div>
|
|
402
|
+
</div>
|
|
403
|
+
|
|
404
|
+
<h4 class="text-md font-medium text-gray-800 mt-4 mb-2">Real-time Streaming</h4>
|
|
405
|
+
<div class="relative">
|
|
406
|
+
<button class="copy-btn absolute top-2 right-2 bg-gray-700 text-white text-xs px-2 py-1 rounded hover:bg-gray-600" data-copy="streaming">📋 Copy</button>
|
|
407
|
+
<div class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto">
|
|
408
|
+
<pre class="text-sm"><code id="streaming">const eventSource = new EventSource('/api/admin/scripts/runs/' + runId + '/stream');
|
|
409
|
+
|
|
410
|
+
eventSource.addEventListener('log', (e) => {
|
|
411
|
+
const data = JSON.parse(e.data);
|
|
412
|
+
console.log('Output:', data.line);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
eventSource.addEventListener('status', (e) => {
|
|
416
|
+
const data = JSON.parse(e.data);
|
|
417
|
+
console.log('Status changed:', data.status);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
eventSource.addEventListener('done', (e) => {
|
|
421
|
+
console.log('Execution completed');
|
|
422
|
+
eventSource.close();
|
|
423
|
+
});</code></pre>
|
|
424
|
+
</div>
|
|
425
|
+
</div>
|
|
426
|
+
|
|
427
|
+
<h4 class="text-md font-medium text-gray-800 mt-4 mb-2">Database Query</h4>
|
|
428
|
+
<div class="relative">
|
|
429
|
+
<button class="copy-btn absolute top-2 right-2 bg-gray-700 text-white text-xs px-2 py-1 rounded hover:bg-gray-600" data-copy="database-query">📋 Copy</button>
|
|
430
|
+
<div class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto">
|
|
431
|
+
<pre class="text-sm"><code id="database-query">const ScriptRun = require('./models/ScriptRun');
|
|
432
|
+
|
|
433
|
+
// Get run status and results
|
|
434
|
+
const run = await ScriptRun.findById(runId);
|
|
435
|
+
console.log('Status:', run.status);
|
|
436
|
+
console.log('Exit code:', run.exitCode);
|
|
437
|
+
console.log('Output tail:', run.outputTail);
|
|
438
|
+
|
|
439
|
+
// List recent runs
|
|
440
|
+
const recentRuns = await ScriptRun.find()
|
|
441
|
+
.sort({ createdAt: -1 })
|
|
442
|
+
.limit(10)
|
|
443
|
+
.populate('scriptId')
|
|
444
|
+
.lean();</code></pre>
|
|
445
|
+
</div>
|
|
446
|
+
</div>
|
|
447
|
+
|
|
448
|
+
<div class="bg-purple-50 border border-purple-200 rounded p-3 mt-4">
|
|
449
|
+
<h5 class="font-medium text-purple-800 mb-1">🔗 Integration Examples</h5>
|
|
450
|
+
<p class="text-sm text-purple-700">Scripts are used by Cron Scheduler, Health Checks, and can be integrated into any system component.</p>
|
|
451
|
+
</div>
|
|
452
|
+
</div>
|
|
453
|
+
</div>
|
|
454
|
+
</div>
|
|
455
|
+
</div>
|
|
456
|
+
</div>
|
|
457
|
+
</div>
|
|
458
|
+
|
|
24
459
|
<div class="grid grid-cols-12 gap-6">
|
|
25
460
|
<div class="col-span-4">
|
|
26
461
|
<div class="bg-white border border-gray-200 rounded-lg">
|
|
@@ -114,11 +549,44 @@
|
|
|
114
549
|
</div>
|
|
115
550
|
|
|
116
551
|
<div class="mt-4 bg-white border border-gray-200 rounded-lg">
|
|
117
|
-
<div class="p-3 border-b border-gray-200
|
|
118
|
-
<div class="
|
|
119
|
-
|
|
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>
|
|
120
589
|
</div>
|
|
121
|
-
<pre id="output" class="p-3 text-xs font-mono whitespace-pre-wrap max-h-[40vh] overflow-auto"></pre>
|
|
122
590
|
</div>
|
|
123
591
|
</div>
|
|
124
592
|
</div>
|
|
@@ -136,6 +604,8 @@
|
|
|
136
604
|
es: null,
|
|
137
605
|
};
|
|
138
606
|
|
|
607
|
+
let currentRunId = null;
|
|
608
|
+
|
|
139
609
|
function qs(id) {
|
|
140
610
|
return document.getElementById(id);
|
|
141
611
|
}
|
|
@@ -214,6 +684,10 @@
|
|
|
214
684
|
env.push({ key, value });
|
|
215
685
|
});
|
|
216
686
|
|
|
687
|
+
// Encode script content as base64 to avoid JSON parsing issues
|
|
688
|
+
const scriptContent = qs('f-script').value;
|
|
689
|
+
const scriptBase64 = btoa(unescape(encodeURIComponent(scriptContent)));
|
|
690
|
+
|
|
217
691
|
return {
|
|
218
692
|
name: qs('f-name').value.trim(),
|
|
219
693
|
codeIdentifier: qs('f-code').value.trim(),
|
|
@@ -224,7 +698,8 @@
|
|
|
224
698
|
defaultWorkingDirectory: qs('f-cwd').value,
|
|
225
699
|
enabled: !!qs('f-enabled').checked,
|
|
226
700
|
env,
|
|
227
|
-
script:
|
|
701
|
+
script: scriptBase64,
|
|
702
|
+
scriptFormat: 'base64',
|
|
228
703
|
};
|
|
229
704
|
}
|
|
230
705
|
|
|
@@ -274,7 +749,19 @@
|
|
|
274
749
|
qs('f-timeout').value = String(s.timeoutMs || 300000);
|
|
275
750
|
qs('f-cwd').value = s.defaultWorkingDirectory || '';
|
|
276
751
|
qs('f-enabled').checked = !!s.enabled;
|
|
277
|
-
|
|
752
|
+
|
|
753
|
+
// Decode script content if it's base64 encoded
|
|
754
|
+
let scriptContent = s.script || '';
|
|
755
|
+
if (s.scriptFormat === 'base64') {
|
|
756
|
+
try {
|
|
757
|
+
scriptContent = decodeURIComponent(escape(atob(scriptContent)));
|
|
758
|
+
} catch (err) {
|
|
759
|
+
console.error('Failed to decode base64 script content:', err);
|
|
760
|
+
scriptContent = s.script || ''; // Fallback to raw content
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
qs('f-script').value = scriptContent;
|
|
764
|
+
|
|
278
765
|
renderEnv(s.env || []);
|
|
279
766
|
loadRuns();
|
|
280
767
|
}
|
|
@@ -320,8 +807,12 @@
|
|
|
320
807
|
<div class="text-xs text-gray-500 font-mono">${String(r._id || '').slice(0, 10)} · ${String(r.createdAt || '').replace(/</g,'<')}</div>
|
|
321
808
|
`;
|
|
322
809
|
btn.addEventListener('click', () => {
|
|
810
|
+
currentRunId = r._id; // Set the current run ID when clicking on a previous run
|
|
323
811
|
setOutput('');
|
|
324
812
|
if (r.outputTail) setOutput(r.outputTail, true);
|
|
813
|
+
|
|
814
|
+
// Load programmatic output for the selected run
|
|
815
|
+
loadProgrammaticOutput(r._id);
|
|
325
816
|
});
|
|
326
817
|
list.appendChild(btn);
|
|
327
818
|
});
|
|
@@ -442,8 +933,16 @@
|
|
|
442
933
|
|
|
443
934
|
setOutput('');
|
|
444
935
|
const res = await api('/api/admin/scripts/' + encodeURIComponent(state.selectedId) + '/run', { method: 'POST' });
|
|
936
|
+
currentRunId = res.runId; // Set the current run ID
|
|
445
937
|
startSse(res.runId);
|
|
446
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
|
|
447
946
|
}
|
|
448
947
|
|
|
449
948
|
qs('btn-refresh').addEventListener('click', loadScripts);
|
|
@@ -487,9 +986,321 @@
|
|
|
487
986
|
});
|
|
488
987
|
qs('f-runner').addEventListener('change', setRunnerWarning);
|
|
489
988
|
|
|
989
|
+
// Documentation section functionality
|
|
990
|
+
function initDocumentation() {
|
|
991
|
+
const docsToggle = qs('docs-toggle');
|
|
992
|
+
const docsContent = qs('docs-content');
|
|
993
|
+
const docsChevron = qs('docs-chevron');
|
|
994
|
+
const searchInput = qs('docs-search');
|
|
995
|
+
const clearSearchBtn = qs('clear-search');
|
|
996
|
+
|
|
997
|
+
// Load saved state from localStorage
|
|
998
|
+
const docsExpanded = localStorage.getItem('admin-scripts-docs-expanded') === 'true';
|
|
999
|
+
if (docsExpanded) {
|
|
1000
|
+
docsContent.classList.remove('hidden');
|
|
1001
|
+
docsChevron.style.transform = 'rotate(180deg)';
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// Toggle documentation section
|
|
1005
|
+
docsToggle.addEventListener('click', () => {
|
|
1006
|
+
const isHidden = docsContent.classList.contains('hidden');
|
|
1007
|
+
if (isHidden) {
|
|
1008
|
+
docsContent.classList.remove('hidden');
|
|
1009
|
+
docsChevron.style.transform = 'rotate(180deg)';
|
|
1010
|
+
localStorage.setItem('admin-scripts-docs-expanded', 'true');
|
|
1011
|
+
} else {
|
|
1012
|
+
docsContent.classList.add('hidden');
|
|
1013
|
+
docsChevron.style.transform = 'rotate(0deg)';
|
|
1014
|
+
localStorage.setItem('admin-scripts-docs-expanded', 'false');
|
|
1015
|
+
}
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
// Tab switching functionality
|
|
1019
|
+
const tabs = document.querySelectorAll('.docs-tab');
|
|
1020
|
+
const tabContents = document.querySelectorAll('.docs-tab-content');
|
|
1021
|
+
|
|
1022
|
+
tabs.forEach(tab => {
|
|
1023
|
+
tab.addEventListener('click', () => {
|
|
1024
|
+
const targetTab = tab.dataset.tab;
|
|
1025
|
+
|
|
1026
|
+
// Update tab styles
|
|
1027
|
+
tabs.forEach(t => {
|
|
1028
|
+
t.classList.remove('border-blue-500', 'text-blue-600');
|
|
1029
|
+
t.classList.add('border-transparent', 'text-gray-500');
|
|
1030
|
+
});
|
|
1031
|
+
tab.classList.remove('border-transparent', 'text-gray-500');
|
|
1032
|
+
tab.classList.add('border-blue-500', 'text-blue-600');
|
|
1033
|
+
|
|
1034
|
+
// Show/hide content
|
|
1035
|
+
tabContents.forEach(content => {
|
|
1036
|
+
if (content.id === `tab-${targetTab}`) {
|
|
1037
|
+
content.classList.remove('hidden');
|
|
1038
|
+
} else {
|
|
1039
|
+
content.classList.add('hidden');
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
});
|
|
1043
|
+
});
|
|
1044
|
+
|
|
1045
|
+
// Search functionality
|
|
1046
|
+
function performSearch(query) {
|
|
1047
|
+
const searchTerm = query.toLowerCase().trim();
|
|
1048
|
+
const allContent = document.querySelectorAll('.docs-tab-content');
|
|
1049
|
+
|
|
1050
|
+
if (!searchTerm) {
|
|
1051
|
+
// Reset all content visibility
|
|
1052
|
+
allContent.forEach(content => {
|
|
1053
|
+
const elements = content.querySelectorAll('h3, h4, h5, p, li, div, pre');
|
|
1054
|
+
elements.forEach(el => {
|
|
1055
|
+
el.style.display = '';
|
|
1056
|
+
el.classList.remove('search-highlight');
|
|
1057
|
+
});
|
|
1058
|
+
});
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
allContent.forEach(content => {
|
|
1063
|
+
const elements = content.querySelectorAll('h3, h4, h5, p, li, div, pre');
|
|
1064
|
+
let hasVisibleContent = false;
|
|
1065
|
+
|
|
1066
|
+
elements.forEach(el => {
|
|
1067
|
+
const text = el.textContent.toLowerCase();
|
|
1068
|
+
if (text.includes(searchTerm)) {
|
|
1069
|
+
el.style.display = '';
|
|
1070
|
+
el.classList.add('search-highlight');
|
|
1071
|
+
hasVisibleContent = true;
|
|
1072
|
+
} else {
|
|
1073
|
+
el.style.display = 'none';
|
|
1074
|
+
el.classList.remove('search-highlight');
|
|
1075
|
+
}
|
|
1076
|
+
});
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
searchInput.addEventListener('input', (e) => {
|
|
1081
|
+
performSearch(e.target.value);
|
|
1082
|
+
});
|
|
1083
|
+
|
|
1084
|
+
clearSearchBtn.addEventListener('click', () => {
|
|
1085
|
+
searchInput.value = '';
|
|
1086
|
+
performSearch('');
|
|
1087
|
+
});
|
|
1088
|
+
|
|
1089
|
+
// Copy to clipboard functionality
|
|
1090
|
+
const copyButtons = document.querySelectorAll('.copy-btn');
|
|
1091
|
+
|
|
1092
|
+
copyButtons.forEach(btn => {
|
|
1093
|
+
btn.addEventListener('click', async () => {
|
|
1094
|
+
const targetId = btn.dataset.copy;
|
|
1095
|
+
const targetElement = document.getElementById(targetId);
|
|
1096
|
+
|
|
1097
|
+
if (targetElement) {
|
|
1098
|
+
try {
|
|
1099
|
+
await navigator.clipboard.writeText(targetElement.textContent);
|
|
1100
|
+
const originalText = btn.textContent;
|
|
1101
|
+
btn.textContent = '✅ Copied!';
|
|
1102
|
+
btn.classList.add('bg-green-600');
|
|
1103
|
+
btn.classList.remove('bg-gray-700');
|
|
1104
|
+
|
|
1105
|
+
setTimeout(() => {
|
|
1106
|
+
btn.textContent = originalText;
|
|
1107
|
+
btn.classList.remove('bg-green-600');
|
|
1108
|
+
btn.classList.add('bg-gray-700');
|
|
1109
|
+
}, 2000);
|
|
1110
|
+
} catch (err) {
|
|
1111
|
+
// Fallback for older browsers
|
|
1112
|
+
const textArea = document.createElement('textarea');
|
|
1113
|
+
textArea.value = targetElement.textContent;
|
|
1114
|
+
document.body.appendChild(textArea);
|
|
1115
|
+
textArea.select();
|
|
1116
|
+
document.execCommand('copy');
|
|
1117
|
+
document.body.removeChild(textArea);
|
|
1118
|
+
|
|
1119
|
+
const originalText = btn.textContent;
|
|
1120
|
+
btn.textContent = '✅ Copied!';
|
|
1121
|
+
setTimeout(() => {
|
|
1122
|
+
btn.textContent = originalText;
|
|
1123
|
+
}, 2000);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
});
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
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
|
+
|
|
490
1300
|
(async function init() {
|
|
491
1301
|
clearForm();
|
|
492
1302
|
normalizeRunnerOptions();
|
|
1303
|
+
initDocumentation();
|
|
493
1304
|
await loadScripts();
|
|
494
1305
|
})();
|
|
495
1306
|
</script>
|