@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.
Files changed (134) hide show
  1. package/cookies.txt +6 -0
  2. package/cookies1.txt +6 -0
  3. package/cookies2.txt +6 -0
  4. package/cookies3.txt +6 -0
  5. package/cookies4.txt +5 -0
  6. package/cookies_old.txt +5 -0
  7. package/cookies_old_test.txt +6 -0
  8. package/cookies_super.txt +5 -0
  9. package/cookies_super_test.txt +6 -0
  10. package/cookies_test.txt +6 -0
  11. package/index.js +9 -0
  12. package/manage.js +745 -0
  13. package/package.json +6 -2
  14. package/plugins/core-waiting-list-migration/README.md +118 -0
  15. package/plugins/core-waiting-list-migration/index.js +438 -0
  16. package/plugins/global-settings-presets/index.js +20 -0
  17. package/plugins/hello-cli/index.js +17 -0
  18. package/plugins/ui-components-seeder/components/suiAlert.js +212 -0
  19. package/plugins/ui-components-seeder/components/suiToast.js +186 -0
  20. package/plugins/ui-components-seeder/index.js +31 -0
  21. package/public/js/admin-ui-components-preview.js +281 -0
  22. package/public/js/admin-ui-components.js +408 -0
  23. package/public/js/llm-provider-model-picker.js +193 -0
  24. package/public/test-iframe-fix.html +63 -0
  25. package/public/test-iframe.html +14 -0
  26. package/src/admin/endpointRegistry.js +68 -0
  27. package/src/controllers/admin.controller.js +36 -10
  28. package/src/controllers/adminAgents.controller.js +37 -0
  29. package/src/controllers/adminDataCleanup.controller.js +45 -0
  30. package/src/controllers/adminLlm.controller.js +19 -8
  31. package/src/controllers/adminLogin.controller.js +269 -0
  32. package/src/controllers/adminMarkdowns.controller.js +157 -0
  33. package/src/controllers/adminPlugins.controller.js +55 -0
  34. package/src/controllers/adminRegistry.controller.js +106 -0
  35. package/src/controllers/adminScripts.controller.js +138 -0
  36. package/src/controllers/adminStats.controller.js +4 -4
  37. package/src/controllers/adminTelegram.controller.js +72 -0
  38. package/src/controllers/markdowns.controller.js +42 -0
  39. package/src/controllers/registry.controller.js +32 -0
  40. package/src/controllers/waitingList.controller.js +52 -74
  41. package/src/helpers/mongooseHelper.js +6 -6
  42. package/src/helpers/scriptBase.js +2 -2
  43. package/src/middleware/auth.js +71 -1
  44. package/src/middleware/rbac.js +62 -0
  45. package/src/middleware.js +584 -176
  46. package/src/models/Agent.js +105 -0
  47. package/src/models/AgentMessage.js +82 -0
  48. package/src/models/GlobalSetting.js +11 -1
  49. package/src/models/Markdown.js +75 -0
  50. package/src/models/ScriptRun.js +8 -0
  51. package/src/models/TelegramBot.js +42 -0
  52. package/src/models/UiComponent.js +2 -0
  53. package/src/models/User.js +1 -1
  54. package/src/routes/admin.routes.js +3 -3
  55. package/src/routes/adminAgents.routes.js +13 -0
  56. package/src/routes/adminAssets.routes.js +11 -11
  57. package/src/routes/adminBlog.routes.js +2 -2
  58. package/src/routes/adminBlogAi.routes.js +2 -2
  59. package/src/routes/adminBlogAutomation.routes.js +2 -2
  60. package/src/routes/adminCache.routes.js +2 -2
  61. package/src/routes/adminConsoleManager.routes.js +2 -2
  62. package/src/routes/adminCrons.routes.js +2 -2
  63. package/src/routes/adminDataCleanup.routes.js +26 -0
  64. package/src/routes/adminDbBrowser.routes.js +2 -2
  65. package/src/routes/adminEjsVirtual.routes.js +2 -2
  66. package/src/routes/adminFeatureFlags.routes.js +6 -6
  67. package/src/routes/adminHeadless.routes.js +2 -2
  68. package/src/routes/adminHealthChecks.routes.js +2 -2
  69. package/src/routes/adminI18n.routes.js +2 -2
  70. package/src/routes/adminJsonConfigs.routes.js +8 -8
  71. package/src/routes/adminLlm.routes.js +8 -7
  72. package/src/routes/adminLogin.routes.js +23 -0
  73. package/src/routes/adminMarkdowns.routes.js +10 -0
  74. package/src/routes/adminMigration.routes.js +12 -12
  75. package/src/routes/adminPages.routes.js +2 -2
  76. package/src/routes/adminPlugins.routes.js +15 -0
  77. package/src/routes/adminProxy.routes.js +2 -2
  78. package/src/routes/adminRateLimits.routes.js +8 -8
  79. package/src/routes/adminRbac.routes.js +2 -2
  80. package/src/routes/adminRegistry.routes.js +24 -0
  81. package/src/routes/adminScripts.routes.js +6 -3
  82. package/src/routes/adminSeoConfig.routes.js +10 -10
  83. package/src/routes/adminTelegram.routes.js +14 -0
  84. package/src/routes/adminTerminals.routes.js +2 -2
  85. package/src/routes/adminUiComponents.routes.js +2 -2
  86. package/src/routes/adminUploadNamespaces.routes.js +7 -7
  87. package/src/routes/blogInternal.routes.js +2 -2
  88. package/src/routes/experiments.routes.js +2 -2
  89. package/src/routes/formsAdmin.routes.js +6 -6
  90. package/src/routes/globalSettings.routes.js +8 -8
  91. package/src/routes/internalExperiments.routes.js +2 -2
  92. package/src/routes/markdowns.routes.js +16 -0
  93. package/src/routes/notificationAdmin.routes.js +7 -7
  94. package/src/routes/orgAdmin.routes.js +16 -16
  95. package/src/routes/pages.routes.js +3 -3
  96. package/src/routes/registry.routes.js +11 -0
  97. package/src/routes/stripeAdmin.routes.js +12 -12
  98. package/src/routes/userAdmin.routes.js +7 -7
  99. package/src/routes/waitingListAdmin.routes.js +2 -2
  100. package/src/routes/workflows.routes.js +3 -3
  101. package/src/services/agent.service.js +546 -0
  102. package/src/services/agentHistory.service.js +345 -0
  103. package/src/services/agentTools.service.js +578 -0
  104. package/src/services/dataCleanup.service.js +286 -0
  105. package/src/services/jsonConfigs.service.js +284 -10
  106. package/src/services/llm.service.js +219 -6
  107. package/src/services/markdowns.service.js +522 -0
  108. package/src/services/plugins.service.js +348 -0
  109. package/src/services/registry.service.js +452 -0
  110. package/src/services/scriptsRunner.service.js +328 -37
  111. package/src/services/telegram.service.js +130 -0
  112. package/src/services/uiComponents.service.js +180 -0
  113. package/src/services/waitingListJson.service.js +401 -0
  114. package/src/utils/rbac/rightsRegistry.js +118 -0
  115. package/test-access.js +63 -0
  116. package/test-iframe-fix.html +63 -0
  117. package/test-iframe.html +14 -0
  118. package/views/admin-403.ejs +92 -0
  119. package/views/admin-agents.ejs +273 -0
  120. package/views/admin-coolify-deploy.ejs +8 -8
  121. package/views/admin-dashboard-home.ejs +52 -2
  122. package/views/admin-dashboard.ejs +179 -7
  123. package/views/admin-data-cleanup.ejs +357 -0
  124. package/views/admin-experiments.ejs +1 -1
  125. package/views/admin-login.ejs +286 -0
  126. package/views/admin-markdowns.ejs +905 -0
  127. package/views/admin-plugins-system.ejs +223 -0
  128. package/views/admin-scripts.ejs +221 -4
  129. package/views/admin-telegram.ejs +269 -0
  130. package/views/admin-ui-components.ejs +82 -402
  131. package/views/admin-users.ejs +207 -11
  132. package/views/partials/dashboard/nav-items.ejs +5 -0
  133. package/views/partials/llm-provider-model-picker.ejs +0 -161
  134. package/analysis-only.skill +0 -0
package/test-access.js ADDED
@@ -0,0 +1,63 @@
1
+ require('dotenv').config();
2
+ const mongoose = require('mongoose');
3
+
4
+ async function testAccessControl() {
5
+ try {
6
+ await mongoose.connect(process.env.MONGODB_URI, { authSource: 'admin' });
7
+ console.log('✅ Connected to MongoDB');
8
+
9
+ // Check if roles and grants exist
10
+ const RbacRole = require('./src/models/RbacRole');
11
+ const RbacGrant = require('./src/models/RbacGrant');
12
+ const User = require('./src/models/User');
13
+
14
+ // Find the limited-admin user
15
+ const user = await User.findOne({ email: 'limitedadmin@example.com' });
16
+
17
+ if (!user) {
18
+ console.log('❌ User not found');
19
+ return;
20
+ }
21
+
22
+ console.log(`✅ Found user: ${user.email} (${user.role})`);
23
+
24
+ // Find the limited-admin role
25
+ const limitedAdminRole = await RbacRole.findOne({ key: 'limited-admin' });
26
+ if (!limitedAdminRole) {
27
+ console.log('❌ limited-admin role not found');
28
+ return;
29
+ }
30
+
31
+ console.log(`✅ Found role: ${limitedAdminRole.name}`);
32
+
33
+ // Check grants for the role
34
+ const grants = await RbacGrant.find({
35
+ subjectType: 'role',
36
+ subjectId: limitedAdminRole._id
37
+ });
38
+
39
+ console.log(`\n📋 Role grants (${grants.length}):`);
40
+ grants.forEach(grant => {
41
+ console.log(` - ${grant.right}`);
42
+ });
43
+
44
+ // Check specific permissions
45
+ const auditGrant = grants.find(g => g.right === 'admin_panel__audit:read');
46
+ const usersGrant = grants.find(g => g.right === 'admin_panel__users:read');
47
+ const errorsGrant = grants.find(g => g.right === 'admin_panel__errors:read');
48
+
49
+ console.log('\n🔍 Permission Summary:');
50
+ console.log(`Audit access: ${auditGrant ? '✅ ALLOWED' : '❌ DENIED'}`);
51
+ console.log(`Users access: ${usersGrant ? '✅ ALLOWED' : '❌ DENIED'}`);
52
+ console.log(`Errors access: ${errorsGrant ? '✅ ALLOWED' : '❌ DENIED'}`);
53
+
54
+ console.log('\n🎉 Access control test completed!');
55
+
56
+ } catch (error) {
57
+ console.error('❌ Test failed:', error);
58
+ } finally {
59
+ await mongoose.disconnect();
60
+ }
61
+ }
62
+
63
+ testAccessControl();
@@ -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>
@@ -0,0 +1,92 @@
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>Access Denied - SuperBackend Admin</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/dist/tabler-icons.min.css">
9
+ </head>
10
+ <body class="bg-gray-50 min-h-screen flex items-center justify-center">
11
+ <div class="max-w-md w-full bg-white rounded-lg shadow-lg p-8">
12
+ <div class="text-center">
13
+ <!-- Error Icon -->
14
+ <div class="mx-auto w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mb-4">
15
+ <i class="ti ti-lock text-red-600 text-2xl"></i>
16
+ </div>
17
+
18
+ <!-- Error Title -->
19
+ <h1 class="text-2xl font-bold text-gray-900 mb-2">Access Denied</h1>
20
+
21
+ <!-- Error Message -->
22
+ <p class="text-gray-600 mb-6">
23
+ You don't have permission to access this module.
24
+ </p>
25
+
26
+ <!-- Permission Details -->
27
+ <div class="bg-gray-50 rounded-lg p-4 mb-6 text-left">
28
+ <h3 class="text-sm font-semibold text-gray-700 mb-2">Permission Details:</h3>
29
+ <div class="space-y-1 text-xs">
30
+ <div class="flex justify-between">
31
+ <span class="text-gray-600">Module:</span>
32
+ <span class="font-mono text-gray-800"><%= moduleId %></span>
33
+ </div>
34
+ <div class="flex justify-between">
35
+ <span class="text-gray-600">Action:</span>
36
+ <span class="font-mono text-gray-800"><%= action %></span>
37
+ </div>
38
+ <div class="flex justify-between">
39
+ <span class="text-gray-600">Required:</span>
40
+ <span class="font-mono text-gray-800 break-all"><%= required %></span>
41
+ </div>
42
+ <% if (reason) { %>
43
+ <div class="flex justify-between">
44
+ <span class="text-gray-600">Reason:</span>
45
+ <span class="font-mono text-gray-800"><%= reason %></span>
46
+ </div>
47
+ <% } %>
48
+ </div>
49
+ </div>
50
+
51
+ <!-- User Info -->
52
+ <% if (user) { %>
53
+ <div class="bg-blue-50 rounded-lg p-4 mb-6 text-left">
54
+ <h3 class="text-sm font-semibold text-blue-700 mb-2">Current User:</h3>
55
+ <div class="space-y-1 text-xs">
56
+ <div class="flex justify-between">
57
+ <span class="text-blue-600">Name:</span>
58
+ <span class="font-mono text-blue-800"><%= user.name || 'N/A' %></span>
59
+ </div>
60
+ <div class="flex justify-between">
61
+ <span class="text-blue-600">Email:</span>
62
+ <span class="font-mono text-blue-800 break-all"><%= user.email %></span>
63
+ </div>
64
+ <div class="flex justify-between">
65
+ <span class="text-blue-600">Role:</span>
66
+ <span class="font-mono text-blue-800"><%= user.role || 'N/A' %></span>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ <% } %>
71
+
72
+ <!-- Action Buttons -->
73
+ <div class="space-y-3">
74
+ <a href="<%= adminPath %>" class="w-full bg-blue-600 text-white py-2 px-4 rounded-lg hover:bg-blue-700 transition-colors flex items-center justify-center">
75
+ <i class="ti ti-layout-dashboard mr-2"></i>
76
+ Return to Dashboard
77
+ </a>
78
+
79
+ <button onclick="history.back()" class="w-full bg-gray-200 text-gray-700 py-2 px-4 rounded-lg hover:bg-gray-300 transition-colors flex items-center justify-center">
80
+ <i class="ti ti-arrow-left mr-2"></i>
81
+ Go Back
82
+ </button>
83
+ </div>
84
+
85
+ <!-- Help Text -->
86
+ <p class="text-xs text-gray-500 mt-6">
87
+ If you believe this is an error, please contact your system administrator.
88
+ </p>
89
+ </div>
90
+ </div>
91
+ </body>
92
+ </html>
@@ -0,0 +1,273 @@
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>AI Agents - SuperBackend</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-50 min-h-screen">
12
+ <div id="app" class="p-6 max-w-6xl mx-auto" v-cloak>
13
+ <div class="flex justify-between items-center mb-8">
14
+ <div>
15
+ <h1 class="text-2xl font-bold text-gray-800">AI Agents</h1>
16
+ <p class="text-gray-500">Configure your intelligent agent gateway</p>
17
+ </div>
18
+ <button @click="openCreateModal" class="bg-indigo-600 text-white px-4 py-2 rounded-lg flex items-center gap-2 hover:bg-indigo-700 transition">
19
+ <i class="ti ti-plus"></i>
20
+ Create Agent
21
+ </button>
22
+ </div>
23
+
24
+ <!-- Agent List -->
25
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
26
+ <div v-for="agent in agents" :key="agent._id" class="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden hover:shadow-md transition">
27
+ <div class="p-6">
28
+ <div class="flex justify-between items-start mb-4">
29
+ <div class="flex items-center gap-3">
30
+ <div class="w-10 h-10 rounded-lg bg-indigo-100 text-indigo-600 flex items-center justify-center text-xl">
31
+ <i class="ti ti-robot"></i>
32
+ </div>
33
+ <div>
34
+ <h3 class="font-bold text-gray-800">{{ agent.name }}</h3>
35
+ <p class="text-xs text-gray-400">{{ agent.model }}</p>
36
+ </div>
37
+ </div>
38
+ <div class="flex gap-1">
39
+ <button @click="editAgent(agent)" class="p-1.5 text-gray-400 hover:text-indigo-600 hover:bg-indigo-50 rounded-md transition">
40
+ <i class="ti ti-edit"></i>
41
+ </button>
42
+ <button @click="confirmDelete(agent)" class="p-1.5 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-md transition">
43
+ <i class="ti ti-trash"></i>
44
+ </button>
45
+ </div>
46
+ </div>
47
+
48
+ <div class="space-y-3 text-sm">
49
+ <p class="text-gray-600 line-clamp-2 italic">"{{ agent.systemPrompt }}"</p>
50
+
51
+ <div class="flex flex-wrap gap-1 mt-4">
52
+ <span v-for="tool in agent.tools" :key="tool" class="px-2 py-0.5 bg-gray-100 text-gray-600 rounded text-xs border border-gray-200">
53
+ {{ tool }}
54
+ </span>
55
+ <span v-if="!agent.tools?.length" class="text-xs text-gray-400">No tools enabled</span>
56
+ </div>
57
+ </div>
58
+ </div>
59
+ </div>
60
+ </div>
61
+
62
+ <!-- Empty State -->
63
+ <div v-if="agents.length === 0" class="bg-white rounded-xl border-2 border-dashed border-gray-200 p-12 text-center">
64
+ <i class="ti ti-robot text-6xl text-gray-200 mb-4 inline-block"></i>
65
+ <h3 class="text-lg font-medium text-gray-800">No agents found</h3>
66
+ <p class="text-gray-500 mb-6">Create your first AI agent to start automating</p>
67
+ <button @click="openCreateModal" class="bg-indigo-600 text-white px-6 py-2 rounded-lg hover:bg-indigo-700 transition">
68
+ Create Agent
69
+ </button>
70
+ </div>
71
+
72
+ <!-- Modal -->
73
+ <div v-if="showModal" class="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
74
+ <div class="bg-white rounded-xl shadow-xl w-full max-w-2xl overflow-hidden max-h-[90vh] flex flex-col">
75
+ <div class="px-6 py-4 border-b border-gray-100 flex justify-between items-center shrink-0">
76
+ <h3 class="font-bold text-gray-800">{{ editingAgent ? 'Edit Agent' : 'Create Agent' }}</h3>
77
+ <button @click="showModal = false" class="text-gray-400 hover:text-gray-600">
78
+ <i class="ti ti-x"></i>
79
+ </button>
80
+ </div>
81
+ <form @submit.prevent="saveAgent" class="p-6 space-y-4 overflow-y-auto">
82
+ <div class="grid grid-cols-2 gap-4">
83
+ <div class="col-span-2">
84
+ <label class="block text-sm font-medium text-gray-700 mb-1">Agent Name</label>
85
+ <input v-model="formData.name" type="text" required class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none" placeholder="Customer Support AI">
86
+ </div>
87
+
88
+ <div>
89
+ <label class="block text-sm font-medium text-gray-700 mb-1">LLM Provider</label>
90
+ <select v-model="formData.providerKey" required class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none">
91
+ <option value="">Select Provider</option>
92
+ <option v-for="(p, key) in providers" :key="key" :value="key">{{ p.label || key }}</option>
93
+ </select>
94
+ </div>
95
+
96
+ <div>
97
+ <label class="block text-sm font-medium text-gray-700 mb-1">Model</label>
98
+ <input v-model="formData.model" type="text" required class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none" placeholder="gpt-4o, claude-3-5-sonnet...">
99
+ </div>
100
+ </div>
101
+
102
+ <div>
103
+ <label class="block text-sm font-medium text-gray-700 mb-1">System Prompt / Instructions</label>
104
+ <textarea v-model="formData.systemPrompt" rows="6" required class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none font-mono text-sm" placeholder="You are a helpful assistant that..."></textarea>
105
+ </div>
106
+
107
+ <div>
108
+ <label class="block text-sm font-medium text-gray-700 mb-2">Enabled Tools</label>
109
+ <div class="grid grid-cols-2 gap-2">
110
+ <label v-for="tool in availableTools" :key="tool" class="flex items-center gap-2 p-2 border border-gray-200 rounded-lg cursor-pointer hover:bg-gray-50">
111
+ <input type="checkbox" :value="tool" v-model="formData.tools" class="w-4 h-4 text-indigo-600 rounded">
112
+ <span class="text-sm font-medium text-gray-700">{{ tool }}</span>
113
+ </label>
114
+ </div>
115
+ </div>
116
+
117
+ <div class="grid grid-cols-2 gap-4">
118
+ <div>
119
+ <label class="block text-sm font-medium text-gray-700 mb-1">Temperature ({{ formData.temperature }})</label>
120
+ <input v-model.number="formData.temperature" type="range" min="0" max="2" step="0.1" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer accent-indigo-600">
121
+ </div>
122
+ <div>
123
+ <label class="block text-sm font-medium text-gray-700 mb-1">Max Tool Iterations</label>
124
+ <input v-model.number="formData.maxIterations" type="number" min="1" max="50" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none" placeholder="10">
125
+ <p class="text-[10px] text-gray-400 mt-1">Maximum number of tool call loops allowed per message. Default is 10.</p>
126
+ </div>
127
+ </div>
128
+
129
+ <div class="flex gap-3 pt-4 shrink-0">
130
+ <button type="button" @click="showModal = false" class="flex-1 px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition">
131
+ Cancel
132
+ </button>
133
+ <button type="submit" :disabled="saving" class="flex-1 px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition disabled:opacity-50">
134
+ {{ saving ? 'Saving...' : (editingAgent ? 'Update' : 'Create') }}
135
+ </button>
136
+ </div>
137
+ </form>
138
+ </div>
139
+ </div>
140
+ </div>
141
+
142
+ <script>
143
+ const { createApp, ref, onMounted } = Vue;
144
+
145
+ createApp({
146
+ setup() {
147
+ const agents = ref([]);
148
+ const providers = ref({});
149
+ const showModal = ref(false);
150
+ const editingAgent = ref(null);
151
+ const saving = ref(false);
152
+
153
+ const availableTools = ['query_database', 'get_system_stats', 'raw_db_query', 'exec'];
154
+
155
+ const formData = ref({
156
+ name: '',
157
+ systemPrompt: 'You are a helpful assistant.',
158
+ providerKey: '',
159
+ model: '',
160
+ tools: [],
161
+ temperature: 0.7,
162
+ maxIterations: 10
163
+ });
164
+
165
+ const baseUrl = '<%= baseUrl %>';
166
+
167
+ const fetchAgents = async () => {
168
+ const res = await fetch(`${baseUrl}/api/admin/agents`);
169
+ const data = await res.json();
170
+ agents.value = data.items;
171
+ };
172
+
173
+ const fetchLLMConfig = async () => {
174
+ // We need an endpoint to get LLM providers.
175
+ // For now, let's try to get it from the LLM admin API if it exists
176
+ try {
177
+ const res = await fetch(`${baseUrl}/api/admin/llm/providers`);
178
+ if (res.ok) {
179
+ const data = await res.json();
180
+ providers.value = data.providers || {};
181
+ }
182
+ } catch (e) {
183
+ console.warn('Failed to fetch LLM providers');
184
+ }
185
+ };
186
+
187
+ const openCreateModal = () => {
188
+ editingAgent.value = null;
189
+ formData.value = {
190
+ name: '',
191
+ systemPrompt: 'You are a helpful assistant.',
192
+ providerKey: Object.keys(providers.value)[0] || '',
193
+ model: '',
194
+ tools: [],
195
+ temperature: 0.7,
196
+ maxIterations: 10
197
+ };
198
+ showModal.value = true;
199
+ };
200
+
201
+ const editAgent = (agent) => {
202
+ editingAgent.value = agent;
203
+ formData.value = {
204
+ name: agent.name,
205
+ systemPrompt: agent.systemPrompt,
206
+ providerKey: agent.providerKey,
207
+ model: agent.model,
208
+ tools: agent.tools || [],
209
+ temperature: agent.temperature || 0.7,
210
+ maxIterations: agent.maxIterations || 10
211
+ };
212
+ showModal.value = true;
213
+ };
214
+
215
+ const confirmDelete = async (agent) => {
216
+ if (confirm(`Are you sure you want to delete agent ${agent.name}?`)) {
217
+ await fetch(`${baseUrl}/api/admin/agents/${agent._id}`, { method: 'DELETE' });
218
+ fetchAgents();
219
+ }
220
+ };
221
+
222
+ const saveAgent = async () => {
223
+ saving.value = true;
224
+ try {
225
+ const url = editingAgent.value
226
+ ? `${baseUrl}/api/admin/agents/${editingAgent.value._id}`
227
+ : `${baseUrl}/api/admin/agents`;
228
+
229
+ const method = editingAgent.value ? 'PUT' : 'POST';
230
+
231
+ const res = await fetch(url, {
232
+ method,
233
+ headers: { 'Content-Type': 'application/json' },
234
+ body: JSON.stringify(formData.value)
235
+ });
236
+
237
+ if (res.ok) {
238
+ showModal.value = false;
239
+ fetchAgents();
240
+ } else {
241
+ const data = await res.json();
242
+ alert(data.error || 'Failed to save agent');
243
+ }
244
+ } catch (err) {
245
+ alert(err.message);
246
+ } finally {
247
+ saving.value = false;
248
+ }
249
+ };
250
+
251
+ onMounted(() => {
252
+ fetchAgents();
253
+ fetchLLMConfig();
254
+ });
255
+
256
+ return {
257
+ agents,
258
+ providers,
259
+ showModal,
260
+ editingAgent,
261
+ formData,
262
+ saving,
263
+ availableTools,
264
+ openCreateModal,
265
+ editAgent,
266
+ confirmDelete,
267
+ saveAgent
268
+ };
269
+ }
270
+ }).mount('#app');
271
+ </script>
272
+ </body>
273
+ </html>
@@ -30,7 +30,7 @@
30
30
  >
31
31
  <i v-if="loading.provisioning" class="ti ti-loader-2 animate-spin"></i>
32
32
  <i v-else class="ti ti-file-code"></i>
33
- Provision manage.sh
33
+ Provision manage.js
34
34
  </button>
35
35
  </div>
36
36
 
@@ -81,24 +81,24 @@ REMOTE_DOMAIN_CONFIG_FILENAME=superlandings.yml</pre>
81
81
  <div>
82
82
  <p class="text-sm font-semibold text-gray-700 mb-2">1. Deploy Application</p>
83
83
  <div class="bg-gray-100 p-2 rounded font-mono text-xs flex justify-between items-center">
84
- <code>./manage.sh deploy</code>
85
- <button @click="copyToClipboard('./manage.sh deploy')" class="p-1 hover:text-blue-600"><i class="ti ti-copy"></i></button>
84
+ <code>node manage.js deploy</code>
85
+ <button @click="copyToClipboard('node manage.js deploy')" class="p-1 hover:text-blue-600"><i class="ti ti-copy"></i></button>
86
86
  </div>
87
87
  <p class="text-xs text-gray-500 mt-1">Syncs files via rsync and runs docker compose remotely.</p>
88
88
  </div>
89
89
  <div>
90
90
  <p class="text-sm font-semibold text-gray-700 mb-2">2. Setup Traefik Proxy</p>
91
91
  <div class="bg-gray-100 p-2 rounded font-mono text-xs flex justify-between items-center">
92
- <code>./manage.sh proxy</code>
93
- <button @click="copyToClipboard('./manage.sh proxy')" class="p-1 hover:text-blue-600"><i class="ti ti-copy"></i></button>
92
+ <code>node manage.js proxy</code>
93
+ <button @click="copyToClipboard('node manage.js proxy')" class="p-1 hover:text-blue-600"><i class="ti ti-copy"></i></button>
94
94
  </div>
95
95
  <p class="text-xs text-gray-500 mt-1">Generates the Traefik YAML configuration file locally.</p>
96
96
  </div>
97
97
  <div>
98
98
  <p class="text-sm font-semibold text-gray-700 mb-2">3. Deploy Domain</p>
99
99
  <div class="bg-gray-100 p-2 rounded font-mono text-xs flex justify-between items-center">
100
- <code>./manage.sh domain</code>
101
- <button @click="copyToClipboard('./manage.sh domain')" class="p-1 hover:text-blue-600"><i class="ti ti-copy"></i></button>
100
+ <code>node manage.js domain</code>
101
+ <button @click="copyToClipboard('node manage.js domain')" class="p-1 hover:text-blue-600"><i class="ti ti-copy"></i></button>
102
102
  </div>
103
103
  <p class="text-xs text-gray-500 mt-1">Uploads the Traefik YAML to the gateway server.</p>
104
104
  </div>
@@ -145,7 +145,7 @@ REMOTE_DOMAIN_CONFIG_FILENAME=superlandings.yml`;
145
145
 
146
146
  const provisionScript = async (overwrite = false) => {
147
147
  if (overwrite) {
148
- if (!confirm('Are you sure you want to overwrite the existing manage.sh?')) {
148
+ if (!confirm('Are you sure you want to overwrite the existing manage.js?')) {
149
149
  return;
150
150
  }
151
151
  }
@@ -149,14 +149,64 @@
149
149
 
150
150
  <script>
151
151
  let growthChart, emailChart;
152
+ const isIframe = <%= typeof isIframe !== 'undefined' && isIframe %>;
152
153
 
153
154
  async function fetchStats() {
154
155
  try {
155
- const res = await fetch('<%= baseUrl %>/api/admin/stats/overview');
156
- const data = await res.json();
156
+ let data;
157
+
158
+ if (isIframe) {
159
+ // In iframe mode, request data from parent window
160
+ data = await new Promise((resolve, reject) => {
161
+ const messageId = 'stats-request-' + Date.now();
162
+
163
+ const handleMessage = (event) => {
164
+ if (event.data.type === 'stats-response' && event.data.messageId === messageId) {
165
+ window.removeEventListener('message', handleMessage);
166
+ if (event.data.error) {
167
+ reject(new Error(event.data.error));
168
+ } else {
169
+ resolve(event.data.data);
170
+ }
171
+ }
172
+ };
173
+
174
+ window.addEventListener('message', handleMessage);
175
+
176
+ // Request data from parent
177
+ window.parent.postMessage({
178
+ type: 'stats-request',
179
+ messageId: messageId,
180
+ endpoint: '<%= baseUrl %>/api/admin/stats/overview'
181
+ }, '*');
182
+
183
+ // Timeout after 5 seconds
184
+ setTimeout(() => {
185
+ window.removeEventListener('message', handleMessage);
186
+ reject(new Error('Timeout waiting for parent response'));
187
+ }, 5000);
188
+ });
189
+ } else {
190
+ // Normal mode - direct fetch
191
+ const res = await fetch('<%= baseUrl %>/api/admin/stats/overview');
192
+ data = await res.json();
193
+ }
194
+
157
195
  updateUI(data);
158
196
  } catch (err) {
159
197
  console.error('Failed to load stats:', err);
198
+ // Show error state
199
+ const app = document.getElementById('app');
200
+ if (app) {
201
+ app.innerHTML = `
202
+ <div class="text-center py-12">
203
+ <i class="ti ti-alert-triangle text-4xl text-yellow-500 mb-4"></i>
204
+ <h3 class="text-lg font-semibold text-gray-900 mb-2">Unable to load stats</h3>
205
+ <p class="text-gray-500">${err.message}</p>
206
+ ${isIframe ? '<p class="text-sm text-gray-400 mt-2">Iframe mode: Parent window communication failed</p>' : ''}
207
+ </div>
208
+ `;
209
+ }
160
210
  }
161
211
  }
162
212