jerkjs 2.1.7 → 2.3.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 +56 -0
- package/README.md +204 -4
- package/README_EN.md +1 -1
- package/README_PT.md +1 -1
- package/docs/ARQUITECTURA_ROUTES.md +84 -38
- package/{JERK_FRAMEWORK_DOCUMENTATION.md → docs/JERK_FRAMEWORK_DOCUMENTATION.md} +28 -2
- package/docs/JERK_MODELOS_HOWTO.md +566 -0
- package/index.js +41 -5
- package/jerk-qbuilder/CHANGELOG.md +71 -0
- package/jerk-qbuilder/HOWTO.md +325 -0
- package/jerk-qbuilder/README.md +52 -0
- package/lib/core/server.js +328 -27
- package/lib/loader/routeLoader.js +148 -117
- package/lib/mvc/GenericAdapter.js +136 -0
- package/lib/mvc/MariaDBAdapter.js +315 -0
- package/lib/mvc/MemoryAdapter.js +269 -0
- package/lib/mvc/ModelControllerExample.js +285 -0
- package/lib/mvc/controllerBase.js +77 -0
- package/lib/mvc/modelBase.js +383 -0
- package/lib/mvc/modelManager.js +284 -0
- package/lib/mvc/userModel.js +265 -0
- package/lib/mvc/viewEngine.js +32 -1
- package/lib/query/MariaDBAdapter.js +78 -0
- package/lib/query/consoleAdapter.js +184 -0
- package/lib/query/queryBuilder.js +953 -0
- package/lib/query/queryBuilderHooks.js +455 -0
- package/lib/query/queryBuilderMiddleware.js +332 -0
- package/lib/utils/mimeType.js +62 -0
- package/package.json +5 -3
- package/utils/find_file_path.sh +36 -0
- package/BUG_REPORTE_COMPRESION.txt +0 -72
- package/standard/CompressionTestController.js +0 -56
- package/standard/HealthController.js +0 -16
- package/standard/HomeController.js +0 -12
- package/standard/ProductController.js +0 -18
- package/standard/README.md +0 -47
- package/standard/UserController.js +0 -23
- package/standard/package.json +0 -22
- package/standard/routes.json +0 -65
- package/standard/server.js +0 -140
- package/standardA/controllers/AuthController.js +0 -82
- package/standardA/controllers/HomeController.js +0 -19
- package/standardA/controllers/UserController.js +0 -41
- package/standardA/server.js +0 -311
- package/standardA/views/auth/dashboard.html +0 -51
- package/standardA/views/auth/login.html +0 -47
- package/standardA/views/index.html +0 -32
- package/standardA/views/users/detail.html +0 -28
- package/standardA/views/users/list.html +0 -36
- /package/{JERK_FRAMEWORK_DIAGRAM.txt → docs/JERK_FRAMEWORK_DIAGRAM.txt} +0 -0
- /package/{JERK_FRAMEWORK_DIAGRAM_MERMAID.mmd → docs/JERK_FRAMEWORK_DIAGRAM_MERMAID.mmd} +0 -0
package/standardA/server.js
DELETED
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
const { APIServer, Logger, Authenticator, Cors, RateLimiter, Compressor, Firewall, SessionManager, ViewEngine, ControllerBase, hooks } = require('../index.js');
|
|
2
|
-
|
|
3
|
-
class StandardAServer {
|
|
4
|
-
constructor() {
|
|
5
|
-
// Obtener puerto de variable de entorno, obligatorio
|
|
6
|
-
this.port = parseInt(process.env.PORT) || 3000;
|
|
7
|
-
this.host = process.env.HOST || 'localhost';
|
|
8
|
-
|
|
9
|
-
// Inicializar componentes
|
|
10
|
-
this.server = new APIServer({
|
|
11
|
-
port: this.port,
|
|
12
|
-
host: this.host,
|
|
13
|
-
https: process.env.USE_HTTPS === 'true',
|
|
14
|
-
key: process.env.HTTPS_KEY_PATH,
|
|
15
|
-
cert: process.env.HTTPS_CERT_PATH,
|
|
16
|
-
requestTimeout: parseInt(process.env.REQUEST_TIMEOUT) || 120000,
|
|
17
|
-
connectionTimeout: parseInt(process.env.CONNECTION_TIMEOUT) || 120000,
|
|
18
|
-
maxBodySize: parseInt(process.env.MAX_BODY_SIZE) || 10 * 1024 * 1024
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
this.logger = new Logger();
|
|
22
|
-
this.authenticator = new Authenticator({ logger: this.logger });
|
|
23
|
-
this.cors = new Cors();
|
|
24
|
-
this.rateLimiter = new RateLimiter();
|
|
25
|
-
this.compressor = new Compressor({ hooks: hooks });
|
|
26
|
-
this.firewall = new Firewall({ logger: this.logger });
|
|
27
|
-
this.sessionManager = new SessionManager();
|
|
28
|
-
// Inicializar ViewEngine con hooks para diagnóstico
|
|
29
|
-
this.viewEngine = new ViewEngine({ hooks: hooks });
|
|
30
|
-
|
|
31
|
-
// Registrar hooks para diagnóstico del sistema de vistas
|
|
32
|
-
this.setupViewEngineDiagnostics();
|
|
33
|
-
|
|
34
|
-
// Registrar hooks para mostrar información al iniciar
|
|
35
|
-
this.setupHooks();
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
setupViewEngineDiagnostics() {
|
|
39
|
-
// Hook para diagnosticar la obtención de rutas de vistas
|
|
40
|
-
hooks.addFilter('view_get_path_before_processing', (viewName, viewEngine) => {
|
|
41
|
-
console.log(`[DIAGNÓSTICO VIEW] getViewPath llamado con: "${viewName}"`);
|
|
42
|
-
return viewName;
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
// Hook para diagnosticar la detección de extensión
|
|
46
|
-
hooks.addAction('view_extension_detection', (viewName, hasExtension, viewEngine) => {
|
|
47
|
-
console.log(`[DIAGNÓSTICO VIEW] Nombre: "${viewName}", Tiene extensión: ${hasExtension}`);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
// Hook para diagnosticar la normalización del path
|
|
51
|
-
hooks.addFilter('view_normalized_path', (normalizedPath, viewName, hasExtension, viewEngine) => {
|
|
52
|
-
console.log(`[DIAGNÓSTICO VIEW] Path normalizado: "${normalizedPath}", Original: "${viewName}", Tiene extensión: ${hasExtension}`);
|
|
53
|
-
return normalizedPath;
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
// Hook para diagnosticar la ruta final
|
|
57
|
-
hooks.addFilter('view_get_path_after_processing', (viewPath, viewName, normalizedPath, viewEngine) => {
|
|
58
|
-
console.log(`[DIAGNÓSTICO VIEW] Ruta final: "${viewPath}", Nombre original: "${viewName}", Path normalizado: "${normalizedPath}"`);
|
|
59
|
-
return viewPath;
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
setupHooks() {
|
|
64
|
-
// Hook para mostrar información antes de iniciar el servidor
|
|
65
|
-
hooks.addAction('pre_server_start', (server) => {
|
|
66
|
-
this.logger.info('=== INICIANDO SERVIDOR ESTÁNDAR JERK (Sin routes.json) ===');
|
|
67
|
-
this.logger.info(`Puerto: ${this.port}`);
|
|
68
|
-
this.logger.info('Características habilitadas:');
|
|
69
|
-
this.logger.info('- Autenticación (JWT, API Key, Basic, OAuth2, OIDC)');
|
|
70
|
-
this.logger.info('- CORS');
|
|
71
|
-
this.logger.info('- Rate Limiting');
|
|
72
|
-
this.logger.info('- Compresión');
|
|
73
|
-
this.logger.info('- Firewall');
|
|
74
|
-
this.logger.info('- Sesiones');
|
|
75
|
-
this.logger.info('- Sistema de Hooks/Filters');
|
|
76
|
-
this.logger.info('- MVC (Modelo-Vista-Controlador)');
|
|
77
|
-
this.logger.info('- Motor de plantillas');
|
|
78
|
-
this.logger.info('- Manejo de errores');
|
|
79
|
-
|
|
80
|
-
// Configurar rutas sin usar routes.json
|
|
81
|
-
this.configureRoutes();
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
// Hook para mostrar información después de iniciar el servidor
|
|
85
|
-
hooks.addAction('post_server_start', (server) => {
|
|
86
|
-
this.logger.info(`Servidor estándar JERK escuchando en http://${this.host}:${this.port}`);
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
configureRoutes() {
|
|
91
|
-
// Importar controladores
|
|
92
|
-
const HomeController = require('./controllers/HomeController');
|
|
93
|
-
const UserController = require('./controllers/UserController');
|
|
94
|
-
const AuthController = require('./controllers/AuthController');
|
|
95
|
-
|
|
96
|
-
// Ruta principal
|
|
97
|
-
this.server.addRoute('GET', '/', (req, res) => {
|
|
98
|
-
HomeController.setRequestResponse(req, res);
|
|
99
|
-
HomeController.index(req, res);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
// Rutas de usuarios
|
|
103
|
-
this.server.addRoute('GET', '/users', (req, res) => {
|
|
104
|
-
UserController.setRequestResponse(req, res);
|
|
105
|
-
UserController.getAll(req, res);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
this.server.addRoute('GET', '/users/:id', (req, res) => {
|
|
109
|
-
UserController.setRequestResponse(req, res);
|
|
110
|
-
UserController.getById(req, res);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// Rutas de autenticación con sesiones
|
|
114
|
-
this.server.addRoute('GET', '/login', (req, res) => {
|
|
115
|
-
AuthController.setRequestResponse(req, res);
|
|
116
|
-
AuthController.showLogin(req, res);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
this.server.addRoute('POST', '/auth/login', (req, res) => {
|
|
120
|
-
AuthController.setRequestResponse(req, res);
|
|
121
|
-
AuthController.handleLogin(req, res);
|
|
122
|
-
}, {
|
|
123
|
-
contentType: 'application/json'
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
this.server.addRoute('POST', '/auth/logout', (req, res) => {
|
|
127
|
-
AuthController.setRequestResponse(req, res);
|
|
128
|
-
AuthController.handleLogout(req, res);
|
|
129
|
-
}, {
|
|
130
|
-
contentType: 'application/json'
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
// Ruta protegida con autenticación por sesión
|
|
134
|
-
this.server.addRoute('GET', '/dashboard', (req, res) => {
|
|
135
|
-
// Verificar si el usuario está autenticado a través de sesión
|
|
136
|
-
if (!req.session || !req.session.data || !req.session.data.authenticated) {
|
|
137
|
-
res.writeHead(302, { 'Location': '/login' });
|
|
138
|
-
res.end();
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
AuthController.setRequestResponse(req, res);
|
|
143
|
-
AuthController.showDashboard(req, res);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
// Ruta protegida con autenticación JWT - usando middleware de autenticación
|
|
147
|
-
// Verificar que JWT_SECRET esté definido antes de crear la ruta
|
|
148
|
-
const jwtSecret = process.env.JWT_SECRET;
|
|
149
|
-
if (!jwtSecret) {
|
|
150
|
-
this.logger.warn('Advertencia: JWT_SECRET no está definido. La ruta /api/protected no estará disponible.');
|
|
151
|
-
} else {
|
|
152
|
-
// Registrar estrategia JWT en el authenticator como se hace en los ejemplos
|
|
153
|
-
this.authenticator.use('jwt-standardA', async (req, options = {}) => {
|
|
154
|
-
const authHeader = req.headers.authorization;
|
|
155
|
-
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
|
|
156
|
-
|
|
157
|
-
if (!token) {
|
|
158
|
-
return false;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
try {
|
|
162
|
-
// Usar jsonwebtoken para validar el token como en los ejemplos
|
|
163
|
-
const jwt = require('jsonwebtoken');
|
|
164
|
-
const decoded = jwt.verify(token, jwtSecret);
|
|
165
|
-
|
|
166
|
-
// Agregar información del usuario a la solicitud
|
|
167
|
-
req.user = decoded;
|
|
168
|
-
return true;
|
|
169
|
-
} catch (error) {
|
|
170
|
-
// Token inválido o expirado
|
|
171
|
-
return false;
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
this.server.addRoute('GET', '/api/protected', (req, res) => {
|
|
176
|
-
// Aplicar autenticación JWT manualmente
|
|
177
|
-
const authHeader = req.headers.authorization;
|
|
178
|
-
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
|
|
179
|
-
|
|
180
|
-
if (!token) {
|
|
181
|
-
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
182
|
-
res.end(JSON.stringify({ error: 'Token no proporcionado' }));
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
try {
|
|
187
|
-
// Usar jsonwebtoken para validar el token como en los ejemplos
|
|
188
|
-
const jwt = require('jsonwebtoken');
|
|
189
|
-
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
|
190
|
-
|
|
191
|
-
// Agregar información del usuario a la solicitud
|
|
192
|
-
req.user = decoded;
|
|
193
|
-
|
|
194
|
-
// Continuar con la lógica del controlador
|
|
195
|
-
AuthController.setRequestResponse(req, res);
|
|
196
|
-
AuthController.protectedApiEndpoint(req, res);
|
|
197
|
-
} catch (error) {
|
|
198
|
-
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
199
|
-
res.end(JSON.stringify({ error: 'Token inválido o expirado' }));
|
|
200
|
-
}
|
|
201
|
-
}, {
|
|
202
|
-
contentType: 'application/json'
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Endpoint para login JWT
|
|
207
|
-
this.server.addRoute('POST', '/api/login', (req, res) => {
|
|
208
|
-
const { username, password } = req.body;
|
|
209
|
-
|
|
210
|
-
// Validación básica
|
|
211
|
-
if (!username || !password) {
|
|
212
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
213
|
-
res.end(JSON.stringify({
|
|
214
|
-
success: false,
|
|
215
|
-
message: 'Usuario y contraseña son requeridos'
|
|
216
|
-
}));
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Lógica de autenticación simulada
|
|
221
|
-
if (username === 'admin' && password === 'password123') {
|
|
222
|
-
// Verificar que JWT_SECRET esté definido en las variables de entorno
|
|
223
|
-
const jwtSecret = process.env.JWT_SECRET;
|
|
224
|
-
if (!jwtSecret) {
|
|
225
|
-
this.logger.error('ERROR: JWT_SECRET no está definido en las variables de entorno');
|
|
226
|
-
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
227
|
-
res.end(JSON.stringify({
|
|
228
|
-
success: false,
|
|
229
|
-
message: 'Configuración de seguridad incompleta - JWT_SECRET no definido'
|
|
230
|
-
}));
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Generar un token JWT usando jsonwebtoken directamente como en los ejemplos
|
|
235
|
-
const jwt = require('jsonwebtoken');
|
|
236
|
-
const payload = {
|
|
237
|
-
userId: 1,
|
|
238
|
-
username: username,
|
|
239
|
-
role: 'admin'
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
const token = jwt.sign(payload, jwtSecret, { expiresIn: '1h' });
|
|
243
|
-
|
|
244
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
245
|
-
res.end(JSON.stringify({
|
|
246
|
-
success: true,
|
|
247
|
-
token,
|
|
248
|
-
user: { id: 1, username: 'admin', role: 'admin' }
|
|
249
|
-
}));
|
|
250
|
-
} else {
|
|
251
|
-
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
252
|
-
res.end(JSON.stringify({
|
|
253
|
-
success: false,
|
|
254
|
-
message: 'Credenciales inválidas'
|
|
255
|
-
}));
|
|
256
|
-
}
|
|
257
|
-
}, {
|
|
258
|
-
contentType: 'application/json'
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
// Ruta de API con content-type JSON
|
|
262
|
-
this.server.addRoute('GET', '/api/data', (req, res) => {
|
|
263
|
-
res.end(JSON.stringify({ message: 'Datos de la API', timestamp: new Date().toISOString() }));
|
|
264
|
-
}, {
|
|
265
|
-
contentType: 'application/json'
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
this.logger.info('Rutas configuradas sin usar routes.json');
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// Método para configurar middlewares estándar
|
|
272
|
-
configureStandardMiddlewares() {
|
|
273
|
-
// Middleware de firewall (debe ir primero)
|
|
274
|
-
this.server.use(this.firewall.middleware());
|
|
275
|
-
|
|
276
|
-
// Middleware de compresión
|
|
277
|
-
this.server.use(this.compressor.middleware());
|
|
278
|
-
|
|
279
|
-
// Middleware de CORS
|
|
280
|
-
this.server.use(this.cors.middleware());
|
|
281
|
-
|
|
282
|
-
// Middleware de limitación de tasa
|
|
283
|
-
this.server.use(this.rateLimiter.middleware());
|
|
284
|
-
|
|
285
|
-
// Middleware de sesión
|
|
286
|
-
this.server.use(this.sessionManager.middleware());
|
|
287
|
-
|
|
288
|
-
// Configurar el motor de vistas en el servidor
|
|
289
|
-
this.server.viewEngine = this.viewEngine;
|
|
290
|
-
const path = require('path');
|
|
291
|
-
this.viewEngine.viewsPath = path.resolve(__dirname, 'views');
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
start() {
|
|
295
|
-
// Configurar middlewares estándar
|
|
296
|
-
this.configureStandardMiddlewares();
|
|
297
|
-
|
|
298
|
-
// Iniciar el servidor
|
|
299
|
-
this.server.start();
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
stop() {
|
|
303
|
-
this.server.stop();
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// Crear e iniciar el servidor estándar
|
|
308
|
-
const standardAServer = new StandardAServer();
|
|
309
|
-
standardAServer.start();
|
|
310
|
-
|
|
311
|
-
module.exports = StandardAServer;
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<title>{{title}}</title>
|
|
5
|
-
<meta charset="UTF-8">
|
|
6
|
-
<style>
|
|
7
|
-
body { font-family: Arial, sans-serif; margin: 40px; background-color: #f5f5f5; }
|
|
8
|
-
.container { max-width: 800px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
|
|
9
|
-
.header { background-color: #007bff; color: white; padding: 15px; border-radius: 5px; margin-bottom: 20px; }
|
|
10
|
-
.user-info { background-color: #f8f9fa; padding: 15px; border-radius: 5px; margin-bottom: 20px; }
|
|
11
|
-
.nav { margin: 20px 0; }
|
|
12
|
-
.nav a { display: inline-block; margin-right: 15px; padding: 8px 15px; background-color: #28a745; color: white; text-decoration: none; border-radius: 3px; }
|
|
13
|
-
.nav a:hover { background-color: #218838; }
|
|
14
|
-
.section { margin: 20px 0; padding: 15px; border-left: 4px solid #007bff; background-color: #f8f9fa; }
|
|
15
|
-
</style>
|
|
16
|
-
</head>
|
|
17
|
-
<body>
|
|
18
|
-
<div class="container">
|
|
19
|
-
<div class="header">
|
|
20
|
-
<h1>{{title}}</h1>
|
|
21
|
-
</div>
|
|
22
|
-
|
|
23
|
-
<div class="user-info">
|
|
24
|
-
<h3>Bienvenido, {{user.username}}!</h3>
|
|
25
|
-
<p>Rol: {{user.role}}</p>
|
|
26
|
-
</div>
|
|
27
|
-
|
|
28
|
-
<div class="nav">
|
|
29
|
-
<a href="/">Inicio</a>
|
|
30
|
-
<a href="/users">Usuarios</a>
|
|
31
|
-
<a href="/auth/logout">Cerrar Sesión</a>
|
|
32
|
-
</div>
|
|
33
|
-
|
|
34
|
-
<div class="section">
|
|
35
|
-
<h2>Panel de Control</h2>
|
|
36
|
-
<p>Este es un área protegida que solo pueden acceder usuarios autenticados.</p>
|
|
37
|
-
<p>Aquí puedes gestionar la configuración de la aplicación, ver estadísticas y realizar tareas administrativas.</p>
|
|
38
|
-
</div>
|
|
39
|
-
|
|
40
|
-
<div class="section">
|
|
41
|
-
<h2>Funcionalidades Disponibles</h2>
|
|
42
|
-
<ul>
|
|
43
|
-
<li>Administración de usuarios</li>
|
|
44
|
-
<li>Configuración del sistema</li>
|
|
45
|
-
<li>Visualización de estadísticas</li>
|
|
46
|
-
<li>Registro de actividad</li>
|
|
47
|
-
</ul>
|
|
48
|
-
</div>
|
|
49
|
-
</div>
|
|
50
|
-
</body>
|
|
51
|
-
</html>
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<title>{{title}}</title>
|
|
5
|
-
<meta charset="UTF-8">
|
|
6
|
-
<style>
|
|
7
|
-
body { font-family: Arial, sans-serif; margin: 40px; background-color: #f5f5f5; }
|
|
8
|
-
.container { max-width: 400px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
|
|
9
|
-
.form-group { margin-bottom: 15px; }
|
|
10
|
-
label { display: block; margin-bottom: 5px; font-weight: bold; }
|
|
11
|
-
input[type="text"], input[type="password"] { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; }
|
|
12
|
-
button { background-color: #007bff; color: white; padding: 10px 15px; border: none; border-radius: 4px; cursor: pointer; width: 100%; }
|
|
13
|
-
button:hover { background-color: #0056b3; }
|
|
14
|
-
.error { color: red; margin-bottom: 15px; }
|
|
15
|
-
.nav { margin-top: 20px; text-align: center; }
|
|
16
|
-
.nav a { color: #007bff; text-decoration: none; }
|
|
17
|
-
.nav a:hover { text-decoration: underline; }
|
|
18
|
-
</style>
|
|
19
|
-
</head>
|
|
20
|
-
<body>
|
|
21
|
-
<div class="container">
|
|
22
|
-
<h1>{{title}}</h1>
|
|
23
|
-
|
|
24
|
-
{{if error}}
|
|
25
|
-
<div class="error">{{error}}</div>
|
|
26
|
-
{{endif}}
|
|
27
|
-
|
|
28
|
-
<form method="POST" action="/auth/login">
|
|
29
|
-
<div class="form-group">
|
|
30
|
-
<label for="username">Usuario:</label>
|
|
31
|
-
<input type="text" id="username" name="username" required>
|
|
32
|
-
</div>
|
|
33
|
-
|
|
34
|
-
<div class="form-group">
|
|
35
|
-
<label for="password">Contraseña:</label>
|
|
36
|
-
<input type="password" id="password" name="password" required>
|
|
37
|
-
</div>
|
|
38
|
-
|
|
39
|
-
<button type="submit">Iniciar Sesión</button>
|
|
40
|
-
</form>
|
|
41
|
-
|
|
42
|
-
<div class="nav">
|
|
43
|
-
<a href="/">Volver al inicio</a>
|
|
44
|
-
</div>
|
|
45
|
-
</div>
|
|
46
|
-
</body>
|
|
47
|
-
</html>
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<title>{{title}}</title>
|
|
5
|
-
<meta charset="UTF-8">
|
|
6
|
-
<style>
|
|
7
|
-
body { font-family: Arial, sans-serif; margin: 40px; }
|
|
8
|
-
.header { background-color: #f0f0f0; padding: 20px; border-radius: 5px; }
|
|
9
|
-
.content { margin-top: 20px; }
|
|
10
|
-
.nav { margin-top: 20px; }
|
|
11
|
-
.nav a { display: inline-block; margin-right: 15px; padding: 8px 15px; background-color: #007bff; color: white; text-decoration: none; border-radius: 3px; }
|
|
12
|
-
.nav a:hover { background-color: #0056b3; }
|
|
13
|
-
</style>
|
|
14
|
-
</head>
|
|
15
|
-
<body>
|
|
16
|
-
<div class="header">
|
|
17
|
-
<h1>{{title}}</h1>
|
|
18
|
-
<p>{{message}}</p>
|
|
19
|
-
</div>
|
|
20
|
-
|
|
21
|
-
<div class="content">
|
|
22
|
-
<h2>Ejemplo de aplicación sin routes.json</h2>
|
|
23
|
-
<p>Este servidor utiliza el método <code>addRoute()</code> con todas las funcionalidades equivalentes a las de <code>routes.json</code>.</p>
|
|
24
|
-
|
|
25
|
-
<div class="nav">
|
|
26
|
-
<a href="/">Inicio</a>
|
|
27
|
-
<a href="/users">Usuarios</a>
|
|
28
|
-
<a href="/api/data">API Data</a>
|
|
29
|
-
</div>
|
|
30
|
-
</div>
|
|
31
|
-
</body>
|
|
32
|
-
</html>
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<title>Detalle del Usuario</title>
|
|
5
|
-
<meta charset="UTF-8">
|
|
6
|
-
<style>
|
|
7
|
-
body { font-family: Arial, sans-serif; margin: 40px; }
|
|
8
|
-
.header { background-color: #f0f0f0; padding: 20px; border-radius: 5px; }
|
|
9
|
-
.user-info { background-color: #f9f9f9; padding: 20px; border-radius: 5px; margin-top: 20px; }
|
|
10
|
-
.back-link { margin-top: 20px; }
|
|
11
|
-
</style>
|
|
12
|
-
</head>
|
|
13
|
-
<body>
|
|
14
|
-
<div class="header">
|
|
15
|
-
<h1>Detalle del Usuario</h1>
|
|
16
|
-
<div class="back-link">
|
|
17
|
-
<a href="/users">← Volver a la lista de usuarios</a>
|
|
18
|
-
</div>
|
|
19
|
-
</div>
|
|
20
|
-
|
|
21
|
-
<div class="user-info">
|
|
22
|
-
<h2>{{user.name}}</h2>
|
|
23
|
-
<p><strong>ID:</strong> {{user.id}}</p>
|
|
24
|
-
<p><strong>Email:</strong> {{user.email}}</p>
|
|
25
|
-
<p><strong>Registrado:</strong> {{user.registered}}</p>
|
|
26
|
-
</div>
|
|
27
|
-
</body>
|
|
28
|
-
</html>
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<title>{{title}}</title>
|
|
5
|
-
<meta charset="UTF-8">
|
|
6
|
-
<style>
|
|
7
|
-
body { font-family: Arial, sans-serif; margin: 40px; }
|
|
8
|
-
.header { background-color: #f0f0f0; padding: 20px; border-radius: 5px; }
|
|
9
|
-
.content { margin-top: 20px; }
|
|
10
|
-
ul { list-style-type: none; padding: 0; }
|
|
11
|
-
li { background-color: #f9f9f9; margin: 5px 0; padding: 10px; border-radius: 3px; }
|
|
12
|
-
</style>
|
|
13
|
-
</head>
|
|
14
|
-
<body>
|
|
15
|
-
<div class="header">
|
|
16
|
-
<h1>{{title}}</h1>
|
|
17
|
-
<p>{{message}}</p>
|
|
18
|
-
</div>
|
|
19
|
-
|
|
20
|
-
<div class="content">
|
|
21
|
-
<h2>Usuarios</h2>
|
|
22
|
-
{{if users}}
|
|
23
|
-
<ul>
|
|
24
|
-
{{foreach:users}}
|
|
25
|
-
<li>
|
|
26
|
-
<strong>{{item.name}}</strong> - {{item.email}}
|
|
27
|
-
<br><a href="/users/{{item.id}}">Ver detalles</a>
|
|
28
|
-
</li>
|
|
29
|
-
{{endforeach}}
|
|
30
|
-
</ul>
|
|
31
|
-
{{else}}
|
|
32
|
-
<p>No hay usuarios disponibles.</p>
|
|
33
|
-
{{endif}}
|
|
34
|
-
</div>
|
|
35
|
-
</body>
|
|
36
|
-
</html>
|
|
File without changes
|
|
File without changes
|