blackcoffee2 2.1.0

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 (170) hide show
  1. package/CHANGELOG.md +664 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +25 -0
  4. package/README.md +246 -0
  5. package/apps.zip +0 -0
  6. package/bin/adminclient +105 -0
  7. package/bin/blackcoffee +133 -0
  8. package/cli/admin-users.js +282 -0
  9. package/cli/commands/app.js +561 -0
  10. package/cli/commands/config.js +182 -0
  11. package/cli/commands/db.js +257 -0
  12. package/cli/commands/server.js +200 -0
  13. package/config/applications.json +5 -0
  14. package/config/database.json +28 -0
  15. package/config/database.json.example +23 -0
  16. package/config/server.json +32 -0
  17. package/controllers/admin/AdminController.js +529 -0
  18. package/controllers/admin/AdminViewController.js +90 -0
  19. package/controllers/admin/AuthController.js +293 -0
  20. package/controllers/admin/DatabaseAdminController.js +218 -0
  21. package/core/SQLiteAdapter.js +333 -0
  22. package/core/appLoader.js +385 -0
  23. package/core/databasePoolManager.js +431 -0
  24. package/core/hotReload.js +363 -0
  25. package/data/ADMIN-README.md +145 -0
  26. package/data/CHANGELOG.md +48 -0
  27. package/data/GTK3-NODE-PROPOSALS.md +410 -0
  28. package/data/admin-db.js +150 -0
  29. package/data/admin-gui.js +452 -0
  30. package/data/blackcoffee_admin.db-shm +0 -0
  31. package/data/blackcoffee_admin.db-wal +0 -0
  32. package/data/migrations/001_create_admin_users.sql +33 -0
  33. package/docs/APP_HOOKS_HANDLER.md +432 -0
  34. package/docs/APP_HOOKS_REQUIREMENTS.md +588 -0
  35. package/docs/ARCHITECTURE.md +435 -0
  36. package/docs/CREAR_APP_Y_USAR_POOLS.md +1595 -0
  37. package/docs/EVENTS_APP_MANUAL.md +289 -0
  38. package/docs/INSITU_BINARY_UPLOAD_PROPOSAL.md +186 -0
  39. package/docs/INSITU_FIREWALL_EXCEPTION.md +187 -0
  40. package/docs/ROADMAP.md +242 -0
  41. package/docs/ROADMAP.md.backup +243 -0
  42. package/includes/404-hooks.js +423 -0
  43. package/includes/adminAuth.js +214 -0
  44. package/includes/adminExtension.js +53 -0
  45. package/includes/appHooks.js +302 -0
  46. package/includes/initAdminDb.js +115 -0
  47. package/includes/routeLoader.js +67 -0
  48. package/includes/sessions.js +223 -0
  49. package/issues/001-duplicate-module-loading.md +92 -0
  50. package/manuales/ADMIN_EXTENSION_COMMANDS_MANUAL.md +261 -0
  51. package/manuales/ADMIN_EXTENSION_HOOK_EXAMPLE.md +28 -0
  52. package/manuales/ADMIN_EXTENSION_INTEGRATION_MANUAL.md +232 -0
  53. package/manuales/CACHE_REGEX_COMMANDS.md +136 -0
  54. package/manuales/CACHE_SYSTEM_MAP.md +206 -0
  55. package/manuales/CREACION_DE_CONTROLADORES_INSITU.md +383 -0
  56. package/manuales/QUEUE_CLI_MODULE_MANUAL.md +289 -0
  57. package/manuales/QUEUE_SYSTEM_MANUAL.md +320 -0
  58. package/manuales/ROUTE_CACHE_MODULE_MANUAL.md +205 -0
  59. package/manuales/SESSION_MANAGER_GUIDE.md +529 -0
  60. package/manuales/SESSION_SECURITY_FLAGS.md +174 -0
  61. package/manuales/WAF_MODULE_MANUAL.md +229 -0
  62. package/manuales/after_route_handler_filter_example.md +116 -0
  63. package/manuales/after_route_handler_usage.md +130 -0
  64. package/manuales/an/303/241lisis-completo-insitu-framework.md +213 -0
  65. package/manuales/async_hooks_promises_guide.md +325 -0
  66. package/manuales/before_route_handler_filter_example.md +97 -0
  67. package/manuales/before_route_handler_usage.md +122 -0
  68. package/manuales/hooks_chaining_conditions_guide.md +261 -0
  69. package/manuales/hooks_filters_documentation.md +493 -0
  70. package/manuales/hooks_filters_documentation_en.md +493 -0
  71. package/manuales/hooks_vs_middlewares_comparison.md +87 -0
  72. package/manuales/manual-mvc-completo.md +934 -0
  73. package/manuales/modulos_administracion.md +89 -0
  74. package/manuales/router_execution_points.md +74 -0
  75. package/manuales/static_file_hooks_usage.md +222 -0
  76. package/models/AdminUserModel.js +132 -0
  77. package/package.json +45 -0
  78. package/programatically/PRoutes.js +89 -0
  79. package/programatically/initFlow.js +211 -0
  80. package/public/admin/css/db-pools.css +336 -0
  81. package/public/admin/css/styles.css +310 -0
  82. package/public/admin/database.html +312 -0
  83. package/public/admin/index.html +116 -0
  84. package/public/admin/js/app.js +470 -0
  85. package/public/admin/js/db-pools.js +253 -0
  86. package/public/admin/login.html +278 -0
  87. package/public/assets/css/styles.css +477 -0
  88. package/public/assets/js/main.js +89 -0
  89. package/public/index.html +136 -0
  90. package/public/templates/404.html +158 -0
  91. package/routes/admin-views.json +20 -0
  92. package/routes/admin.json +38 -0
  93. package/routes/auth.json +32 -0
  94. package/routes/static.json +18 -0
  95. package/server.js +299 -0
  96. package/test-aplicacion.con-logisession/BlackCoffee.js +226 -0
  97. package/test-aplicacion.con-logisession/SSL_SETUP.md +53 -0
  98. package/test-aplicacion.con-logisession/certs/ca-certificate.pem +32 -0
  99. package/test-aplicacion.con-logisession/certs/ca-private-key.pem +52 -0
  100. package/test-aplicacion.con-logisession/certs/certificate-2048.pem +22 -0
  101. package/test-aplicacion.con-logisession/certs/certificate.pem +32 -0
  102. package/test-aplicacion.con-logisession/certs/private-key-2048.pem +28 -0
  103. package/test-aplicacion.con-logisession/certs/private-key.pem +52 -0
  104. package/test-aplicacion.con-logisession/config/iaQueueSetup.js +84 -0
  105. package/test-aplicacion.con-logisession/config/qwen-rules.json +39 -0
  106. package/test-aplicacion.con-logisession/controllers/analyticsController.js +117 -0
  107. package/test-aplicacion.con-logisession/controllers/auth/AdminAuthController.js +142 -0
  108. package/test-aplicacion.con-logisession/controllers/auth/AuthController.js +439 -0
  109. package/test-aplicacion.con-logisession/controllers/auth/AuthViewController.js +223 -0
  110. package/test-aplicacion.con-logisession/controllers/endpointController.js +66 -0
  111. package/test-aplicacion.con-logisession/controllers/example.js +183 -0
  112. package/test-aplicacion.con-logisession/controllers/iaQueueController.js +367 -0
  113. package/test-aplicacion.con-logisession/controllers/queueController.js +206 -0
  114. package/test-aplicacion.con-logisession/controllers/qwenQueueController.js +197 -0
  115. package/test-aplicacion.con-logisession/controllers/test.js +0 -0
  116. package/test-aplicacion.con-logisession/controllers/tracking/EventsNoFinishController.js +78 -0
  117. package/test-aplicacion.con-logisession/controllers/tracking/TrackingController.js +412 -0
  118. package/test-aplicacion.con-logisession/controllers/tracking/TrackingControllerWithLoadModel.js +437 -0
  119. package/test-aplicacion.con-logisession/hooks/admin-hooks.js +20 -0
  120. package/test-aplicacion.con-logisession/hooks/general-hooks.js +97 -0
  121. package/test-aplicacion.con-logisession/hooks/queue-hooks.js +64 -0
  122. package/test-aplicacion.con-logisession/hooks/route-directory-hooks.js +38 -0
  123. package/test-aplicacion.con-logisession/hooks/security-hooks.js +24 -0
  124. package/test-aplicacion.con-logisession/insitu-admin-client/README.md +69 -0
  125. package/test-aplicacion.con-logisession/insitu-admin-client/package.json +23 -0
  126. package/test-aplicacion.con-logisession/insitu-admin-client.js +257 -0
  127. package/test-aplicacion.con-logisession/models/ExampleModel.js +88 -0
  128. package/test-aplicacion.con-logisession/models/QueueJobModel.js +263 -0
  129. package/test-aplicacion.con-logisession/models/TokenModel.js +207 -0
  130. package/test-aplicacion.con-logisession/models/auth/AuthModel.js +66 -0
  131. package/test-aplicacion.con-logisession/models/auth/UserModel.js +189 -0
  132. package/test-aplicacion.con-logisession/models/tracking/CompletedCartModel.js +213 -0
  133. package/test-aplicacion.con-logisession/models/tracking/EventModel.js +366 -0
  134. package/test-aplicacion.con-logisession/models/tracking/EventsNoFinishModel.js +131 -0
  135. package/test-aplicacion.con-logisession/models/tracking/SessionModel.js +360 -0
  136. package/test-aplicacion.con-logisession/models/tracking/SiteFlowModel.js +286 -0
  137. package/test-aplicacion.con-logisession/models/tracking/TokenModel.js +207 -0
  138. package/test-aplicacion.con-logisession/package-lock.json +3313 -0
  139. package/test-aplicacion.con-logisession/package.json +32 -0
  140. package/test-aplicacion.con-logisession/public/blackcoffee-welcome/index.html +1339 -0
  141. package/test-aplicacion.con-logisession/public/css/style.css +64 -0
  142. package/test-aplicacion.con-logisession/public/ejemplo-estatica/index.html +18 -0
  143. package/test-aplicacion.con-logisession/public/ejemplo-estatica/script.js +16 -0
  144. package/test-aplicacion.con-logisession/public/ejemplo-estatica/styles.css +43 -0
  145. package/test-aplicacion.con-logisession/public/images/logo.svg +7 -0
  146. package/test-aplicacion.con-logisession/public/js/main.js +67 -0
  147. package/test-aplicacion.con-logisession/routes/analytics-routes.json +8 -0
  148. package/test-aplicacion.con-logisession/routes/auth-routes.json +98 -0
  149. package/test-aplicacion.con-logisession/routes/blackcoffee-welcome-routes.json +20 -0
  150. package/test-aplicacion.con-logisession/routes/duplicate-test-routes.json.disabled +16 -0
  151. package/test-aplicacion.con-logisession/routes/ejemplo-estatica-routes.json +11 -0
  152. package/test-aplicacion.con-logisession/routes/endpoints-routes.json +8 -0
  153. package/test-aplicacion.con-logisession/routes/ia-queue-routes.json +26 -0
  154. package/test-aplicacion.con-logisession/routes/product-routes.json.disabled +20 -0
  155. package/test-aplicacion.con-logisession/routes/queue-routes.json +32 -0
  156. package/test-aplicacion.con-logisession/routes/qwen-routes.json +14 -0
  157. package/test-aplicacion.con-logisession/routes/static-routes.json +29 -0
  158. package/test-aplicacion.con-logisession/routes/tracking-routes.json +58 -0
  159. package/test-aplicacion.con-logisession/routes/tracking-with-loadmodel-routes.json +51 -0
  160. package/test-aplicacion.con-logisession/utils/dbAdapter.js +88 -0
  161. package/test-aplicacion.con-logisession/utils/qbWrapper.js +4 -0
  162. package/test-aplicacion.con-logisession/utils/queueProcessor.js +305 -0
  163. package/test-aplicacion.con-logisession/utils/qwenRulesService.js +131 -0
  164. package/test-aplicacion.con-logisession/utils/tokenHelper.js +22 -0
  165. package/test-aplicacion.con-logisession/views/auth/dashboard.html +443 -0
  166. package/test-aplicacion.con-logisession/views/auth/forgot-password.html +200 -0
  167. package/test-aplicacion.con-logisession/views/auth/login.html +213 -0
  168. package/test-aplicacion.con-logisession/views/auth/register.html +294 -0
  169. package/test-aplicacion.con-logisession/views/contact/form.html +47 -0
  170. package/test-aplicacion.con-logisession/views/products/index.html +39 -0
@@ -0,0 +1,310 @@
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ body {
8
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
9
+ background: #faf6f2;
10
+ color: #2c1810;
11
+ }
12
+
13
+ .dashboard {
14
+ display: flex;
15
+ min-height: 100vh;
16
+ }
17
+
18
+ /* Sidebar */
19
+ .sidebar {
20
+ width: 260px;
21
+ background: linear-gradient(135deg, #310000 0%, #000 100%);
22
+ color: #b28868;
23
+ padding: 2rem;
24
+ position: fixed;
25
+ height: 100vh;
26
+ overflow-y: auto;
27
+ }
28
+
29
+ .logo h1 {
30
+ font-size: 1.8rem;
31
+ margin-bottom: 0.5rem;
32
+ }
33
+
34
+ .logo p {
35
+ opacity: 0.8;
36
+ font-size: 0.9rem;
37
+ }
38
+
39
+ .nav {
40
+ margin-top: 2rem;
41
+ }
42
+
43
+ .nav-item {
44
+ display: block;
45
+ padding: 0.8rem 1rem;
46
+ color: rgba(255, 255, 255, 0.8);
47
+ text-decoration: none;
48
+ border-radius: 8px;
49
+ margin-bottom: 0.5rem;
50
+ transition: all 0.3s;
51
+ }
52
+
53
+ .nav-item:hover,
54
+ .nav-item.active {
55
+ background: rgba(255, 255, 255, 0.2);
56
+ color: white;
57
+ }
58
+
59
+ .server-status {
60
+ position: absolute;
61
+ bottom: 2rem;
62
+ display: flex;
63
+ align-items: center;
64
+ gap: 0.5rem;
65
+ font-size: 0.9rem;
66
+ }
67
+
68
+ .status-indicator {
69
+ width: 10px;
70
+ height: 10px;
71
+ border-radius: 50%;
72
+ background: #4caf50;
73
+ animation: pulse 2s infinite;
74
+ }
75
+
76
+ @keyframes pulse {
77
+ 0%, 100% { opacity: 1; }
78
+ 50% { opacity: 0.5; }
79
+ }
80
+
81
+ /* Main Content */
82
+ .main-content {
83
+ margin-left: 260px;
84
+ padding: 2rem;
85
+ flex: 1;
86
+ }
87
+
88
+ .section {
89
+ display: none;
90
+ }
91
+
92
+ .section.active {
93
+ display: block;
94
+ }
95
+
96
+ h2 {
97
+ font-size: 2rem;
98
+ margin-bottom: 1.5rem;
99
+ color: #310000;
100
+ }
101
+
102
+ /* Stats Grid */
103
+ .stats-grid {
104
+ display: grid;
105
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
106
+ gap: 1.5rem;
107
+ margin-bottom: 2rem;
108
+ }
109
+
110
+ .stat-card {
111
+ background: white;
112
+ padding: 1.5rem;
113
+ border-radius: 12px;
114
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
115
+ display: flex;
116
+ align-items: center;
117
+ gap: 1rem;
118
+ }
119
+
120
+ .stat-icon {
121
+ font-size: 2.5rem;
122
+ }
123
+
124
+ .stat-info h3 {
125
+ font-size: 2rem;
126
+ color: #b28868;
127
+ }
128
+
129
+ .stat-info p {
130
+ color: #666;
131
+ font-size: 0.9rem;
132
+ }
133
+
134
+ /* Apps List */
135
+ .apps-list,
136
+ .apps-grid {
137
+ display: flex;
138
+ flex-direction: column;
139
+ gap: 1rem;
140
+ }
141
+
142
+ .app-card {
143
+ background: white;
144
+ padding: 1.5rem;
145
+ border-radius: 12px;
146
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
147
+ display: flex;
148
+ justify-content: space-between;
149
+ align-items: center;
150
+ }
151
+
152
+ .app-info h4 {
153
+ color: #310000;
154
+ margin-bottom: 0.5rem;
155
+ }
156
+
157
+ .app-info p {
158
+ color: #666;
159
+ font-size: 0.9rem;
160
+ }
161
+
162
+ .app-actions {
163
+ display: flex;
164
+ gap: 0.5rem;
165
+ }
166
+
167
+ .btn {
168
+ padding: 0.5rem 1rem;
169
+ border: none;
170
+ border-radius: 6px;
171
+ cursor: pointer;
172
+ font-weight: 600;
173
+ transition: all 0.3s;
174
+ }
175
+
176
+ .btn-primary {
177
+ background: linear-gradient(135deg, #310000 0%, #000 100%);
178
+ color: #b28868;
179
+ }
180
+
181
+ .btn-secondary {
182
+ background: #f5f0eb;
183
+ color: #310000;
184
+ }
185
+
186
+ .btn-danger {
187
+ background: #f44336;
188
+ color: white;
189
+ }
190
+
191
+ .btn:hover {
192
+ transform: translateY(-2px);
193
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
194
+ }
195
+
196
+ /* Deploy Box */
197
+ .deploy-box {
198
+ background: white;
199
+ padding: 2rem;
200
+ border-radius: 12px;
201
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
202
+ }
203
+
204
+ .drop-zone {
205
+ border: 2px dashed #b28868;
206
+ border-radius: 12px;
207
+ padding: 3rem;
208
+ text-align: center;
209
+ cursor: pointer;
210
+ transition: all 0.3s;
211
+ }
212
+
213
+ .drop-zone:hover,
214
+ .drop-zone.dragover {
215
+ background: rgba(178, 136, 104, 0.1);
216
+ border-color: #310000;
217
+ }
218
+
219
+ .drop-icon {
220
+ font-size: 4rem;
221
+ margin-bottom: 1rem;
222
+ }
223
+
224
+ .deploy-status {
225
+ margin-top: 1rem;
226
+ padding: 1rem;
227
+ border-radius: 8px;
228
+ display: none;
229
+ }
230
+
231
+ .deploy-status.success {
232
+ background: #e8f5e9;
233
+ color: #2e7d32;
234
+ display: block;
235
+ }
236
+
237
+ .deploy-status.error {
238
+ background: #ffebee;
239
+ color: #c62828;
240
+ display: block;
241
+ }
242
+
243
+ /* Config Box */
244
+ .config-box {
245
+ background: white;
246
+ padding: 2rem;
247
+ border-radius: 12px;
248
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
249
+ }
250
+
251
+ .config-item {
252
+ margin-bottom: 1rem;
253
+ padding-bottom: 1rem;
254
+ border-bottom: 1px solid #eee;
255
+ }
256
+
257
+ .config-item:last-child {
258
+ border-bottom: none;
259
+ }
260
+
261
+ .config-item label {
262
+ display: block;
263
+ font-weight: 600;
264
+ margin-bottom: 0.5rem;
265
+ color: #310000;
266
+ }
267
+
268
+ .config-item input,
269
+ .config-item select {
270
+ width: 100%;
271
+ padding: 0.5rem;
272
+ border: 1px solid #ddd;
273
+ border-radius: 6px;
274
+ font-size: 1rem;
275
+ }
276
+
277
+ /* User Info */
278
+ .user-info {
279
+ margin-top: 2rem;
280
+ padding-top: 1rem;
281
+ border-top: 1px solid rgba(255, 255, 255, 0.2);
282
+ display: flex;
283
+ align-items: center;
284
+ gap: 12px;
285
+ }
286
+
287
+ .user-avatar {
288
+ font-size: 24px;
289
+ }
290
+
291
+ .user-name {
292
+ flex: 1;
293
+ font-size: 14px;
294
+ font-weight: 500;
295
+ }
296
+
297
+ .btn-logout {
298
+ background: rgba(255, 255, 255, 0.2);
299
+ border: none;
300
+ color: white;
301
+ font-size: 18px;
302
+ padding: 6px 10px;
303
+ border-radius: 6px;
304
+ cursor: pointer;
305
+ transition: background 0.3s;
306
+ }
307
+
308
+ .btn-logout:hover {
309
+ background: rgba(255, 255, 255, 0.3);
310
+ }
@@ -0,0 +1,312 @@
1
+ <!DOCTYPE html>
2
+ <html lang="es">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Database Pools - BlackCoffee Admin</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ body { font-family: 'Segoe UI', sans-serif; background: #f5f7fa; }
10
+ .dashboard { display: flex; min-height: 100vh; }
11
+ .sidebar { width: 260px; background: linear-gradient(135deg, #310000 0%, #000 100%); color: #b28868; padding: 2rem; position: fixed; height: 100vh; }
12
+ .sidebar h1 { font-size: 1.8rem; margin-bottom: 0.5rem; }
13
+ .sidebar p { opacity: 0.8; font-size: 0.9rem; }
14
+ .nav { margin-top: 2rem; }
15
+ .nav a { display: block; padding: 0.8rem 1rem; color: rgba(255,255,255,0.8); text-decoration: none; border-radius: 8px; margin-bottom: 0.5rem; }
16
+ .nav a:hover, .nav a.active { background: rgba(255,255,255,0.2); color: white; }
17
+ .main-content { margin-left: 260px; padding: 2rem; flex: 1; }
18
+ header { margin-bottom: 2rem; }
19
+ header h1 { font-size: 2.5rem; color: #667eea; margin-bottom: 0.5rem; }
20
+ header p { color: #666; font-size: 1.1rem; }
21
+ .actions { display: flex; gap: 1rem; margin-bottom: 2rem; }
22
+ .btn { padding: 0.8rem 2rem; border: none; border-radius: 8px; font-weight: 600; cursor: pointer; }
23
+ .btn-primary { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; }
24
+ .btn-success { background: #4caf50; color: white; }
25
+ .btn-danger { background: #f44336; color: white; }
26
+ .btn-secondary { background: #9e9e9e; color: white; }
27
+ .form-section { background: white; padding: 2rem; border-radius: 12px; margin-bottom: 2rem; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
28
+ .form-section h2 { color: #667eea; margin-bottom: 1.5rem; }
29
+ .form-group { margin-bottom: 1.5rem; }
30
+ .form-group label { display: block; margin-bottom: 0.5rem; font-weight: 600; }
31
+ .form-group input, .form-group select { width: 100%; padding: 0.8rem; border: 2px solid #ddd; border-radius: 8px; font-size: 1rem; }
32
+ .form-group input:focus, .form-group select:focus { outline: none; border-color: #667eea; }
33
+ .form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
34
+ .db-fields { background: #f9f9f9; padding: 1.5rem; border-radius: 8px; margin-top: 1rem; }
35
+ .form-actions { display: flex; gap: 1rem; justify-content: flex-end; margin-top: 2rem; }
36
+ .pools-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 1.5rem; }
37
+ .pool-card { background: white; padding: 1.5rem; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
38
+ .pool-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; padding-bottom: 1rem; border-bottom: 2px solid #f0f0f0; }
39
+ .pool-header h3 { color: #333; font-size: 1.3rem; }
40
+ .pool-type { display: inline-block; padding: 0.3rem 0.8rem; border-radius: 20px; font-size: 0.8rem; font-weight: 600; }
41
+ .pool-type-sqlite { background: #e3f2fd; color: #1976d2; }
42
+ .pool-type-mariadb, .pool-type-mysql { background: #fce4ec; color: #c2185b; }
43
+ .pool-type-memory { background: #fff3e0; color: #f57c00; }
44
+ .pool-status { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 1rem; }
45
+ .status-indicator { width: 10px; height: 10px; border-radius: 50%; }
46
+ .status-connected { background: #4caf50; }
47
+ .status-disconnected { background: #f44336; }
48
+ .pool-details { margin-bottom: 1rem; }
49
+ .pool-detail { display: flex; justify-content: space-between; padding: 0.5rem 0; border-bottom: 1px solid #f0f0f0; }
50
+ .pool-actions { display: flex; gap: 0.5rem; justify-content: flex-end; }
51
+ .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-top: 2rem; }
52
+ .stat-card { background: white; padding: 1.5rem; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); text-align: center; }
53
+ .stat-value { font-size: 2.5rem; font-weight: bold; color: #667eea; margin-bottom: 0.5rem; }
54
+ .stat-label { color: #666; font-size: 0.9rem; }
55
+ .no-pools { text-align: center; padding: 3rem; color: #999; font-size: 1.1rem; }
56
+ </style>
57
+ </head>
58
+ <body>
59
+ <div class="dashboard">
60
+ <aside class="sidebar">
61
+ <div class="logo">
62
+ <h1>☕ BlackCoffee</h1>
63
+ <p>Database Manager</p>
64
+ </div>
65
+ <nav class="nav">
66
+ <a href="/admin">📊 Dashboard</a>
67
+ <a href="/admin/database" class="active">🗄️ Database Pools</a>
68
+ <a href="/admin#apps">📦 Aplicaciones</a>
69
+ <a href="/admin#deploy">🚀 Deploy</a>
70
+ <a href="/admin#settings">⚙️ Configuración</a>
71
+ </nav>
72
+ </aside>
73
+
74
+ <main class="main-content">
75
+ <header>
76
+ <h1>🗄️ Database Pools</h1>
77
+ <p>Gestiona los pools de conexiones a bases de datos</p>
78
+ </header>
79
+
80
+ <section class="actions">
81
+ <button onclick="showCreatePoolForm()" class="btn btn-primary">➕ Crear Pool</button>
82
+ <button onclick="loadPools()" class="btn btn-secondary">🔄 Recargar</button>
83
+ </section>
84
+
85
+ <section id="createPoolForm" class="form-section" style="display: none;">
86
+ <h2>Crear Nuevo Pool</h2>
87
+ <form onsubmit="createPool(event)">
88
+ <div class="form-group">
89
+ <label for="poolName">Nombre del Pool:</label>
90
+ <input type="text" id="poolName" name="name" required placeholder="ej: production, analytics">
91
+ </div>
92
+ <div class="form-group">
93
+ <label for="poolType">Tipo de Base de Datos:</label>
94
+ <select id="poolType" name="type" required onchange="toggleDbTypeFields()">
95
+ <option value="">Seleccionar...</option>
96
+ <option value="sqlite">SQLite</option>
97
+ <option value="mariadb">MariaDB / MySQL</option>
98
+ <option value="memory">Memoria (RAM)</option>
99
+ </select>
100
+ </div>
101
+ <div id="sqliteFields" class="db-fields" style="display: none;">
102
+ <div class="form-group">
103
+ <label for="sqliteFilename">Archivo SQLite:</label>
104
+ <input type="text" id="sqliteFilename" name="filename" placeholder="data/mydb.sqlite">
105
+ </div>
106
+ </div>
107
+ <div id="mariadbFields" class="db-fields" style="display: none;">
108
+ <div class="form-row">
109
+ <div class="form-group">
110
+ <label for="dbHost">Host:</label>
111
+ <input type="text" id="dbHost" name="host" value="localhost">
112
+ </div>
113
+ <div class="form-group">
114
+ <label for="dbPort">Port:</label>
115
+ <input type="number" id="dbPort" name="port" value="3306">
116
+ </div>
117
+ </div>
118
+ <div class="form-row">
119
+ <div class="form-group">
120
+ <label for="dbUser">Usuario:</label>
121
+ <input type="text" id="dbUser" name="user" value="root">
122
+ </div>
123
+ <div class="form-group">
124
+ <label for="dbPassword">Contraseña:</label>
125
+ <input type="password" id="dbPassword" name="password" value="">
126
+ </div>
127
+ </div>
128
+ <div class="form-group">
129
+ <label for="dbName">Base de Datos:</label>
130
+ <input type="text" id="dbName" name="database" placeholder="nombre_de_la_base_de_datos">
131
+ </div>
132
+ <div class="form-group">
133
+ <label for="dbConnectionLimit">Límite de Conexiones:</label>
134
+ <input type="number" id="dbConnectionLimit" name="connectionLimit" value="10">
135
+ </div>
136
+ </div>
137
+ <div class="form-actions">
138
+ <button type="submit" class="btn btn-success">Crear Pool</button>
139
+ <button type="button" onclick="hideCreatePoolForm()" class="btn btn-secondary">Cancelar</button>
140
+ </div>
141
+ </form>
142
+ </section>
143
+
144
+ <section id="poolsList">
145
+ <h2>Pools Configurados</h2>
146
+ <div id="poolsGrid" class="pools-grid"></div>
147
+ </section>
148
+
149
+ <section id="statsSection">
150
+ <h2>Estadísticas</h2>
151
+ <div id="statsGrid" class="stats-grid"></div>
152
+ </section>
153
+ </main>
154
+ </div>
155
+
156
+ <script>
157
+ const API_BASE = '/api/admin/database';
158
+
159
+ function toggleDbTypeFields() {
160
+ const type = document.getElementById('poolType').value;
161
+ document.getElementById('sqliteFields').style.display = type === 'sqlite' ? 'block' : 'none';
162
+ document.getElementById('mariadbFields').style.display = ['mariadb', 'mysql'].includes(type) ? 'block' : 'none';
163
+ }
164
+
165
+ function showCreatePoolForm() {
166
+ document.getElementById('createPoolForm').style.display = 'block';
167
+ }
168
+
169
+ function hideCreatePoolForm() {
170
+ document.getElementById('createPoolForm').style.display = 'none';
171
+ }
172
+
173
+ async function loadPools() {
174
+ try {
175
+ const response = await fetch(API_BASE + '/pools');
176
+ const result = await response.json();
177
+ if (result.success) {
178
+ renderPools(result.data);
179
+ renderStats(result.stats);
180
+ } else {
181
+ alert('Error: ' + result.error);
182
+ }
183
+ } catch (error) {
184
+ alert('Error cargando pools: ' + error.message);
185
+ }
186
+ }
187
+
188
+ function renderPools(pools) {
189
+ const grid = document.getElementById('poolsGrid');
190
+ if (pools.length === 0) {
191
+ grid.innerHTML = '<p class="no-pools">No hay pools configurados. ¡Crea el primero!</p>';
192
+ return;
193
+ }
194
+ grid.innerHTML = pools.map(pool => `
195
+ <div class="pool-card">
196
+ <div class="pool-header">
197
+ <h3>${escapeHtml(pool.name)}</h3>
198
+ <span class="pool-type pool-type-${pool.type}">${pool.type.toUpperCase()}</span>
199
+ </div>
200
+ <div class="pool-status">
201
+ <span class="status-indicator ${pool.connected ? 'status-connected' : 'status-disconnected'}"></span>
202
+ <span>${pool.connected ? 'Conectado' : 'Desconectado'}</span>
203
+ </div>
204
+ <div class="pool-details">${renderPoolDetails(pool)}</div>
205
+ <div class="pool-actions">
206
+ ${pool.connected
207
+ ? `<button onclick="disconnectPool('${pool.name}')" class="btn btn-danger btn-sm">Desconectar</button>`
208
+ : `<button onclick="connectPool('${pool.name}')" class="btn btn-success btn-sm">Conectar</button>`
209
+ }
210
+ <button onclick="removePool('${pool.name}')" class="btn btn-danger btn-sm">🗑️ Eliminar</button>
211
+ </div>
212
+ </div>
213
+ `).join('');
214
+ }
215
+
216
+ function renderPoolDetails(pool) {
217
+ let details = '';
218
+ if (pool.type === 'sqlite') {
219
+ details = `<div class="pool-detail"><label>Archivo:</label><span>${escapeHtml(pool.config.filename || 'N/A')}</span></div>`;
220
+ } else if (['mariadb', 'mysql'].includes(pool.type)) {
221
+ details = `
222
+ <div class="pool-detail"><label>Host:</label><span>${escapeHtml(pool.config.host || 'N/A')}:${pool.config.port || 3306}</span></div>
223
+ <div class="pool-detail"><label>Database:</label><span>${escapeHtml(pool.config.database || 'N/A')}</span></div>
224
+ <div class="pool-detail"><label>Usuario:</label><span>${escapeHtml(pool.config.user || 'N/A')}</span></div>
225
+ <div class="pool-detail"><label>Conexiones:</label><span>${pool.config.connectionLimit || 10}</span></div>
226
+ `;
227
+ } else if (pool.type === 'memory') {
228
+ details = `<div class="pool-detail"><label>Tipo:</label><span>Memoria RAM</span></div>`;
229
+ }
230
+ return details;
231
+ }
232
+
233
+ function renderStats(stats) {
234
+ const grid = document.getElementById('statsGrid');
235
+ if (!stats) { grid.innerHTML = ''; return; }
236
+ grid.innerHTML = `
237
+ <div class="stat-card"><div class="stat-value">${stats.totalPools || 0}</div><div class="stat-label">Total Pools</div></div>
238
+ <div class="stat-card"><div class="stat-value">${stats.connectedPools || 0}</div><div class="stat-label">Conectados</div></div>
239
+ <div class="stat-card"><div class="stat-value">${(stats.totalPools || 0) - (stats.connectedPools || 0)}</div><div class="stat-label">Desconectados</div></div>
240
+ `;
241
+ }
242
+
243
+ async function createPool(event) {
244
+ event.preventDefault();
245
+ const formData = new FormData(event.target);
246
+ const data = Object.fromEntries(formData.entries());
247
+ try {
248
+ const response = await fetch(API_BASE + '/pools', {
249
+ method: 'POST',
250
+ headers: { 'Content-Type': 'application/json' },
251
+ body: JSON.stringify(data)
252
+ });
253
+ const result = await response.json();
254
+ if (result.success) {
255
+ alert('Pool creado exitosamente. Reinicia el servidor para aplicar los cambios.');
256
+ hideCreatePoolForm();
257
+ loadPools();
258
+ event.target.reset();
259
+ } else {
260
+ alert('Error: ' + result.error);
261
+ }
262
+ } catch (error) {
263
+ alert('Error creando pool: ' + error.message);
264
+ }
265
+ }
266
+
267
+ async function connectPool(name) {
268
+ if (!confirm(`¿Conectar pool '${name}'?`)) return;
269
+ try {
270
+ const response = await fetch(API_BASE + '/pools/' + name + '/connect', { method: 'POST' });
271
+ const result = await response.json();
272
+ if (result.success) { alert(`Pool '${name}' conectado`); loadPools(); }
273
+ else { alert('Error: ' + result.error); }
274
+ } catch (error) {
275
+ alert('Error conectando pool: ' + error.message);
276
+ }
277
+ }
278
+
279
+ async function disconnectPool(name) {
280
+ if (!confirm(`¿Desconectar pool '${name}'?`)) return;
281
+ try {
282
+ const response = await fetch(API_BASE + '/pools/' + name + '/disconnect', { method: 'POST' });
283
+ const result = await response.json();
284
+ if (result.success) { alert(`Pool '${name}' desconectado`); loadPools(); }
285
+ else { alert('Error: ' + result.error); }
286
+ } catch (error) {
287
+ alert('Error desconectando pool: ' + error.message);
288
+ }
289
+ }
290
+
291
+ async function removePool(name) {
292
+ if (!confirm(`¿Estás seguro de eliminar el pool '${name}'?`)) return;
293
+ try {
294
+ const response = await fetch(API_BASE + '/pools/' + name, { method: 'DELETE' });
295
+ const result = await response.json();
296
+ if (result.success) { alert(`Pool '${name}' eliminado`); loadPools(); }
297
+ else { alert('Error: ' + result.error); }
298
+ } catch (error) {
299
+ alert('Error eliminando pool: ' + error.message);
300
+ }
301
+ }
302
+
303
+ function escapeHtml(text) {
304
+ const div = document.createElement('div');
305
+ div.textContent = text;
306
+ return div.innerHTML;
307
+ }
308
+
309
+ document.addEventListener('DOMContentLoaded', loadPools);
310
+ </script>
311
+ </body>
312
+ </html>