utn-cli 2.0.57 → 2.0.58

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "utn-cli",
3
- "version": "2.0.57",
3
+ "version": "2.0.58",
4
4
  "description": "Herramienta CLI unificada para la gestión de plantillas en SIGU.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -353,6 +353,23 @@ Router.get('/cerrarSesion', async (solicitud, respuesta, next) => {
353
353
  }
354
354
  });
355
355
 
356
+ Router.delete('/cerrarSesionPorToken/:Token', async (solicitud, respuesta, next) => {
357
+ try {
358
+ if (await Miscelaneo.validarTokenV2(solicitud.headers.authorization) && await Miscelaneo.validarAccesoDelOrigen(solicitud)) {
359
+ try {
360
+ return respuesta.json({ body: await Miscelaneo.cerrarSesionPorToken(solicitud.params.Token), error: undefined });
361
+ } catch (error) {
362
+ const MensajeDeError = 'No fue posible cerrar la sesión';
363
+ console.error(new ManejadorDeErrores(MensajeDeError, ManejadorDeErrores.obtenerNumeroDeLinea(), true, `Dirección IP: ${solicitud.ip}`));
364
+ return respuesta.status(500).json({ body: undefined, error: MensajeDeError });
365
+ }
366
+ }
367
+ return respuesta.status(401).json({ body: undefined, error: ManejadorDeErrores.mensajeDeError401() });
368
+ } catch (error) {
369
+ next(error);
370
+ }
371
+ });
372
+
356
373
  Router.get('/obtenerEnlaceDePerfil', async (solicitud, respuesta, next) => {
357
374
  try {
358
375
  if (await Miscelaneo.validarTokenV2(solicitud.headers.authorization) && await Miscelaneo.validarAccesoDelOrigen(solicitud)) {
@@ -66,6 +66,7 @@ class Miscelaneo {
66
66
  JSON_VALUE(Solicitud, '$.method') AS Metodo,
67
67
  JSON_VALUE(Solicitud, '$.originalUrl') AS URL,
68
68
  IFNULL(JSON_VALUE(Solicitud, '$.country'), 'Costa Rica') AS Pais,
69
+ SUBSTRING_INDEX(JSON_VALUE(Solicitud, '$.headers.authorization'), ' ', -1) AS Token,
69
70
  CASE
70
71
  WHEN JSON_VALUE(Solicitud, '$.userAgent') LIKE '%Mobile%' THEN 'Móvil'
71
72
  WHEN JSON_VALUE(Solicitud, '$.userAgent') LIKE '%Android%' AND JSON_VALUE(Solicitud, '$.userAgent') NOT LIKE '%Mobile%' THEN 'Tablet'
@@ -85,6 +86,11 @@ class Miscelaneo {
85
86
  }
86
87
  }
87
88
 
89
+ async cerrarSesionPorToken(Token) {
90
+ await ejecutarConsultaSIGU("DELETE FROM `SIGU`.`SIGU_Sesiones` WHERE `Token` = ?", [Token]);
91
+ return true;
92
+ }
93
+
88
94
  async UsuariosActuales() {
89
95
  const ConexionSigu = await crearObjetoConexionSIGU();
90
96
  const Actuales = await ConexionSigu.query("SELECT COUNT(DISTINCT `Identificador`) AS `Total` FROM `SIGU`.`SIGU_Sesiones` WHERE `LastUpdate` >= NOW() - INTERVAL 2 HOUR");
@@ -10,7 +10,7 @@
10
10
  border-radius: 12px;
11
11
  margin-bottom: 20px;
12
12
  border: 1px solid #e0e6ed;
13
- box-shadow: 0 2px 4px rgba(0,0,0,0.05);
13
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
14
14
  text-align: left;
15
15
  }
16
16
 
@@ -80,7 +80,7 @@
80
80
  border: 1px solid #e0e6ed;
81
81
  border-radius: 12px;
82
82
  transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
83
- box-shadow: 0 2px 4px rgba(0,0,0,0.05);
83
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
84
84
  text-align: left;
85
85
  }
86
86
 
@@ -110,17 +110,37 @@
110
110
  }
111
111
 
112
112
  /* Colores por método */
113
- .metodo-get { color: #0069B4; background-color: #e3f2fd; }
114
- .metodo-post { color: #518a5f; background-color: #e8f5e9; }
115
- .metodo-put { color: #FFA757; background-color: #fff3e0; }
116
- .metodo-delete { color: #F82617; background-color: #ffebee; }
117
- .metodo-default { color: #7d7d7d; background-color: #f5f5f5; }
113
+ .metodo-get {
114
+ color: #0069B4;
115
+ background-color: #e3f2fd;
116
+ }
117
+
118
+ .metodo-post {
119
+ color: #518a5f;
120
+ background-color: #e8f5e9;
121
+ }
122
+
123
+ .metodo-put {
124
+ color: #FFA757;
125
+ background-color: #fff3e0;
126
+ }
127
+
128
+ .metodo-delete {
129
+ color: #F82617;
130
+ background-color: #ffebee;
131
+ }
132
+
133
+ .metodo-default {
134
+ color: #7d7d7d;
135
+ background-color: #f5f5f5;
136
+ }
118
137
 
119
138
  .item-contenido {
120
139
  flex: 1;
121
140
  display: flex;
122
141
  flex-direction: column;
123
- min-width: 0; /* Evita que el flex desborde */
142
+ min-width: 0;
143
+ /* Evita que el flex desborde */
124
144
  }
125
145
 
126
146
  .item-titulo {
@@ -129,6 +149,22 @@
129
149
  font-weight: 600;
130
150
  word-break: break-all;
131
151
  margin-bottom: 2px;
152
+ display: flex;
153
+ align-items: center;
154
+ gap: 8px;
155
+ flex-wrap: wrap;
156
+ }
157
+
158
+ .badge-sesion-actual {
159
+ background-color: #e8f5e9;
160
+ color: #2e7d32;
161
+ font-size: 10px;
162
+ padding: 2px 8px;
163
+ border-radius: 12px;
164
+ border: 1px solid #c8e6c9;
165
+ text-transform: uppercase;
166
+ font-weight: 700;
167
+ white-space: nowrap;
132
168
  }
133
169
 
134
170
  .item-detalles {
@@ -152,10 +188,21 @@
152
188
  height: 14px;
153
189
  }
154
190
 
155
- .detalle-dispositivo mat-icon { color: #673ab7; }
156
- .detalle-browser mat-icon { color: #5f6368; }
157
- .detalle-ip mat-icon { color: #0069B4; }
158
- .detalle-pais mat-icon { color: #F87600; }
191
+ .detalle-dispositivo mat-icon {
192
+ color: #673ab7;
193
+ }
194
+
195
+ .detalle-browser mat-icon {
196
+ color: #5f6368;
197
+ }
198
+
199
+ .detalle-ip mat-icon {
200
+ color: #0069B4;
201
+ }
202
+
203
+ .detalle-pais mat-icon {
204
+ color: #F87600;
205
+ }
159
206
 
160
207
  .item-fecha {
161
208
  font-size: 11px;
@@ -180,14 +227,14 @@ mat-progress-bar {
180
227
  flex-direction: column;
181
228
  align-items: flex-start;
182
229
  }
183
-
230
+
184
231
  .item-icono {
185
232
  margin-bottom: 12px;
186
233
  }
187
-
234
+
188
235
  .item-fecha {
189
236
  margin-left: 0;
190
237
  margin-top: 12px;
191
238
  align-items: flex-start;
192
239
  }
193
- }
240
+ }
@@ -2,49 +2,59 @@
2
2
  <div class="header-actividad">
3
3
  <h2><mat-icon>history</mat-icon> Actividad reciente de la cuenta</h2>
4
4
  <p>Historial de acciones realizadas en el sistema</p>
5
-
5
+
6
6
  <div class="mensaje-informativo">
7
7
  <mat-icon>info</mat-icon>
8
- <p>Si tiene dudas sobre la información que ve en pantalla, por favor comparta una captura de pantalla al correo <strong>soporte@utn.ac.cr</strong>.</p>
8
+ <p>Si tiene dudas sobre la información que ve en pantalla, por favor comparta una captura de pantalla al correo
9
+ <strong>soporte@utn.ac.cr</strong>.</p>
9
10
  </div>
10
11
  </div>
11
12
 
12
13
  @if(cargando){
13
- <mat-progress-bar mode="indeterminate"></mat-progress-bar>
14
+ <mat-progress-bar mode="indeterminate"></mat-progress-bar>
14
15
  }
15
-
16
+
16
17
  <div class="lista-actividad">
17
18
  @if(actividad.length === 0 && !cargando) {
18
- <p class="sin-datos">No hay actividad reciente.</p>
19
+ <p class="sin-datos">No hay actividad reciente.</p>
19
20
  }
20
21
 
21
22
  @for (item of actividad; track item.Consecutivo) {
22
- <div class="actividad-item">
23
- <div class="item-icono" [ngClass]="{
23
+ <div class="actividad-item">
24
+ <div class="item-icono" [ngClass]="{
24
25
  'metodo-get': item.Metodo === 'GET',
25
26
  'metodo-post': item.Metodo === 'POST',
26
27
  'metodo-put': item.Metodo === 'PUT',
27
28
  'metodo-delete': item.Metodo === 'DELETE'
28
29
  }">
29
- <mat-icon [matTooltip]="item.Metodo">{{ getIcon(item.Metodo) }}</mat-icon>
30
- </div>
31
- <div class="item-contenido">
32
- <div class="item-titulo">{{ item.URL }}</div>
33
- <div class="item-detalles">
34
- <span class="detalle-dispositivo">
35
- <mat-icon>{{ item.Dispositivo === 'Móvil' ? 'smartphone' : (item.Dispositivo === 'Tablet' ? 'tablet' : 'laptop') }}</mat-icon>
36
- {{ item.Dispositivo }}
37
- </span>
38
- <span class="detalle-browser"><mat-icon>public</mat-icon> {{ item.Navegador }}</span>
39
- <span class="detalle-ip"><mat-icon>lan</mat-icon> {{ item.IP }}</span>
40
- <span class="detalle-pais"><mat-icon>location_on</mat-icon> {{ item.Pais }}</span>
41
- </div>
30
+ <mat-icon [matTooltip]="item.Metodo">{{ getIcon(item.Metodo) }}</mat-icon>
31
+ </div>
32
+ <div class="item-contenido">
33
+ <div class="item-titulo">
34
+ {{ item.URL }}
35
+ @if(esSesionActual(item.Token)){
36
+ <span class="badge-sesion-actual">Sesión actual</span>
37
+ }
42
38
  </div>
43
- <div class="item-fecha">
44
- <span>{{ item.Fecha | date: 'dd/MM/yyyy' }}</span>
45
- <strong>{{ item.Fecha | date: 'HH:mm:ss' }}</strong>
39
+ <div class="item-detalles">
40
+ <span class="detalle-dispositivo">
41
+ <mat-icon>{{ item.Dispositivo === 'Móvil' ? 'smartphone' : (item.Dispositivo === 'Tablet' ? 'tablet' :
42
+ 'laptop') }}</mat-icon>
43
+ {{ item.Dispositivo }}
44
+ </span>
45
+ <span class="detalle-browser"><mat-icon>public</mat-icon> {{ item.Navegador }}</span>
46
+ <span class="detalle-ip"><mat-icon>lan</mat-icon> {{ item.IP }}</span>
47
+ <span class="detalle-pais"><mat-icon>location_on</mat-icon> {{ item.Pais }}</span>
46
48
  </div>
47
49
  </div>
50
+ <div class="item-fecha">
51
+ <span>{{ item.Fecha | date: 'dd/MM/yyyy' }}</span>
52
+ <strong>{{ item.Fecha | date: 'HH:mm:ss' }}</strong>
53
+ <button mat-icon-button color="warn" matTooltip="Cerrar esta sesión" (click)="cerrarSesion(item.Token)">
54
+ <mat-icon>logout</mat-icon>
55
+ </button>
56
+ </div>
57
+ </div>
48
58
  }
49
59
  </div>
50
- </div>
60
+ </div>
@@ -4,26 +4,30 @@ import { HttpClient } from '@angular/common/http';
4
4
  import { MatIconModule } from '@angular/material/icon';
5
5
  import { MatTooltipModule } from '@angular/material/tooltip';
6
6
  import { MatProgressBarModule } from '@angular/material/progress-bar';
7
+ import { MatButtonModule } from '@angular/material/button';
7
8
  import { DatosGlobalesService } from '../../../datos-globales.service';
8
9
 
9
10
  @Component({
10
11
  selector: 'app-gestion-actividad',
11
12
  standalone: true,
12
- imports: [CommonModule, MatIconModule, MatTooltipModule, MatProgressBarModule],
13
+ imports: [CommonModule, MatIconModule, MatTooltipModule, MatProgressBarModule, MatButtonModule],
13
14
  templateUrl: './gestion-actividad.component.html',
14
15
  styleUrls: ['./gestion-actividad.component.css']
15
16
  })
16
17
  export class GestionActividadComponent implements OnInit {
17
18
  public actividad: any[] = [];
18
19
  public cargando = false;
20
+ private tokenActual = '';
19
21
 
20
22
  constructor(private http: HttpClient, private datosGlobalesService: DatosGlobalesService) { }
21
23
 
22
24
  ngOnInit(): void {
25
+ this.tokenActual = this.datosGlobalesService.ObtenerToken().split(' ')[1] || '';
23
26
  this.obtenerActividad();
24
27
  }
25
28
 
26
29
  obtenerActividad(): void {
30
+ this.cargando = true;
27
31
  this.http.get(`${this.datosGlobalesService.ObtenerURL()}misc/ListarActividades`).subscribe({
28
32
  next: (datos: any) => {
29
33
  this.actividad = datos.body;
@@ -36,17 +40,27 @@ export class GestionActividadComponent implements OnInit {
36
40
  this.cargando = false;
37
41
  }
38
42
  });
39
- // this.cargando = true;
40
- // this.http.get<any[]>(`${this.datosGlobalesService.ObtenerURL()}Actividad`).subscribe({
41
- // next: (datos) => {
42
- // this.actividad = datos;
43
- // this.cargando = false;
44
- // },
45
- // error: (error) => {
46
- // console.error('Error al obtener actividad:', error);
47
- // this.cargando = false;
48
- // }
49
- // });
43
+ }
44
+
45
+ cerrarSesion(token: string): void {
46
+ if (confirm('¿Está seguro de que desea cerrar esta sesión?')) {
47
+ this.http.delete(`${this.datosGlobalesService.ObtenerURL()}misc/cerrarSesionPorToken/${token}`).subscribe({
48
+ next: () => {
49
+ if (token === this.tokenActual) {
50
+ this.datosGlobalesService.RedirigirALogin();
51
+ } else {
52
+ this.obtenerActividad();
53
+ }
54
+ },
55
+ error: (error) => {
56
+ console.error('Error al cerrar sesión:', error);
57
+ }
58
+ });
59
+ }
60
+ }
61
+
62
+ esSesionActual(token: string): boolean {
63
+ return token === this.tokenActual;
50
64
  }
51
65
 
52
66
  getIcon(metodo: string): string {
@@ -1,5 +1,240 @@
1
- .mensaje {
2
- margin-top: 1%;
1
+ .contenedor-actividad {
2
+ background-color: transparent;
3
+ margin: 0;
4
+ padding: 10px;
5
+ }
6
+
7
+ .header-actividad {
8
+ background-color: white;
9
+ padding: 20px 24px;
10
+ border-radius: 12px;
11
+ margin-bottom: 20px;
12
+ border: 1px solid #e0e6ed;
13
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
14
+ text-align: left;
15
+ }
16
+
17
+ .header-actividad h2 {
18
+ margin: 0;
19
+ color: #002f6b;
20
+ font-size: 1.5rem;
21
+ font-weight: 700;
3
22
  display: flex;
23
+ align-items: center;
24
+ gap: 12px;
25
+ }
26
+
27
+ .header-actividad h2 mat-icon {
28
+ font-size: 28px;
29
+ width: 28px;
30
+ height: 28px;
31
+ color: #0b4fce;
32
+ }
33
+
34
+ .header-actividad p {
35
+ margin: 8px 0 0 0;
36
+ color: #5f6368;
37
+ font-size: 0.95rem;
38
+ }
39
+
40
+ .mensaje-informativo {
41
+ display: flex;
42
+ align-items: center;
43
+ gap: 12px;
44
+ background-color: #e3f2fd;
45
+ border-left: 4px solid #1976d2;
46
+ padding: 12px 16px;
47
+ margin-top: 20px;
48
+ border-radius: 4px;
49
+ }
50
+
51
+ .mensaje-informativo mat-icon {
52
+ color: #1976d2;
53
+ font-size: 24px;
54
+ width: 24px;
55
+ height: 24px;
56
+ }
57
+
58
+ .mensaje-informativo p {
59
+ margin: 0 !important;
60
+ font-size: 13.5px !important;
61
+ color: #0d47a1 !important;
62
+ line-height: 1.4;
63
+ }
64
+
65
+ .mensaje-informativo strong {
66
+ color: #d32f2f;
67
+ }
68
+
69
+ .lista-actividad {
70
+ display: flex;
71
+ flex-direction: column;
72
+ gap: 12px;
73
+ }
74
+
75
+ .actividad-item {
76
+ display: flex;
77
+ align-items: center;
78
+ padding: 16px;
79
+ background-color: white;
80
+ border: 1px solid #e0e6ed;
81
+ border-radius: 12px;
82
+ transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
83
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
84
+ text-align: left;
85
+ }
86
+
87
+ .actividad-item:hover {
88
+ transform: translateY(-2px);
89
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
90
+ border-color: #0b4fce;
91
+ background-color: #f0f7ff;
92
+ }
93
+
94
+ .item-icono {
95
+ margin-right: 12px;
96
+ display: flex;
97
+ align-items: center;
4
98
  justify-content: center;
99
+ width: 40px;
100
+ height: 40px;
101
+ border-radius: 50%;
102
+ background-color: #f1f3f4;
103
+ flex-shrink: 0;
104
+ }
105
+
106
+ .item-icono mat-icon {
107
+ font-size: 20px;
108
+ width: 20px;
109
+ height: 20px;
110
+ }
111
+
112
+ /* Colores por método */
113
+ .metodo-get {
114
+ color: #0069B4;
115
+ background-color: #e3f2fd;
116
+ }
117
+
118
+ .metodo-post {
119
+ color: #518a5f;
120
+ background-color: #e8f5e9;
121
+ }
122
+
123
+ .metodo-put {
124
+ color: #FFA757;
125
+ background-color: #fff3e0;
126
+ }
127
+
128
+ .metodo-delete {
129
+ color: #F82617;
130
+ background-color: #ffebee;
131
+ }
132
+
133
+ .metodo-default {
134
+ color: #7d7d7d;
135
+ background-color: #f5f5f5;
136
+ }
137
+
138
+ .item-contenido {
139
+ flex: 1;
140
+ display: flex;
141
+ flex-direction: column;
142
+ min-width: 0;
143
+ /* Evita que el flex desborde */
144
+ }
145
+
146
+ .item-titulo {
147
+ font-size: 14px;
148
+ color: #002f6b;
149
+ font-weight: 600;
150
+ word-break: break-all;
151
+ margin-bottom: 2px;
152
+ display: flex;
153
+ align-items: center;
154
+ gap: 8px;
155
+ flex-wrap: wrap;
156
+ }
157
+
158
+ .badge-sesion-actual {
159
+ background-color: #e8f5e9;
160
+ color: #2e7d32;
161
+ font-size: 10px;
162
+ padding: 2px 8px;
163
+ border-radius: 12px;
164
+ border: 1px solid #c8e6c9;
165
+ text-transform: uppercase;
166
+ font-weight: 700;
167
+ white-space: nowrap;
168
+ }
169
+
170
+ .item-detalles {
171
+ display: flex;
172
+ flex-wrap: wrap;
173
+ gap: 8px;
174
+ font-size: 12px;
175
+ color: #5f6368;
176
+ }
177
+
178
+ .item-detalles span {
179
+ display: flex;
180
+ align-items: center;
181
+ gap: 3px;
182
+ white-space: nowrap;
183
+ }
184
+
185
+ .item-detalles mat-icon {
186
+ font-size: 14px;
187
+ width: 14px;
188
+ height: 14px;
189
+ }
190
+
191
+ .detalle-dispositivo mat-icon {
192
+ color: #673ab7;
193
+ }
194
+
195
+ .detalle-browser mat-icon {
196
+ color: #5f6368;
197
+ }
198
+
199
+ .detalle-ip mat-icon {
200
+ color: #0069B4;
201
+ }
202
+
203
+ .detalle-pais mat-icon {
204
+ color: #F87600;
205
+ }
206
+
207
+ .item-fecha {
208
+ font-size: 11px;
209
+ color: #9aa0a6;
210
+ white-space: nowrap;
211
+ margin-left: 12px;
212
+ display: flex;
213
+ flex-direction: column;
214
+ align-items: flex-end;
215
+ font-family: 'Roboto Mono', monospace;
216
+ flex-shrink: 0;
217
+ }
218
+
219
+ mat-progress-bar {
220
+ height: 4px;
221
+ border-radius: 2px;
222
+ margin-bottom: 10px;
223
+ }
224
+
225
+ @media (max-width: 600px) {
226
+ .actividad-item {
227
+ flex-direction: column;
228
+ align-items: flex-start;
229
+ }
230
+
231
+ .item-icono {
232
+ margin-bottom: 12px;
233
+ }
234
+
235
+ .item-fecha {
236
+ margin-left: 0;
237
+ margin-top: 12px;
238
+ align-items: flex-start;
239
+ }
5
240
  }
@@ -1,22 +1,60 @@
1
- <h3 mat-dialog-title>{{ data.titulo }}</h3>
1
+ <div class="contenedor-actividad">
2
+ <div class="header-actividad">
3
+ <h2><mat-icon>history</mat-icon> Actividad reciente de la cuenta</h2>
4
+ <p>Historial de acciones realizadas en el sistema</p>
2
5
 
3
- <mat-dialog-content>
4
- <p>{{ data.mensaje }}</p>
5
- @if(SolicitaMensaje){
6
- <mat-form-field class="mensaje">
7
- <mat-label>Agregar comentario</mat-label>
8
- <textarea matInput placeholder="Explicación del motivo de aprobación o rechazo" [(ngModel)]="MensajeBrindado"></textarea>
9
- </mat-form-field>
10
- }
11
-
12
- </mat-dialog-content>
6
+ <div class="mensaje-informativo">
7
+ <mat-icon>info</mat-icon>
8
+ <p>Si tiene dudas sobre la información que ve en pantalla, por favor comparta una captura de pantalla al correo
9
+ <strong>soporte@utn.ac.cr</strong>.</p>
10
+ </div>
11
+ </div>
13
12
 
14
- <mat-dialog-actions>
15
- @if(data.textoCerrar){
16
- <button mat-button (click)="onClose()">{{ data.textoCerrar }}</button>
13
+ @if(cargando){
14
+ <mat-progress-bar mode="indeterminate"></mat-progress-bar>
17
15
  }
18
16
 
19
- @if(data.textoAceptar){
20
- <button mat-button (click)="onAccept()" cdkFocusInitial>{{ data.textoAceptar }}</button>
21
- }
22
- </mat-dialog-actions>
17
+ <div class="lista-actividad">
18
+ @if(actividad.length === 0 && !cargando) {
19
+ <p class="sin-datos">No hay actividad reciente.</p>
20
+ }
21
+
22
+ @for (item of actividad; track item.Consecutivo) {
23
+ <div class="actividad-item">
24
+ <div class="item-icono" [ngClass]="{
25
+ 'metodo-get': item.Metodo === 'GET',
26
+ 'metodo-post': item.Metodo === 'POST',
27
+ 'metodo-put': item.Metodo === 'PUT',
28
+ 'metodo-delete': item.Metodo === 'DELETE'
29
+ }">
30
+ <mat-icon [matTooltip]="item.Metodo">{{ getIcon(item.Metodo) }}</mat-icon>
31
+ </div>
32
+ <div class="item-contenido">
33
+ <div class="item-titulo">
34
+ {{ item.URL }}
35
+ @if(esSesionActual(item.Token)){
36
+ <span class="badge-sesion-actual">Sesión actual</span>
37
+ }
38
+ </div>
39
+ <div class="item-detalles">
40
+ <span class="detalle-dispositivo">
41
+ <mat-icon>{{ item.Dispositivo === 'Móvil' ? 'smartphone' : (item.Dispositivo === 'Tablet' ? 'tablet' :
42
+ 'laptop') }}</mat-icon>
43
+ {{ item.Dispositivo }}
44
+ </span>
45
+ <span class="detalle-browser"><mat-icon>public</mat-icon> {{ item.Navegador }}</span>
46
+ <span class="detalle-ip"><mat-icon>lan</mat-icon> {{ item.IP }}</span>
47
+ <span class="detalle-pais"><mat-icon>location_on</mat-icon> {{ item.Pais }}</span>
48
+ </div>
49
+ </div>
50
+ <div class="item-fecha">
51
+ <span>{{ item.Fecha | date: 'dd/MM/yyyy' }}</span>
52
+ <strong>{{ item.Fecha | date: 'HH:mm:ss' }}</strong>
53
+ <button mat-icon-button color="warn" matTooltip="Cerrar esta sesión" (click)="cerrarSesion(item.Token)">
54
+ <mat-icon>logout</mat-icon>
55
+ </button>
56
+ </div>
57
+ </div>
58
+ }
59
+ </div>
60
+ </div>
@@ -1,65 +1,86 @@
1
- import { Component, Inject } from '@angular/core';
1
+ import { Component, OnInit } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { HttpClient } from '@angular/common/http';
4
+ import { MatIconModule } from '@angular/material/icon';
5
+ import { MatTooltipModule } from '@angular/material/tooltip';
6
+ import { MatProgressBarModule } from '@angular/material/progress-bar';
2
7
  import { MatButtonModule } from '@angular/material/button';
3
- import {
4
- MAT_DIALOG_DATA,
5
- MatDialogActions,
6
- MatDialogContent,
7
- MatDialogRef,
8
- MatDialogTitle
9
- } from '@angular/material/dialog';
10
- import { MatFormFieldModule } from '@angular/material/form-field';
11
- import { MatInputModule } from '@angular/material/input';
12
- import { FormsModule } from '@angular/forms';
13
-
14
- export interface DialogData {
15
- titulo: string;
16
- mensaje: string;
17
- textoCerrar?: string;
18
- textoAceptar?: string;
19
- SolicitaMensaje?: string;
20
- onClose?: () => void;
21
- onAccept?: (MensajeBrindado: any) => void;
22
- }
8
+ import { MatDialog } from '@angular/material/dialog';
9
+ import { MensajeConfirmacionComponent } from '../mensaje-confirmacion/mensaje-confirmacion';
10
+ import { DatosGlobalesService } from '../../../datos-globales.service';
23
11
 
24
12
  @Component({
25
- selector: 'app-dialogo',
26
- templateUrl: './mensaje-confirmacion.component.html',
27
- styleUrls: ['./mensaje-confirmacion.component.css'],
28
- imports: [
29
- MatButtonModule,
30
- MatDialogTitle,
31
- MatDialogContent,
32
- MatDialogActions,
33
- MatFormFieldModule,
34
- MatInputModule,
35
- FormsModule
36
- ]
13
+ selector: 'app-gestion-actividad',
14
+ standalone: true,
15
+ imports: [CommonModule, MatIconModule, MatTooltipModule, MatProgressBarModule, MatButtonModule],
16
+ templateUrl: './gestion-actividad.component.html',
17
+ styleUrls: ['./gestion-actividad.component.css']
37
18
  })
38
- export class MensajeConfirmacionComponent {
19
+ export class GestionActividadComponent implements OnInit {
20
+ public actividad: any[] = [];
21
+ public cargando = false;
22
+ private tokenActual = '';
23
+
24
+ constructor(private http: HttpClient, private datosGlobalesService: DatosGlobalesService, private dialog: MatDialog) { }
39
25
 
40
- SolicitaMensaje: string | undefined;
26
+ ngOnInit(): void {
27
+ this.tokenActual = this.datosGlobalesService.ObtenerToken().split(' ')[1] || '';
28
+ this.obtenerActividad();
29
+ }
41
30
 
42
- public MensajeBrindado: string = '';
31
+ obtenerActividad(): void {
32
+ this.cargando = true;
33
+ this.http.get(`${this.datosGlobalesService.ObtenerURL()}misc/ListarActividades`).subscribe({
34
+ next: (datos: any) => {
35
+ this.actividad = datos.body;
36
+ },
37
+ error: (error) => {
38
+ console.error('Error al obtener actividad:', error);
39
+ this.cargando = false;
40
+ },
41
+ complete: () => {
42
+ this.cargando = false;
43
+ }
44
+ });
45
+ }
43
46
 
44
- constructor(
45
- public dialogRef: MatDialogRef<MensajeConfirmacionComponent>,
46
- @Inject(MAT_DIALOG_DATA) public data: DialogData
47
- ) {
48
- this.dialogRef.disableClose = true;
49
- this.SolicitaMensaje = this.data.SolicitaMensaje;
47
+ cerrarSesion(token: string): void {
48
+ this.dialog.open(MensajeConfirmacionComponent, {
49
+ data: {
50
+ titulo: 'Confirmación',
51
+ mensaje: '¿Está seguro de que desea cerrar esta sesión?',
52
+ textoCerrar: 'Cancelar',
53
+ textoAceptar: 'Cerrar sesión',
54
+ onClose: () => { },
55
+ onAccept: () => {
56
+ this.http.delete(`${this.datosGlobalesService.ObtenerURL()}misc/cerrarSesionPorToken/${token}`).subscribe({
57
+ next: () => {
58
+ if (token === this.tokenActual) {
59
+ this.datosGlobalesService.RedirigirALogin();
60
+ } else {
61
+ this.obtenerActividad();
62
+ }
63
+ },
64
+ error: (error) => {
65
+ console.error('Error al cerrar sesión:', error);
66
+ }
67
+ });
68
+ },
69
+ },
70
+ });
50
71
  }
51
72
 
52
- onClose(): void {
53
- if (this.data.onClose) {
54
- this.data.onClose();
55
- }
56
- this.dialogRef.close();
73
+ esSesionActual(token: string): boolean {
74
+ return token === this.tokenActual;
57
75
  }
58
76
 
59
- onAccept(): void {
60
- if (this.data.onAccept) {
61
- this.data.onAccept(this.MensajeBrindado);
77
+ getIcon(metodo: string): string {
78
+ switch (metodo) {
79
+ case 'GET': return 'search';
80
+ case 'POST': return 'add_circle';
81
+ case 'PUT': return 'edit';
82
+ case 'DELETE': return 'delete';
83
+ default: return 'help_outline';
62
84
  }
63
- this.dialogRef.close(this.MensajeBrindado);
64
85
  }
65
86
  }