@noatgnu/cupcake-core 1.3.0 → 1.3.2
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/fesm2022/noatgnu-cupcake-core.mjs +508 -410
- package/fesm2022/noatgnu-cupcake-core.mjs.map +1 -1
- package/index.d.ts +21 -2
- package/package.json +1 -1
|
@@ -2,9 +2,9 @@ import * as i0 from '@angular/core';
|
|
|
2
2
|
import { inject, InjectionToken, Injectable, signal, computed, Component, effect, ChangeDetectorRef, NgModule } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/common/http';
|
|
4
4
|
import { HttpClient, HttpParams, provideHttpClient, withInterceptors, HttpClientModule } from '@angular/common/http';
|
|
5
|
-
import { BehaviorSubject, catchError, throwError, switchMap, filter, take, map, tap, Subject,
|
|
5
|
+
import { BehaviorSubject, catchError, throwError, switchMap, filter, take, map, tap, Subject, timer, EMPTY, interval, debounceTime, distinctUntilChanged } from 'rxjs';
|
|
6
6
|
import { Router, ActivatedRoute, RouterModule } from '@angular/router';
|
|
7
|
-
import { map as map$1, tap as tap$1,
|
|
7
|
+
import { map as map$1, takeUntil, tap as tap$1, switchMap as switchMap$1, filter as filter$1 } from 'rxjs/operators';
|
|
8
8
|
import * as i1$1 from '@angular/forms';
|
|
9
9
|
import { FormBuilder, Validators, ReactiveFormsModule, FormsModule, NonNullableFormBuilder } from '@angular/forms';
|
|
10
10
|
import * as i2 from '@angular/common';
|
|
@@ -1164,6 +1164,407 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
|
|
|
1164
1164
|
}]
|
|
1165
1165
|
}] });
|
|
1166
1166
|
|
|
1167
|
+
const WEBSOCKET_ENDPOINT = new InjectionToken('WEBSOCKET_ENDPOINT', {
|
|
1168
|
+
providedIn: 'root',
|
|
1169
|
+
factory: () => 'notifications'
|
|
1170
|
+
});
|
|
1171
|
+
class WebSocketService {
|
|
1172
|
+
authService;
|
|
1173
|
+
ws = null;
|
|
1174
|
+
config;
|
|
1175
|
+
destroy$ = new Subject();
|
|
1176
|
+
reconnectAttempts = 0;
|
|
1177
|
+
isConnecting = false;
|
|
1178
|
+
connectionState = signal('disconnected', ...(ngDevMode ? [{ debugName: "connectionState" }] : []));
|
|
1179
|
+
lastError = signal(null, ...(ngDevMode ? [{ debugName: "lastError" }] : []));
|
|
1180
|
+
messageSubject = new Subject();
|
|
1181
|
+
connectionSubject = new BehaviorSubject(false);
|
|
1182
|
+
messages$ = this.messageSubject.asObservable();
|
|
1183
|
+
isConnected$ = this.connectionSubject.asObservable();
|
|
1184
|
+
connectionState$ = computed(() => this.connectionState(), ...(ngDevMode ? [{ debugName: "connectionState$" }] : []));
|
|
1185
|
+
lastError$ = computed(() => this.lastError(), ...(ngDevMode ? [{ debugName: "lastError$" }] : []));
|
|
1186
|
+
config_token = inject(CUPCAKE_CORE_CONFIG);
|
|
1187
|
+
endpoint = inject(WEBSOCKET_ENDPOINT, { optional: true }) || 'notifications';
|
|
1188
|
+
constructor(authService) {
|
|
1189
|
+
this.authService = authService;
|
|
1190
|
+
this.config = {
|
|
1191
|
+
url: this.getWebSocketUrl(),
|
|
1192
|
+
endpoint: this.endpoint,
|
|
1193
|
+
reconnectInterval: this.getAdaptiveReconnectInterval(),
|
|
1194
|
+
maxReconnectAttempts: 3
|
|
1195
|
+
};
|
|
1196
|
+
this.authService.isAuthenticated$.subscribe(isAuthenticated => {
|
|
1197
|
+
if (!isAuthenticated && this.ws) {
|
|
1198
|
+
console.log('User logged out - disconnecting WebSocket');
|
|
1199
|
+
this.disconnect();
|
|
1200
|
+
}
|
|
1201
|
+
});
|
|
1202
|
+
this.setupBrowserResourceHandling();
|
|
1203
|
+
}
|
|
1204
|
+
getWebSocketUrl() {
|
|
1205
|
+
const apiUrl = this.config_token.apiUrl;
|
|
1206
|
+
try {
|
|
1207
|
+
const url = new URL(apiUrl);
|
|
1208
|
+
const protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
1209
|
+
const host = url.host;
|
|
1210
|
+
const endpoint = this.config?.endpoint || 'notifications';
|
|
1211
|
+
return `${protocol}//${host}/ws/${endpoint}/`;
|
|
1212
|
+
}
|
|
1213
|
+
catch (error) {
|
|
1214
|
+
console.error('Invalid API URL in cupcake config:', apiUrl);
|
|
1215
|
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
1216
|
+
const host = window.location.host;
|
|
1217
|
+
const endpoint = this.config?.endpoint || 'notifications';
|
|
1218
|
+
return `${protocol}//${host}/ws/${endpoint}/`;
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
connect() {
|
|
1222
|
+
console.log('🔌 WebSocket connect() called');
|
|
1223
|
+
console.log('🔌 Current connection state:', this.connectionState());
|
|
1224
|
+
console.log('🔌 WebSocket URL:', this.config.url);
|
|
1225
|
+
if (this.isConnecting) {
|
|
1226
|
+
console.log('WebSocket connection already in progress');
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
1230
|
+
console.log('WebSocket already connected');
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
if (this.ws && this.ws.readyState !== WebSocket.CLOSED) {
|
|
1234
|
+
console.log('Closing existing WebSocket connection');
|
|
1235
|
+
this.ws.close();
|
|
1236
|
+
this.ws = null;
|
|
1237
|
+
}
|
|
1238
|
+
const token = this.authService.getAccessToken();
|
|
1239
|
+
if (!token) {
|
|
1240
|
+
console.error('❌ Cannot connect WebSocket - no authentication token');
|
|
1241
|
+
this.lastError.set('Authentication required');
|
|
1242
|
+
this.connectionState.set('error');
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
this.isConnecting = true;
|
|
1246
|
+
this.connectionState.set('connecting');
|
|
1247
|
+
this.lastError.set(null);
|
|
1248
|
+
try {
|
|
1249
|
+
const wsUrl = `${this.config.url}?token=${encodeURIComponent(token)}`;
|
|
1250
|
+
console.log('Connecting to WebSocket:', wsUrl.replace(token, '[TOKEN_HIDDEN]'));
|
|
1251
|
+
this.ws = new WebSocket(wsUrl);
|
|
1252
|
+
this.ws.onopen = this.onOpen.bind(this);
|
|
1253
|
+
this.ws.onmessage = this.onMessage.bind(this);
|
|
1254
|
+
this.ws.onerror = this.onError.bind(this);
|
|
1255
|
+
this.ws.onclose = this.onClose.bind(this);
|
|
1256
|
+
const connectionTimeout = setTimeout(() => {
|
|
1257
|
+
if (this.ws?.readyState === WebSocket.CONNECTING) {
|
|
1258
|
+
console.warn('WebSocket connection timeout - closing');
|
|
1259
|
+
this.ws.close();
|
|
1260
|
+
this.lastError.set('Connection timeout');
|
|
1261
|
+
this.connectionState.set('error');
|
|
1262
|
+
this.isConnecting = false;
|
|
1263
|
+
}
|
|
1264
|
+
}, 10000);
|
|
1265
|
+
this.ws.onopen = (event) => {
|
|
1266
|
+
clearTimeout(connectionTimeout);
|
|
1267
|
+
this.onOpen(event);
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
catch (error) {
|
|
1271
|
+
console.error('WebSocket connection error:', error);
|
|
1272
|
+
this.connectionState.set('error');
|
|
1273
|
+
this.lastError.set('Connection failed');
|
|
1274
|
+
this.isConnecting = false;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
disconnect() {
|
|
1278
|
+
this.destroy$.next();
|
|
1279
|
+
this.isConnecting = false;
|
|
1280
|
+
if (this.ws) {
|
|
1281
|
+
this.ws.close(1000, 'User disconnected');
|
|
1282
|
+
this.ws = null;
|
|
1283
|
+
}
|
|
1284
|
+
this.connectionState.set('disconnected');
|
|
1285
|
+
this.connectionSubject.next(false);
|
|
1286
|
+
this.reconnectAttempts = 0;
|
|
1287
|
+
}
|
|
1288
|
+
send(message) {
|
|
1289
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
1290
|
+
this.ws.send(JSON.stringify(message));
|
|
1291
|
+
}
|
|
1292
|
+
else {
|
|
1293
|
+
console.warn('WebSocket not connected, cannot send message:', message);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
subscribe(subscriptionType, options = {}) {
|
|
1297
|
+
this.send({
|
|
1298
|
+
type: 'subscribe',
|
|
1299
|
+
subscription_type: subscriptionType,
|
|
1300
|
+
...options
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
onOpen(event) {
|
|
1304
|
+
console.log('WebSocket connected');
|
|
1305
|
+
this.isConnecting = false;
|
|
1306
|
+
this.connectionState.set('connected');
|
|
1307
|
+
this.connectionSubject.next(true);
|
|
1308
|
+
this.reconnectAttempts = 0;
|
|
1309
|
+
this.lastError.set(null);
|
|
1310
|
+
}
|
|
1311
|
+
onMessage(event) {
|
|
1312
|
+
try {
|
|
1313
|
+
const data = JSON.parse(event.data);
|
|
1314
|
+
console.log('WebSocket message received:', data);
|
|
1315
|
+
this.messageSubject.next(data);
|
|
1316
|
+
}
|
|
1317
|
+
catch (error) {
|
|
1318
|
+
console.error('Error parsing WebSocket message:', error);
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
onError(event) {
|
|
1322
|
+
console.error('WebSocket error:', event);
|
|
1323
|
+
this.isConnecting = false;
|
|
1324
|
+
this.connectionState.set('error');
|
|
1325
|
+
this.lastError.set('Connection error occurred');
|
|
1326
|
+
}
|
|
1327
|
+
onClose(event) {
|
|
1328
|
+
console.log(`WebSocket closed: ${event.code} ${event.reason}`);
|
|
1329
|
+
this.isConnecting = false;
|
|
1330
|
+
this.connectionState.set('disconnected');
|
|
1331
|
+
this.connectionSubject.next(false);
|
|
1332
|
+
if (event.code === 4001) {
|
|
1333
|
+
this.lastError.set('Authentication failed');
|
|
1334
|
+
console.error('WebSocket authentication failed');
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
1337
|
+
else if (event.code === 4003) {
|
|
1338
|
+
this.lastError.set('Insufficient permissions');
|
|
1339
|
+
console.error('WebSocket permission denied');
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
if (event.code !== 1000 && this.reconnectAttempts < (this.config.maxReconnectAttempts || 5)) {
|
|
1343
|
+
this.attemptReconnection();
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
attemptReconnection() {
|
|
1347
|
+
this.reconnectAttempts++;
|
|
1348
|
+
const delay = this.config.reconnectInterval || 5000;
|
|
1349
|
+
console.log(`WebSocket reconnection attempt ${this.reconnectAttempts} in ${delay}ms`);
|
|
1350
|
+
timer(delay).pipe(takeUntil(this.destroy$), tap$1(() => {
|
|
1351
|
+
if (this.reconnectAttempts <= (this.config.maxReconnectAttempts || 5)) {
|
|
1352
|
+
this.connect();
|
|
1353
|
+
}
|
|
1354
|
+
else {
|
|
1355
|
+
console.error('Max WebSocket reconnection attempts reached');
|
|
1356
|
+
this.lastError.set('Connection failed - max attempts reached');
|
|
1357
|
+
}
|
|
1358
|
+
})).subscribe();
|
|
1359
|
+
}
|
|
1360
|
+
filterMessages(type) {
|
|
1361
|
+
return this.messages$.pipe(tap$1(msg => console.log('Filtering message:', msg.type, 'looking for:', type)), switchMap$1(message => message.type === type ? [message] : EMPTY));
|
|
1362
|
+
}
|
|
1363
|
+
getNotifications() {
|
|
1364
|
+
return this.filterMessages('notification');
|
|
1365
|
+
}
|
|
1366
|
+
getSystemNotifications() {
|
|
1367
|
+
return this.filterMessages('system.notification');
|
|
1368
|
+
}
|
|
1369
|
+
reconnectWithNewToken() {
|
|
1370
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
1371
|
+
console.log('Reconnecting WebSocket with new token');
|
|
1372
|
+
this.disconnect();
|
|
1373
|
+
setTimeout(() => this.connect(), 100);
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
shouldConnect() {
|
|
1377
|
+
return this.authService.isAuthenticated() && !!this.authService.getAccessToken();
|
|
1378
|
+
}
|
|
1379
|
+
updateConfig() {
|
|
1380
|
+
this.config.url = this.getWebSocketUrl();
|
|
1381
|
+
console.log('WebSocket URL updated to:', this.config.url);
|
|
1382
|
+
}
|
|
1383
|
+
ngOnDestroy() {
|
|
1384
|
+
this.disconnect();
|
|
1385
|
+
}
|
|
1386
|
+
getAdaptiveReconnectInterval() {
|
|
1387
|
+
const baseInterval = 5000;
|
|
1388
|
+
const tabCount = this.estimateTabCount();
|
|
1389
|
+
if (tabCount > 20) {
|
|
1390
|
+
return baseInterval * 3;
|
|
1391
|
+
}
|
|
1392
|
+
else if (tabCount > 10) {
|
|
1393
|
+
return baseInterval * 2;
|
|
1394
|
+
}
|
|
1395
|
+
return baseInterval;
|
|
1396
|
+
}
|
|
1397
|
+
estimateTabCount() {
|
|
1398
|
+
try {
|
|
1399
|
+
if ('memory' in performance) {
|
|
1400
|
+
const memory = performance.memory;
|
|
1401
|
+
const usedMB = memory.usedJSHeapSize / (1024 * 1024);
|
|
1402
|
+
if (usedMB > 500)
|
|
1403
|
+
return 25;
|
|
1404
|
+
if (usedMB > 300)
|
|
1405
|
+
return 15;
|
|
1406
|
+
if (usedMB > 150)
|
|
1407
|
+
return 10;
|
|
1408
|
+
return 5;
|
|
1409
|
+
}
|
|
1410
|
+
return 10;
|
|
1411
|
+
}
|
|
1412
|
+
catch (error) {
|
|
1413
|
+
return 10;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
canConnectSafely() {
|
|
1417
|
+
try {
|
|
1418
|
+
if ('memory' in performance) {
|
|
1419
|
+
const memory = performance.memory;
|
|
1420
|
+
const usedMB = memory.usedJSHeapSize / (1024 * 1024);
|
|
1421
|
+
const totalMB = memory.totalJSHeapSize / (1024 * 1024);
|
|
1422
|
+
const memoryUsageRatio = usedMB / totalMB;
|
|
1423
|
+
if (memoryUsageRatio > 0.9) {
|
|
1424
|
+
console.warn('High memory usage detected, delaying WebSocket connection');
|
|
1425
|
+
return false;
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
const tabCount = this.estimateTabCount();
|
|
1429
|
+
if (tabCount > 30) {
|
|
1430
|
+
console.warn('Too many tabs detected, may affect WebSocket reliability');
|
|
1431
|
+
return false;
|
|
1432
|
+
}
|
|
1433
|
+
return true;
|
|
1434
|
+
}
|
|
1435
|
+
catch (error) {
|
|
1436
|
+
return true;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
setupBrowserResourceHandling() {
|
|
1440
|
+
document.addEventListener('visibilitychange', () => {
|
|
1441
|
+
if (document.visibilityState === 'visible') {
|
|
1442
|
+
if (this.shouldConnect() && this.connectionState() === 'disconnected') {
|
|
1443
|
+
console.log('Tab became active - attempting WebSocket reconnection');
|
|
1444
|
+
setTimeout(() => this.connect(), 1000);
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
else {
|
|
1448
|
+
const tabCount = this.estimateTabCount();
|
|
1449
|
+
if (tabCount > 15 && this.ws?.readyState === WebSocket.OPEN) {
|
|
1450
|
+
console.log('Tab inactive with high tab count - maintaining connection with reduced activity');
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
});
|
|
1454
|
+
window.addEventListener('beforeunload', () => {
|
|
1455
|
+
if (this.ws) {
|
|
1456
|
+
this.ws.close(1000, 'Page unloading');
|
|
1457
|
+
}
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: WebSocketService, deps: [{ token: AuthService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1461
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: WebSocketService, providedIn: 'root' });
|
|
1462
|
+
}
|
|
1463
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: WebSocketService, decorators: [{
|
|
1464
|
+
type: Injectable,
|
|
1465
|
+
args: [{
|
|
1466
|
+
providedIn: 'root'
|
|
1467
|
+
}]
|
|
1468
|
+
}], ctorParameters: () => [{ type: AuthService }] });
|
|
1469
|
+
|
|
1470
|
+
class AsyncTaskMonitorService extends BaseApiService {
|
|
1471
|
+
destroy$ = new Subject();
|
|
1472
|
+
tasksSubject = new BehaviorSubject([]);
|
|
1473
|
+
isSubscribed = false;
|
|
1474
|
+
websocket = inject(WebSocketService);
|
|
1475
|
+
tasks$ = this.tasksSubject.asObservable();
|
|
1476
|
+
activeTasks$ = this.tasks$.pipe(map$1(tasks => {
|
|
1477
|
+
const taskArray = Array.isArray(tasks) ? tasks : [];
|
|
1478
|
+
const active = taskArray.filter(task => task.status === TaskStatus.QUEUED || task.status === TaskStatus.STARTED);
|
|
1479
|
+
console.log('AsyncTaskMonitorService: activeTasks$ emitting:', active.length, 'active tasks out of', taskArray.length, 'total');
|
|
1480
|
+
return active;
|
|
1481
|
+
}));
|
|
1482
|
+
ngOnDestroy() {
|
|
1483
|
+
this.destroy$.next();
|
|
1484
|
+
this.destroy$.complete();
|
|
1485
|
+
}
|
|
1486
|
+
startRealtimeUpdates() {
|
|
1487
|
+
if (this.isSubscribed) {
|
|
1488
|
+
console.log('AsyncTaskMonitorService: Already subscribed to real-time updates');
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
console.log('AsyncTaskMonitorService: Starting real-time updates');
|
|
1492
|
+
this.isSubscribed = true;
|
|
1493
|
+
if (!this.websocket.connectionState$() || this.websocket.connectionState$() === 'disconnected') {
|
|
1494
|
+
console.log('AsyncTaskMonitorService: WebSocket not connected, connecting now...');
|
|
1495
|
+
this.websocket.connect();
|
|
1496
|
+
}
|
|
1497
|
+
else {
|
|
1498
|
+
console.log('AsyncTaskMonitorService: WebSocket already connected');
|
|
1499
|
+
}
|
|
1500
|
+
this.websocket.isConnected$.pipe(takeUntil(this.destroy$), filter$1(connected => connected)).subscribe(() => {
|
|
1501
|
+
console.log('AsyncTaskMonitorService: WebSocket connected, subscribing to async_task_updates');
|
|
1502
|
+
this.websocket.subscribe('async_task_updates');
|
|
1503
|
+
});
|
|
1504
|
+
this.websocket.filterMessages('async_task.update').pipe(takeUntil(this.destroy$)).subscribe((message) => {
|
|
1505
|
+
console.log('AsyncTaskMonitorService: Received async_task.update message:', message);
|
|
1506
|
+
this.handleTaskUpdate(message);
|
|
1507
|
+
});
|
|
1508
|
+
this.loadAllTasks();
|
|
1509
|
+
}
|
|
1510
|
+
stopRealtimeUpdates() {
|
|
1511
|
+
console.log('AsyncTaskMonitorService: Stopping real-time updates');
|
|
1512
|
+
this.isSubscribed = false;
|
|
1513
|
+
this.destroy$.next();
|
|
1514
|
+
}
|
|
1515
|
+
loadAllTasks() {
|
|
1516
|
+
const httpParams = this.buildHttpParams({ limit: 100 });
|
|
1517
|
+
this.get(`${this.apiUrl}/async-tasks/`, { params: httpParams }).subscribe({
|
|
1518
|
+
next: (response) => {
|
|
1519
|
+
const taskArray = Array.isArray(response.results) ? response.results : [];
|
|
1520
|
+
console.log('AsyncTaskMonitorService: Loaded tasks from server:', taskArray.length);
|
|
1521
|
+
this.tasksSubject.next(taskArray);
|
|
1522
|
+
},
|
|
1523
|
+
error: (error) => {
|
|
1524
|
+
console.error('AsyncTaskMonitorService: Error loading tasks:', error);
|
|
1525
|
+
}
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1528
|
+
cancelTask(taskId) {
|
|
1529
|
+
return this.delete(`${this.apiUrl}/async-tasks/${taskId}/cancel/`);
|
|
1530
|
+
}
|
|
1531
|
+
handleTaskUpdate(message) {
|
|
1532
|
+
console.log('AsyncTaskMonitorService: handleTaskUpdate called with message:', message);
|
|
1533
|
+
const taskId = message.task_id;
|
|
1534
|
+
const currentTasks = this.tasksSubject.value;
|
|
1535
|
+
console.log('AsyncTaskMonitorService: Current tasks in subject:', currentTasks.length);
|
|
1536
|
+
const existingTaskIndex = currentTasks.findIndex(t => t.id === taskId);
|
|
1537
|
+
console.log('AsyncTaskMonitorService: Existing task index:', existingTaskIndex);
|
|
1538
|
+
if (existingTaskIndex >= 0) {
|
|
1539
|
+
const existingTask = currentTasks[existingTaskIndex];
|
|
1540
|
+
const updatedTask = {
|
|
1541
|
+
...existingTask,
|
|
1542
|
+
status: message.status,
|
|
1543
|
+
progressPercentage: message.progress_percentage || existingTask.progressPercentage,
|
|
1544
|
+
progressDescription: message.progress_description || existingTask.progressDescription,
|
|
1545
|
+
errorMessage: message.error_message || existingTask.errorMessage,
|
|
1546
|
+
result: message.result || existingTask.result,
|
|
1547
|
+
};
|
|
1548
|
+
const updatedTasks = [...currentTasks];
|
|
1549
|
+
updatedTasks[existingTaskIndex] = updatedTask;
|
|
1550
|
+
console.log('AsyncTaskMonitorService: Emitting updated tasks:', updatedTasks.length, 'active tasks:', updatedTasks.filter(t => t.status === TaskStatus.QUEUED || t.status === TaskStatus.STARTED).length);
|
|
1551
|
+
this.tasksSubject.next(updatedTasks);
|
|
1552
|
+
}
|
|
1553
|
+
else {
|
|
1554
|
+
console.log('AsyncTaskMonitorService: Task not found in current list, fetching all tasks');
|
|
1555
|
+
this.loadAllTasks();
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncTaskMonitorService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
1559
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncTaskMonitorService, providedIn: 'root' });
|
|
1560
|
+
}
|
|
1561
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncTaskMonitorService, decorators: [{
|
|
1562
|
+
type: Injectable,
|
|
1563
|
+
args: [{
|
|
1564
|
+
providedIn: 'root'
|
|
1565
|
+
}]
|
|
1566
|
+
}] });
|
|
1567
|
+
|
|
1167
1568
|
class ToastService {
|
|
1168
1569
|
toastsSignal = signal([], ...(ngDevMode ? [{ debugName: "toastsSignal" }] : []));
|
|
1169
1570
|
toasts = this.toastsSignal.asReadonly();
|
|
@@ -1401,443 +1802,140 @@ class ThemeService {
|
|
|
1401
1802
|
this.mediaQuery.addEventListener('change', () => {
|
|
1402
1803
|
if (this._theme() === 'auto') {
|
|
1403
1804
|
this.updateDocumentTheme();
|
|
1404
|
-
}
|
|
1405
|
-
});
|
|
1406
|
-
this.updateDocumentTheme();
|
|
1407
|
-
}
|
|
1408
|
-
setTheme(theme) {
|
|
1409
|
-
this._theme.set(theme);
|
|
1410
|
-
localStorage.setItem(this.THEME_KEY, theme);
|
|
1411
|
-
this.updateDocumentTheme();
|
|
1412
|
-
}
|
|
1413
|
-
toggleTheme() {
|
|
1414
|
-
const current = this._theme();
|
|
1415
|
-
if (current === 'light') {
|
|
1416
|
-
this.setTheme('dark');
|
|
1417
|
-
}
|
|
1418
|
-
else if (current === 'dark') {
|
|
1419
|
-
this.setTheme('auto');
|
|
1420
|
-
}
|
|
1421
|
-
else {
|
|
1422
|
-
this.setTheme('light');
|
|
1423
|
-
}
|
|
1424
|
-
}
|
|
1425
|
-
loadStoredTheme() {
|
|
1426
|
-
const stored = localStorage.getItem(this.THEME_KEY);
|
|
1427
|
-
return stored && ['light', 'dark', 'auto'].includes(stored) ? stored : 'auto';
|
|
1428
|
-
}
|
|
1429
|
-
updateDocumentTheme() {
|
|
1430
|
-
const isDark = this.isDark();
|
|
1431
|
-
document.documentElement.setAttribute('data-bs-theme', isDark ? 'dark' : 'light');
|
|
1432
|
-
document.documentElement.classList.toggle('dark-mode', isDark);
|
|
1433
|
-
}
|
|
1434
|
-
getThemeIcon() {
|
|
1435
|
-
const theme = this._theme();
|
|
1436
|
-
switch (theme) {
|
|
1437
|
-
case 'light': return 'bi-sun-fill';
|
|
1438
|
-
case 'dark': return 'bi-moon-fill';
|
|
1439
|
-
case 'auto': return 'bi-circle-half';
|
|
1440
|
-
default: return 'bi-circle-half';
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1443
|
-
getThemeLabel() {
|
|
1444
|
-
const theme = this._theme();
|
|
1445
|
-
switch (theme) {
|
|
1446
|
-
case 'light': return 'Light Mode';
|
|
1447
|
-
case 'dark': return 'Dark Mode';
|
|
1448
|
-
case 'auto': return 'Auto Mode';
|
|
1449
|
-
default: return 'Auto Mode';
|
|
1450
|
-
}
|
|
1451
|
-
}
|
|
1452
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1453
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ThemeService, providedIn: 'root' });
|
|
1454
|
-
}
|
|
1455
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ThemeService, decorators: [{
|
|
1456
|
-
type: Injectable,
|
|
1457
|
-
args: [{
|
|
1458
|
-
providedIn: 'root'
|
|
1459
|
-
}]
|
|
1460
|
-
}], ctorParameters: () => [] });
|
|
1461
|
-
|
|
1462
|
-
class UserManagementService {
|
|
1463
|
-
apiService = inject(ApiService);
|
|
1464
|
-
authService = inject(AuthService);
|
|
1465
|
-
usersSubject = new BehaviorSubject([]);
|
|
1466
|
-
users$ = this.usersSubject.asObservable();
|
|
1467
|
-
totalUsersSubject = new BehaviorSubject(0);
|
|
1468
|
-
totalUsers$ = this.totalUsersSubject.asObservable();
|
|
1469
|
-
constructor() { }
|
|
1470
|
-
getUserProfile() {
|
|
1471
|
-
return this.apiService.getUserProfile();
|
|
1472
|
-
}
|
|
1473
|
-
updateProfile(profileData) {
|
|
1474
|
-
return this.apiService.updateProfile(profileData);
|
|
1475
|
-
}
|
|
1476
|
-
changePassword(passwordData) {
|
|
1477
|
-
return this.apiService.changePassword(passwordData);
|
|
1478
|
-
}
|
|
1479
|
-
requestEmailChange(emailData) {
|
|
1480
|
-
return this.apiService.requestEmailChange(emailData);
|
|
1481
|
-
}
|
|
1482
|
-
getUsers(params) {
|
|
1483
|
-
return this.apiService.getUsers(params);
|
|
1484
|
-
}
|
|
1485
|
-
getUser(id) {
|
|
1486
|
-
return this.apiService.getUser(id);
|
|
1487
|
-
}
|
|
1488
|
-
createUser(userData) {
|
|
1489
|
-
return this.apiService.createUser(userData);
|
|
1490
|
-
}
|
|
1491
|
-
updateUser(id, userData) {
|
|
1492
|
-
return this.apiService.updateUser(id, userData);
|
|
1493
|
-
}
|
|
1494
|
-
deleteUser(id) {
|
|
1495
|
-
return this.apiService.deleteUser(id);
|
|
1496
|
-
}
|
|
1497
|
-
resetUserPassword(userId, passwordData) {
|
|
1498
|
-
return this.apiService.resetUserPassword(userId, passwordData);
|
|
1499
|
-
}
|
|
1500
|
-
getUserDisplayName(user) {
|
|
1501
|
-
if (!user)
|
|
1502
|
-
return '';
|
|
1503
|
-
if (user.firstName || user.lastName) {
|
|
1504
|
-
return `${user.firstName} ${user.lastName}`.trim();
|
|
1505
|
-
}
|
|
1506
|
-
return user.username || user.email || 'User';
|
|
1507
|
-
}
|
|
1508
|
-
formatDate(dateString) {
|
|
1509
|
-
return dateString ? new Date(dateString).toLocaleDateString() : 'Never';
|
|
1510
|
-
}
|
|
1511
|
-
isCurrentUserAdmin() {
|
|
1512
|
-
const user = this.authService.getCurrentUser();
|
|
1513
|
-
return user?.isStaff || false;
|
|
1514
|
-
}
|
|
1515
|
-
isCurrentUserSuperuser() {
|
|
1516
|
-
const user = this.authService.getCurrentUser();
|
|
1517
|
-
return user?.isSuperuser || false;
|
|
1518
|
-
}
|
|
1519
|
-
updateUsersState(users, total) {
|
|
1520
|
-
this.usersSubject.next(users);
|
|
1521
|
-
this.totalUsersSubject.next(total);
|
|
1522
|
-
}
|
|
1523
|
-
getCurrentUsers() {
|
|
1524
|
-
return this.usersSubject.getValue();
|
|
1525
|
-
}
|
|
1526
|
-
getCurrentTotalUsers() {
|
|
1527
|
-
return this.totalUsersSubject.getValue();
|
|
1528
|
-
}
|
|
1529
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: UserManagementService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1530
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: UserManagementService, providedIn: 'root' });
|
|
1531
|
-
}
|
|
1532
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: UserManagementService, decorators: [{
|
|
1533
|
-
type: Injectable,
|
|
1534
|
-
args: [{
|
|
1535
|
-
providedIn: 'root'
|
|
1536
|
-
}]
|
|
1537
|
-
}], ctorParameters: () => [] });
|
|
1538
|
-
|
|
1539
|
-
const WEBSOCKET_ENDPOINT = new InjectionToken('WEBSOCKET_ENDPOINT', {
|
|
1540
|
-
providedIn: 'root',
|
|
1541
|
-
factory: () => 'notifications'
|
|
1542
|
-
});
|
|
1543
|
-
class WebSocketService {
|
|
1544
|
-
authService;
|
|
1545
|
-
ws = null;
|
|
1546
|
-
config;
|
|
1547
|
-
destroy$ = new Subject();
|
|
1548
|
-
reconnectAttempts = 0;
|
|
1549
|
-
isConnecting = false;
|
|
1550
|
-
connectionState = signal('disconnected', ...(ngDevMode ? [{ debugName: "connectionState" }] : []));
|
|
1551
|
-
lastError = signal(null, ...(ngDevMode ? [{ debugName: "lastError" }] : []));
|
|
1552
|
-
messageSubject = new Subject();
|
|
1553
|
-
connectionSubject = new BehaviorSubject(false);
|
|
1554
|
-
messages$ = this.messageSubject.asObservable();
|
|
1555
|
-
isConnected$ = this.connectionSubject.asObservable();
|
|
1556
|
-
connectionState$ = computed(() => this.connectionState(), ...(ngDevMode ? [{ debugName: "connectionState$" }] : []));
|
|
1557
|
-
lastError$ = computed(() => this.lastError(), ...(ngDevMode ? [{ debugName: "lastError$" }] : []));
|
|
1558
|
-
config_token = inject(CUPCAKE_CORE_CONFIG);
|
|
1559
|
-
endpoint = inject(WEBSOCKET_ENDPOINT, { optional: true }) || 'notifications';
|
|
1560
|
-
constructor(authService) {
|
|
1561
|
-
this.authService = authService;
|
|
1562
|
-
this.config = {
|
|
1563
|
-
url: this.getWebSocketUrl(),
|
|
1564
|
-
endpoint: this.endpoint,
|
|
1565
|
-
reconnectInterval: this.getAdaptiveReconnectInterval(),
|
|
1566
|
-
maxReconnectAttempts: 3
|
|
1567
|
-
};
|
|
1568
|
-
this.authService.isAuthenticated$.subscribe(isAuthenticated => {
|
|
1569
|
-
if (!isAuthenticated && this.ws) {
|
|
1570
|
-
console.log('User logged out - disconnecting WebSocket');
|
|
1571
|
-
this.disconnect();
|
|
1572
|
-
}
|
|
1573
|
-
});
|
|
1574
|
-
this.setupBrowserResourceHandling();
|
|
1575
|
-
}
|
|
1576
|
-
getWebSocketUrl() {
|
|
1577
|
-
const apiUrl = this.config_token.apiUrl;
|
|
1578
|
-
try {
|
|
1579
|
-
const url = new URL(apiUrl);
|
|
1580
|
-
const protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
1581
|
-
const host = url.host;
|
|
1582
|
-
const endpoint = this.config?.endpoint || 'notifications';
|
|
1583
|
-
return `${protocol}//${host}/ws/${endpoint}/`;
|
|
1584
|
-
}
|
|
1585
|
-
catch (error) {
|
|
1586
|
-
console.error('Invalid API URL in cupcake config:', apiUrl);
|
|
1587
|
-
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
1588
|
-
const host = window.location.host;
|
|
1589
|
-
const endpoint = this.config?.endpoint || 'notifications';
|
|
1590
|
-
return `${protocol}//${host}/ws/${endpoint}/`;
|
|
1591
|
-
}
|
|
1592
|
-
}
|
|
1593
|
-
connect() {
|
|
1594
|
-
console.log('🔌 WebSocket connect() called');
|
|
1595
|
-
console.log('🔌 Current connection state:', this.connectionState());
|
|
1596
|
-
console.log('🔌 WebSocket URL:', this.config.url);
|
|
1597
|
-
if (this.isConnecting) {
|
|
1598
|
-
console.log('WebSocket connection already in progress');
|
|
1599
|
-
return;
|
|
1600
|
-
}
|
|
1601
|
-
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
1602
|
-
console.log('WebSocket already connected');
|
|
1603
|
-
return;
|
|
1604
|
-
}
|
|
1605
|
-
if (this.ws && this.ws.readyState !== WebSocket.CLOSED) {
|
|
1606
|
-
console.log('Closing existing WebSocket connection');
|
|
1607
|
-
this.ws.close();
|
|
1608
|
-
this.ws = null;
|
|
1609
|
-
}
|
|
1610
|
-
const token = this.authService.getAccessToken();
|
|
1611
|
-
if (!token) {
|
|
1612
|
-
console.error('❌ Cannot connect WebSocket - no authentication token');
|
|
1613
|
-
this.lastError.set('Authentication required');
|
|
1614
|
-
this.connectionState.set('error');
|
|
1615
|
-
return;
|
|
1616
|
-
}
|
|
1617
|
-
this.isConnecting = true;
|
|
1618
|
-
this.connectionState.set('connecting');
|
|
1619
|
-
this.lastError.set(null);
|
|
1620
|
-
try {
|
|
1621
|
-
const wsUrl = `${this.config.url}?token=${encodeURIComponent(token)}`;
|
|
1622
|
-
console.log('Connecting to WebSocket:', wsUrl.replace(token, '[TOKEN_HIDDEN]'));
|
|
1623
|
-
this.ws = new WebSocket(wsUrl);
|
|
1624
|
-
this.ws.onopen = this.onOpen.bind(this);
|
|
1625
|
-
this.ws.onmessage = this.onMessage.bind(this);
|
|
1626
|
-
this.ws.onerror = this.onError.bind(this);
|
|
1627
|
-
this.ws.onclose = this.onClose.bind(this);
|
|
1628
|
-
const connectionTimeout = setTimeout(() => {
|
|
1629
|
-
if (this.ws?.readyState === WebSocket.CONNECTING) {
|
|
1630
|
-
console.warn('WebSocket connection timeout - closing');
|
|
1631
|
-
this.ws.close();
|
|
1632
|
-
this.lastError.set('Connection timeout');
|
|
1633
|
-
this.connectionState.set('error');
|
|
1634
|
-
this.isConnecting = false;
|
|
1635
|
-
}
|
|
1636
|
-
}, 10000);
|
|
1637
|
-
this.ws.onopen = (event) => {
|
|
1638
|
-
clearTimeout(connectionTimeout);
|
|
1639
|
-
this.onOpen(event);
|
|
1640
|
-
};
|
|
1641
|
-
}
|
|
1642
|
-
catch (error) {
|
|
1643
|
-
console.error('WebSocket connection error:', error);
|
|
1644
|
-
this.connectionState.set('error');
|
|
1645
|
-
this.lastError.set('Connection failed');
|
|
1646
|
-
this.isConnecting = false;
|
|
1647
|
-
}
|
|
1648
|
-
}
|
|
1649
|
-
disconnect() {
|
|
1650
|
-
this.destroy$.next();
|
|
1651
|
-
this.isConnecting = false;
|
|
1652
|
-
if (this.ws) {
|
|
1653
|
-
this.ws.close(1000, 'User disconnected');
|
|
1654
|
-
this.ws = null;
|
|
1655
|
-
}
|
|
1656
|
-
this.connectionState.set('disconnected');
|
|
1657
|
-
this.connectionSubject.next(false);
|
|
1658
|
-
this.reconnectAttempts = 0;
|
|
1805
|
+
}
|
|
1806
|
+
});
|
|
1807
|
+
this.updateDocumentTheme();
|
|
1659
1808
|
}
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1809
|
+
setTheme(theme) {
|
|
1810
|
+
this._theme.set(theme);
|
|
1811
|
+
localStorage.setItem(this.THEME_KEY, theme);
|
|
1812
|
+
this.updateDocumentTheme();
|
|
1813
|
+
}
|
|
1814
|
+
toggleTheme() {
|
|
1815
|
+
const current = this._theme();
|
|
1816
|
+
if (current === 'light') {
|
|
1817
|
+
this.setTheme('dark');
|
|
1818
|
+
}
|
|
1819
|
+
else if (current === 'dark') {
|
|
1820
|
+
this.setTheme('auto');
|
|
1663
1821
|
}
|
|
1664
1822
|
else {
|
|
1665
|
-
|
|
1823
|
+
this.setTheme('light');
|
|
1666
1824
|
}
|
|
1667
1825
|
}
|
|
1668
|
-
|
|
1669
|
-
this.
|
|
1670
|
-
|
|
1671
|
-
subscription_type: subscriptionType,
|
|
1672
|
-
...options
|
|
1673
|
-
});
|
|
1826
|
+
loadStoredTheme() {
|
|
1827
|
+
const stored = localStorage.getItem(this.THEME_KEY);
|
|
1828
|
+
return stored && ['light', 'dark', 'auto'].includes(stored) ? stored : 'auto';
|
|
1674
1829
|
}
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
this.connectionSubject.next(true);
|
|
1680
|
-
this.reconnectAttempts = 0;
|
|
1681
|
-
this.lastError.set(null);
|
|
1830
|
+
updateDocumentTheme() {
|
|
1831
|
+
const isDark = this.isDark();
|
|
1832
|
+
document.documentElement.setAttribute('data-bs-theme', isDark ? 'dark' : 'light');
|
|
1833
|
+
document.documentElement.classList.toggle('dark-mode', isDark);
|
|
1682
1834
|
}
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1835
|
+
getThemeIcon() {
|
|
1836
|
+
const theme = this._theme();
|
|
1837
|
+
switch (theme) {
|
|
1838
|
+
case 'light': return 'bi-sun-fill';
|
|
1839
|
+
case 'dark': return 'bi-moon-fill';
|
|
1840
|
+
case 'auto': return 'bi-circle-half';
|
|
1841
|
+
default: return 'bi-circle-half';
|
|
1688
1842
|
}
|
|
1689
|
-
|
|
1690
|
-
|
|
1843
|
+
}
|
|
1844
|
+
getThemeLabel() {
|
|
1845
|
+
const theme = this._theme();
|
|
1846
|
+
switch (theme) {
|
|
1847
|
+
case 'light': return 'Light Mode';
|
|
1848
|
+
case 'dark': return 'Dark Mode';
|
|
1849
|
+
case 'auto': return 'Auto Mode';
|
|
1850
|
+
default: return 'Auto Mode';
|
|
1691
1851
|
}
|
|
1692
1852
|
}
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1853
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1854
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ThemeService, providedIn: 'root' });
|
|
1855
|
+
}
|
|
1856
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ThemeService, decorators: [{
|
|
1857
|
+
type: Injectable,
|
|
1858
|
+
args: [{
|
|
1859
|
+
providedIn: 'root'
|
|
1860
|
+
}]
|
|
1861
|
+
}], ctorParameters: () => [] });
|
|
1862
|
+
|
|
1863
|
+
class UserManagementService {
|
|
1864
|
+
apiService = inject(ApiService);
|
|
1865
|
+
authService = inject(AuthService);
|
|
1866
|
+
usersSubject = new BehaviorSubject([]);
|
|
1867
|
+
users$ = this.usersSubject.asObservable();
|
|
1868
|
+
totalUsersSubject = new BehaviorSubject(0);
|
|
1869
|
+
totalUsers$ = this.totalUsersSubject.asObservable();
|
|
1870
|
+
constructor() { }
|
|
1871
|
+
getUserProfile() {
|
|
1872
|
+
return this.apiService.getUserProfile();
|
|
1698
1873
|
}
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
this.isConnecting = false;
|
|
1702
|
-
this.connectionState.set('disconnected');
|
|
1703
|
-
this.connectionSubject.next(false);
|
|
1704
|
-
if (event.code === 4001) {
|
|
1705
|
-
this.lastError.set('Authentication failed');
|
|
1706
|
-
console.error('WebSocket authentication failed');
|
|
1707
|
-
return;
|
|
1708
|
-
}
|
|
1709
|
-
else if (event.code === 4003) {
|
|
1710
|
-
this.lastError.set('Insufficient permissions');
|
|
1711
|
-
console.error('WebSocket permission denied');
|
|
1712
|
-
return;
|
|
1713
|
-
}
|
|
1714
|
-
if (event.code !== 1000 && this.reconnectAttempts < (this.config.maxReconnectAttempts || 5)) {
|
|
1715
|
-
this.attemptReconnection();
|
|
1716
|
-
}
|
|
1874
|
+
updateProfile(profileData) {
|
|
1875
|
+
return this.apiService.updateProfile(profileData);
|
|
1717
1876
|
}
|
|
1718
|
-
|
|
1719
|
-
this.
|
|
1720
|
-
const delay = this.config.reconnectInterval || 5000;
|
|
1721
|
-
console.log(`WebSocket reconnection attempt ${this.reconnectAttempts} in ${delay}ms`);
|
|
1722
|
-
timer(delay).pipe(takeUntil(this.destroy$), tap$1(() => {
|
|
1723
|
-
if (this.reconnectAttempts <= (this.config.maxReconnectAttempts || 5)) {
|
|
1724
|
-
this.connect();
|
|
1725
|
-
}
|
|
1726
|
-
else {
|
|
1727
|
-
console.error('Max WebSocket reconnection attempts reached');
|
|
1728
|
-
this.lastError.set('Connection failed - max attempts reached');
|
|
1729
|
-
}
|
|
1730
|
-
})).subscribe();
|
|
1877
|
+
changePassword(passwordData) {
|
|
1878
|
+
return this.apiService.changePassword(passwordData);
|
|
1731
1879
|
}
|
|
1732
|
-
|
|
1733
|
-
return this.
|
|
1880
|
+
requestEmailChange(emailData) {
|
|
1881
|
+
return this.apiService.requestEmailChange(emailData);
|
|
1734
1882
|
}
|
|
1735
|
-
|
|
1736
|
-
return this.
|
|
1883
|
+
getUsers(params) {
|
|
1884
|
+
return this.apiService.getUsers(params);
|
|
1737
1885
|
}
|
|
1738
|
-
|
|
1739
|
-
return this.
|
|
1886
|
+
getUser(id) {
|
|
1887
|
+
return this.apiService.getUser(id);
|
|
1740
1888
|
}
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
console.log('Reconnecting WebSocket with new token');
|
|
1744
|
-
this.disconnect();
|
|
1745
|
-
setTimeout(() => this.connect(), 100);
|
|
1746
|
-
}
|
|
1889
|
+
createUser(userData) {
|
|
1890
|
+
return this.apiService.createUser(userData);
|
|
1747
1891
|
}
|
|
1748
|
-
|
|
1749
|
-
return this.
|
|
1892
|
+
updateUser(id, userData) {
|
|
1893
|
+
return this.apiService.updateUser(id, userData);
|
|
1750
1894
|
}
|
|
1751
|
-
|
|
1752
|
-
this.
|
|
1753
|
-
console.log('WebSocket URL updated to:', this.config.url);
|
|
1895
|
+
deleteUser(id) {
|
|
1896
|
+
return this.apiService.deleteUser(id);
|
|
1754
1897
|
}
|
|
1755
|
-
|
|
1756
|
-
this.
|
|
1898
|
+
resetUserPassword(userId, passwordData) {
|
|
1899
|
+
return this.apiService.resetUserPassword(userId, passwordData);
|
|
1757
1900
|
}
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
if (
|
|
1762
|
-
return
|
|
1763
|
-
}
|
|
1764
|
-
else if (tabCount > 10) {
|
|
1765
|
-
return baseInterval * 2;
|
|
1901
|
+
getUserDisplayName(user) {
|
|
1902
|
+
if (!user)
|
|
1903
|
+
return '';
|
|
1904
|
+
if (user.firstName || user.lastName) {
|
|
1905
|
+
return `${user.firstName} ${user.lastName}`.trim();
|
|
1766
1906
|
}
|
|
1767
|
-
return
|
|
1907
|
+
return user.username || user.email || 'User';
|
|
1768
1908
|
}
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
if ('memory' in performance) {
|
|
1772
|
-
const memory = performance.memory;
|
|
1773
|
-
const usedMB = memory.usedJSHeapSize / (1024 * 1024);
|
|
1774
|
-
if (usedMB > 500)
|
|
1775
|
-
return 25;
|
|
1776
|
-
if (usedMB > 300)
|
|
1777
|
-
return 15;
|
|
1778
|
-
if (usedMB > 150)
|
|
1779
|
-
return 10;
|
|
1780
|
-
return 5;
|
|
1781
|
-
}
|
|
1782
|
-
return 10;
|
|
1783
|
-
}
|
|
1784
|
-
catch (error) {
|
|
1785
|
-
return 10;
|
|
1786
|
-
}
|
|
1909
|
+
formatDate(dateString) {
|
|
1910
|
+
return dateString ? new Date(dateString).toLocaleDateString() : 'Never';
|
|
1787
1911
|
}
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
const memory = performance.memory;
|
|
1792
|
-
const usedMB = memory.usedJSHeapSize / (1024 * 1024);
|
|
1793
|
-
const totalMB = memory.totalJSHeapSize / (1024 * 1024);
|
|
1794
|
-
const memoryUsageRatio = usedMB / totalMB;
|
|
1795
|
-
if (memoryUsageRatio > 0.9) {
|
|
1796
|
-
console.warn('High memory usage detected, delaying WebSocket connection');
|
|
1797
|
-
return false;
|
|
1798
|
-
}
|
|
1799
|
-
}
|
|
1800
|
-
const tabCount = this.estimateTabCount();
|
|
1801
|
-
if (tabCount > 30) {
|
|
1802
|
-
console.warn('Too many tabs detected, may affect WebSocket reliability');
|
|
1803
|
-
return false;
|
|
1804
|
-
}
|
|
1805
|
-
return true;
|
|
1806
|
-
}
|
|
1807
|
-
catch (error) {
|
|
1808
|
-
return true;
|
|
1809
|
-
}
|
|
1912
|
+
isCurrentUserAdmin() {
|
|
1913
|
+
const user = this.authService.getCurrentUser();
|
|
1914
|
+
return user?.isStaff || false;
|
|
1810
1915
|
}
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
if (this.shouldConnect() && this.connectionState() === 'disconnected') {
|
|
1815
|
-
console.log('Tab became active - attempting WebSocket reconnection');
|
|
1816
|
-
setTimeout(() => this.connect(), 1000);
|
|
1817
|
-
}
|
|
1818
|
-
}
|
|
1819
|
-
else {
|
|
1820
|
-
const tabCount = this.estimateTabCount();
|
|
1821
|
-
if (tabCount > 15 && this.ws?.readyState === WebSocket.OPEN) {
|
|
1822
|
-
console.log('Tab inactive with high tab count - maintaining connection with reduced activity');
|
|
1823
|
-
}
|
|
1824
|
-
}
|
|
1825
|
-
});
|
|
1826
|
-
window.addEventListener('beforeunload', () => {
|
|
1827
|
-
if (this.ws) {
|
|
1828
|
-
this.ws.close(1000, 'Page unloading');
|
|
1829
|
-
}
|
|
1830
|
-
});
|
|
1916
|
+
isCurrentUserSuperuser() {
|
|
1917
|
+
const user = this.authService.getCurrentUser();
|
|
1918
|
+
return user?.isSuperuser || false;
|
|
1831
1919
|
}
|
|
1832
|
-
|
|
1833
|
-
|
|
1920
|
+
updateUsersState(users, total) {
|
|
1921
|
+
this.usersSubject.next(users);
|
|
1922
|
+
this.totalUsersSubject.next(total);
|
|
1923
|
+
}
|
|
1924
|
+
getCurrentUsers() {
|
|
1925
|
+
return this.usersSubject.getValue();
|
|
1926
|
+
}
|
|
1927
|
+
getCurrentTotalUsers() {
|
|
1928
|
+
return this.totalUsersSubject.getValue();
|
|
1929
|
+
}
|
|
1930
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: UserManagementService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1931
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: UserManagementService, providedIn: 'root' });
|
|
1834
1932
|
}
|
|
1835
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type:
|
|
1933
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: UserManagementService, decorators: [{
|
|
1836
1934
|
type: Injectable,
|
|
1837
1935
|
args: [{
|
|
1838
1936
|
providedIn: 'root'
|
|
1839
1937
|
}]
|
|
1840
|
-
}], ctorParameters: () => [
|
|
1938
|
+
}], ctorParameters: () => [] });
|
|
1841
1939
|
|
|
1842
1940
|
const WEBSOCKET_ENDPOINTS = new InjectionToken('WEBSOCKET_ENDPOINTS');
|
|
1843
1941
|
class WebSocketEndpoints {
|
|
@@ -3503,5 +3601,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
|
|
|
3503
3601
|
* Generated bundle index. Do not edit.
|
|
3504
3602
|
*/
|
|
3505
3603
|
|
|
3506
|
-
export { AnnotationType, ApiService, AuthService, BaseApiService, CUPCAKE_CORE_CONFIG, CupcakeCoreModule, InvitationStatus, InvitationStatusLabels, LabGroupService, LabGroupsComponent, LoginComponent, NotificationService, PoweredByFooterComponent, RegisterComponent, ResourceRole, ResourceRoleLabels, ResourceService, ResourceType, ResourceTypeLabels, ResourceVisibility, ResourceVisibilityLabels, SiteConfigComponent, SiteConfigService, TASK_STATUS_COLORS, TASK_STATUS_LABELS, TASK_TYPE_LABELS, TaskStatus, TaskType, ThemeService, ToastContainerComponent, ToastService, UserManagementComponent, UserManagementService, UserProfileComponent, WEBSOCKET_ENDPOINT, WEBSOCKET_ENDPOINTS, WebSocketConfigService, WebSocketEndpoints, WebSocketService, adminGuard, authGuard, authInterceptor, resetRefreshState };
|
|
3604
|
+
export { AnnotationType, ApiService, AsyncTaskMonitorService, AuthService, BaseApiService, CUPCAKE_CORE_CONFIG, CupcakeCoreModule, InvitationStatus, InvitationStatusLabels, LabGroupService, LabGroupsComponent, LoginComponent, NotificationService, PoweredByFooterComponent, RegisterComponent, ResourceRole, ResourceRoleLabels, ResourceService, ResourceType, ResourceTypeLabels, ResourceVisibility, ResourceVisibilityLabels, SiteConfigComponent, SiteConfigService, TASK_STATUS_COLORS, TASK_STATUS_LABELS, TASK_TYPE_LABELS, TaskStatus, TaskType, ThemeService, ToastContainerComponent, ToastService, UserManagementComponent, UserManagementService, UserProfileComponent, WEBSOCKET_ENDPOINT, WEBSOCKET_ENDPOINTS, WebSocketConfigService, WebSocketEndpoints, WebSocketService, adminGuard, authGuard, authInterceptor, resetRefreshState };
|
|
3507
3605
|
//# sourceMappingURL=noatgnu-cupcake-core.mjs.map
|