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.
- package/CHANGELOG.md +664 -0
- package/LICENSE +201 -0
- package/NOTICE +25 -0
- package/README.md +246 -0
- package/apps.zip +0 -0
- package/bin/adminclient +105 -0
- package/bin/blackcoffee +133 -0
- package/cli/admin-users.js +282 -0
- package/cli/commands/app.js +561 -0
- package/cli/commands/config.js +182 -0
- package/cli/commands/db.js +257 -0
- package/cli/commands/server.js +200 -0
- package/config/applications.json +5 -0
- package/config/database.json +28 -0
- package/config/database.json.example +23 -0
- package/config/server.json +32 -0
- package/controllers/admin/AdminController.js +529 -0
- package/controllers/admin/AdminViewController.js +90 -0
- package/controllers/admin/AuthController.js +293 -0
- package/controllers/admin/DatabaseAdminController.js +218 -0
- package/core/SQLiteAdapter.js +333 -0
- package/core/appLoader.js +385 -0
- package/core/databasePoolManager.js +431 -0
- package/core/hotReload.js +363 -0
- package/data/ADMIN-README.md +145 -0
- package/data/CHANGELOG.md +48 -0
- package/data/GTK3-NODE-PROPOSALS.md +410 -0
- package/data/admin-db.js +150 -0
- package/data/admin-gui.js +452 -0
- package/data/blackcoffee_admin.db-shm +0 -0
- package/data/blackcoffee_admin.db-wal +0 -0
- package/data/migrations/001_create_admin_users.sql +33 -0
- package/docs/APP_HOOKS_HANDLER.md +432 -0
- package/docs/APP_HOOKS_REQUIREMENTS.md +588 -0
- package/docs/ARCHITECTURE.md +435 -0
- package/docs/CREAR_APP_Y_USAR_POOLS.md +1595 -0
- package/docs/EVENTS_APP_MANUAL.md +289 -0
- package/docs/INSITU_BINARY_UPLOAD_PROPOSAL.md +186 -0
- package/docs/INSITU_FIREWALL_EXCEPTION.md +187 -0
- package/docs/ROADMAP.md +242 -0
- package/docs/ROADMAP.md.backup +243 -0
- package/includes/404-hooks.js +423 -0
- package/includes/adminAuth.js +214 -0
- package/includes/adminExtension.js +53 -0
- package/includes/appHooks.js +302 -0
- package/includes/initAdminDb.js +115 -0
- package/includes/routeLoader.js +67 -0
- package/includes/sessions.js +223 -0
- package/issues/001-duplicate-module-loading.md +92 -0
- package/manuales/ADMIN_EXTENSION_COMMANDS_MANUAL.md +261 -0
- package/manuales/ADMIN_EXTENSION_HOOK_EXAMPLE.md +28 -0
- package/manuales/ADMIN_EXTENSION_INTEGRATION_MANUAL.md +232 -0
- package/manuales/CACHE_REGEX_COMMANDS.md +136 -0
- package/manuales/CACHE_SYSTEM_MAP.md +206 -0
- package/manuales/CREACION_DE_CONTROLADORES_INSITU.md +383 -0
- package/manuales/QUEUE_CLI_MODULE_MANUAL.md +289 -0
- package/manuales/QUEUE_SYSTEM_MANUAL.md +320 -0
- package/manuales/ROUTE_CACHE_MODULE_MANUAL.md +205 -0
- package/manuales/SESSION_MANAGER_GUIDE.md +529 -0
- package/manuales/SESSION_SECURITY_FLAGS.md +174 -0
- package/manuales/WAF_MODULE_MANUAL.md +229 -0
- package/manuales/after_route_handler_filter_example.md +116 -0
- package/manuales/after_route_handler_usage.md +130 -0
- package/manuales/an/303/241lisis-completo-insitu-framework.md +213 -0
- package/manuales/async_hooks_promises_guide.md +325 -0
- package/manuales/before_route_handler_filter_example.md +97 -0
- package/manuales/before_route_handler_usage.md +122 -0
- package/manuales/hooks_chaining_conditions_guide.md +261 -0
- package/manuales/hooks_filters_documentation.md +493 -0
- package/manuales/hooks_filters_documentation_en.md +493 -0
- package/manuales/hooks_vs_middlewares_comparison.md +87 -0
- package/manuales/manual-mvc-completo.md +934 -0
- package/manuales/modulos_administracion.md +89 -0
- package/manuales/router_execution_points.md +74 -0
- package/manuales/static_file_hooks_usage.md +222 -0
- package/models/AdminUserModel.js +132 -0
- package/package.json +45 -0
- package/programatically/PRoutes.js +89 -0
- package/programatically/initFlow.js +211 -0
- package/public/admin/css/db-pools.css +336 -0
- package/public/admin/css/styles.css +310 -0
- package/public/admin/database.html +312 -0
- package/public/admin/index.html +116 -0
- package/public/admin/js/app.js +470 -0
- package/public/admin/js/db-pools.js +253 -0
- package/public/admin/login.html +278 -0
- package/public/assets/css/styles.css +477 -0
- package/public/assets/js/main.js +89 -0
- package/public/index.html +136 -0
- package/public/templates/404.html +158 -0
- package/routes/admin-views.json +20 -0
- package/routes/admin.json +38 -0
- package/routes/auth.json +32 -0
- package/routes/static.json +18 -0
- package/server.js +299 -0
- package/test-aplicacion.con-logisession/BlackCoffee.js +226 -0
- package/test-aplicacion.con-logisession/SSL_SETUP.md +53 -0
- package/test-aplicacion.con-logisession/certs/ca-certificate.pem +32 -0
- package/test-aplicacion.con-logisession/certs/ca-private-key.pem +52 -0
- package/test-aplicacion.con-logisession/certs/certificate-2048.pem +22 -0
- package/test-aplicacion.con-logisession/certs/certificate.pem +32 -0
- package/test-aplicacion.con-logisession/certs/private-key-2048.pem +28 -0
- package/test-aplicacion.con-logisession/certs/private-key.pem +52 -0
- package/test-aplicacion.con-logisession/config/iaQueueSetup.js +84 -0
- package/test-aplicacion.con-logisession/config/qwen-rules.json +39 -0
- package/test-aplicacion.con-logisession/controllers/analyticsController.js +117 -0
- package/test-aplicacion.con-logisession/controllers/auth/AdminAuthController.js +142 -0
- package/test-aplicacion.con-logisession/controllers/auth/AuthController.js +439 -0
- package/test-aplicacion.con-logisession/controllers/auth/AuthViewController.js +223 -0
- package/test-aplicacion.con-logisession/controllers/endpointController.js +66 -0
- package/test-aplicacion.con-logisession/controllers/example.js +183 -0
- package/test-aplicacion.con-logisession/controllers/iaQueueController.js +367 -0
- package/test-aplicacion.con-logisession/controllers/queueController.js +206 -0
- package/test-aplicacion.con-logisession/controllers/qwenQueueController.js +197 -0
- package/test-aplicacion.con-logisession/controllers/test.js +0 -0
- package/test-aplicacion.con-logisession/controllers/tracking/EventsNoFinishController.js +78 -0
- package/test-aplicacion.con-logisession/controllers/tracking/TrackingController.js +412 -0
- package/test-aplicacion.con-logisession/controllers/tracking/TrackingControllerWithLoadModel.js +437 -0
- package/test-aplicacion.con-logisession/hooks/admin-hooks.js +20 -0
- package/test-aplicacion.con-logisession/hooks/general-hooks.js +97 -0
- package/test-aplicacion.con-logisession/hooks/queue-hooks.js +64 -0
- package/test-aplicacion.con-logisession/hooks/route-directory-hooks.js +38 -0
- package/test-aplicacion.con-logisession/hooks/security-hooks.js +24 -0
- package/test-aplicacion.con-logisession/insitu-admin-client/README.md +69 -0
- package/test-aplicacion.con-logisession/insitu-admin-client/package.json +23 -0
- package/test-aplicacion.con-logisession/insitu-admin-client.js +257 -0
- package/test-aplicacion.con-logisession/models/ExampleModel.js +88 -0
- package/test-aplicacion.con-logisession/models/QueueJobModel.js +263 -0
- package/test-aplicacion.con-logisession/models/TokenModel.js +207 -0
- package/test-aplicacion.con-logisession/models/auth/AuthModel.js +66 -0
- package/test-aplicacion.con-logisession/models/auth/UserModel.js +189 -0
- package/test-aplicacion.con-logisession/models/tracking/CompletedCartModel.js +213 -0
- package/test-aplicacion.con-logisession/models/tracking/EventModel.js +366 -0
- package/test-aplicacion.con-logisession/models/tracking/EventsNoFinishModel.js +131 -0
- package/test-aplicacion.con-logisession/models/tracking/SessionModel.js +360 -0
- package/test-aplicacion.con-logisession/models/tracking/SiteFlowModel.js +286 -0
- package/test-aplicacion.con-logisession/models/tracking/TokenModel.js +207 -0
- package/test-aplicacion.con-logisession/package-lock.json +3313 -0
- package/test-aplicacion.con-logisession/package.json +32 -0
- package/test-aplicacion.con-logisession/public/blackcoffee-welcome/index.html +1339 -0
- package/test-aplicacion.con-logisession/public/css/style.css +64 -0
- package/test-aplicacion.con-logisession/public/ejemplo-estatica/index.html +18 -0
- package/test-aplicacion.con-logisession/public/ejemplo-estatica/script.js +16 -0
- package/test-aplicacion.con-logisession/public/ejemplo-estatica/styles.css +43 -0
- package/test-aplicacion.con-logisession/public/images/logo.svg +7 -0
- package/test-aplicacion.con-logisession/public/js/main.js +67 -0
- package/test-aplicacion.con-logisession/routes/analytics-routes.json +8 -0
- package/test-aplicacion.con-logisession/routes/auth-routes.json +98 -0
- package/test-aplicacion.con-logisession/routes/blackcoffee-welcome-routes.json +20 -0
- package/test-aplicacion.con-logisession/routes/duplicate-test-routes.json.disabled +16 -0
- package/test-aplicacion.con-logisession/routes/ejemplo-estatica-routes.json +11 -0
- package/test-aplicacion.con-logisession/routes/endpoints-routes.json +8 -0
- package/test-aplicacion.con-logisession/routes/ia-queue-routes.json +26 -0
- package/test-aplicacion.con-logisession/routes/product-routes.json.disabled +20 -0
- package/test-aplicacion.con-logisession/routes/queue-routes.json +32 -0
- package/test-aplicacion.con-logisession/routes/qwen-routes.json +14 -0
- package/test-aplicacion.con-logisession/routes/static-routes.json +29 -0
- package/test-aplicacion.con-logisession/routes/tracking-routes.json +58 -0
- package/test-aplicacion.con-logisession/routes/tracking-with-loadmodel-routes.json +51 -0
- package/test-aplicacion.con-logisession/utils/dbAdapter.js +88 -0
- package/test-aplicacion.con-logisession/utils/qbWrapper.js +4 -0
- package/test-aplicacion.con-logisession/utils/queueProcessor.js +305 -0
- package/test-aplicacion.con-logisession/utils/qwenRulesService.js +131 -0
- package/test-aplicacion.con-logisession/utils/tokenHelper.js +22 -0
- package/test-aplicacion.con-logisession/views/auth/dashboard.html +443 -0
- package/test-aplicacion.con-logisession/views/auth/forgot-password.html +200 -0
- package/test-aplicacion.con-logisession/views/auth/login.html +213 -0
- package/test-aplicacion.con-logisession/views/auth/register.html +294 -0
- package/test-aplicacion.con-logisession/views/contact/form.html +47 -0
- 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>
|