@qwickapps/server 1.4.0 → 1.5.1
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 +507 -0
- package/README.md +9 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -2
- package/dist/index.js.map +1 -1
- package/dist/plugins/bans/bans-plugin.d.ts.map +1 -1
- package/dist/plugins/bans/bans-plugin.js +12 -3
- package/dist/plugins/bans/bans-plugin.js.map +1 -1
- package/dist/plugins/devices/__tests__/devices-plugin.test.d.ts +11 -0
- package/dist/plugins/devices/__tests__/devices-plugin.test.d.ts.map +1 -0
- package/dist/plugins/devices/__tests__/devices-plugin.test.js +410 -0
- package/dist/plugins/devices/__tests__/devices-plugin.test.js.map +1 -0
- package/dist/plugins/devices/__tests__/token-utils.test.d.ts +7 -0
- package/dist/plugins/devices/__tests__/token-utils.test.d.ts.map +1 -0
- package/dist/plugins/devices/__tests__/token-utils.test.js +197 -0
- package/dist/plugins/devices/__tests__/token-utils.test.js.map +1 -0
- package/dist/plugins/devices/adapters/compute-adapter.d.ts +36 -0
- package/dist/plugins/devices/adapters/compute-adapter.d.ts.map +1 -0
- package/dist/plugins/devices/adapters/compute-adapter.js +100 -0
- package/dist/plugins/devices/adapters/compute-adapter.js.map +1 -0
- package/dist/plugins/devices/adapters/index.d.ts +12 -0
- package/dist/plugins/devices/adapters/index.d.ts.map +1 -0
- package/dist/plugins/devices/adapters/index.js +10 -0
- package/dist/plugins/devices/adapters/index.js.map +1 -0
- package/dist/plugins/devices/adapters/mobile-adapter.d.ts +41 -0
- package/dist/plugins/devices/adapters/mobile-adapter.d.ts.map +1 -0
- package/dist/plugins/devices/adapters/mobile-adapter.js +131 -0
- package/dist/plugins/devices/adapters/mobile-adapter.js.map +1 -0
- package/dist/plugins/devices/devices-plugin.d.ts +70 -0
- package/dist/plugins/devices/devices-plugin.d.ts.map +1 -0
- package/dist/plugins/devices/devices-plugin.js +453 -0
- package/dist/plugins/devices/devices-plugin.js.map +1 -0
- package/dist/plugins/devices/index.d.ts +18 -0
- package/dist/plugins/devices/index.d.ts.map +1 -0
- package/dist/plugins/devices/index.js +18 -0
- package/dist/plugins/devices/index.js.map +1 -0
- package/dist/plugins/devices/stores/index.d.ts +9 -0
- package/dist/plugins/devices/stores/index.d.ts.map +1 -0
- package/dist/plugins/devices/stores/index.js +9 -0
- package/dist/plugins/devices/stores/index.js.map +1 -0
- package/dist/plugins/devices/stores/postgres-store.d.ts +26 -0
- package/dist/plugins/devices/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/devices/stores/postgres-store.js +199 -0
- package/dist/plugins/devices/stores/postgres-store.js.map +1 -0
- package/dist/plugins/devices/token-utils.d.ts +100 -0
- package/dist/plugins/devices/token-utils.d.ts.map +1 -0
- package/dist/plugins/devices/token-utils.js +162 -0
- package/dist/plugins/devices/token-utils.js.map +1 -0
- package/dist/plugins/devices/types.d.ts +307 -0
- package/dist/plugins/devices/types.d.ts.map +1 -0
- package/dist/plugins/devices/types.js +10 -0
- package/dist/plugins/devices/types.js.map +1 -0
- package/dist/plugins/index.d.ts +14 -2
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +13 -1
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/notifications/__tests__/notifications-manager.test.d.ts +5 -0
- package/dist/plugins/notifications/__tests__/notifications-manager.test.d.ts.map +1 -0
- package/dist/plugins/notifications/__tests__/notifications-manager.test.js +470 -0
- package/dist/plugins/notifications/__tests__/notifications-manager.test.js.map +1 -0
- package/dist/plugins/notifications/index.d.ts +71 -0
- package/dist/plugins/notifications/index.d.ts.map +1 -0
- package/dist/plugins/notifications/index.js +72 -0
- package/dist/plugins/notifications/index.js.map +1 -0
- package/dist/plugins/notifications/notifications-manager.d.ts +182 -0
- package/dist/plugins/notifications/notifications-manager.d.ts.map +1 -0
- package/dist/plugins/notifications/notifications-manager.js +610 -0
- package/dist/plugins/notifications/notifications-manager.js.map +1 -0
- package/dist/plugins/notifications/notifications-plugin.d.ts +83 -0
- package/dist/plugins/notifications/notifications-plugin.d.ts.map +1 -0
- package/dist/plugins/notifications/notifications-plugin.js +337 -0
- package/dist/plugins/notifications/notifications-plugin.js.map +1 -0
- package/dist/plugins/notifications/types.d.ts +164 -0
- package/dist/plugins/notifications/types.d.ts.map +1 -0
- package/dist/plugins/notifications/types.js +9 -0
- package/dist/plugins/notifications/types.js.map +1 -0
- package/dist/plugins/parental/__tests__/parental-plugin.test.d.ts +12 -0
- package/dist/plugins/parental/__tests__/parental-plugin.test.d.ts.map +1 -0
- package/dist/plugins/parental/__tests__/parental-plugin.test.js +349 -0
- package/dist/plugins/parental/__tests__/parental-plugin.test.js.map +1 -0
- package/dist/plugins/parental/adapters/index.d.ts +8 -0
- package/dist/plugins/parental/adapters/index.d.ts.map +1 -0
- package/dist/plugins/parental/adapters/index.js +7 -0
- package/dist/plugins/parental/adapters/index.js.map +1 -0
- package/dist/plugins/parental/adapters/kids-adapter.d.ts +24 -0
- package/dist/plugins/parental/adapters/kids-adapter.d.ts.map +1 -0
- package/dist/plugins/parental/adapters/kids-adapter.js +174 -0
- package/dist/plugins/parental/adapters/kids-adapter.js.map +1 -0
- package/dist/plugins/parental/index.d.ts +14 -0
- package/dist/plugins/parental/index.d.ts.map +1 -0
- package/dist/plugins/parental/index.js +15 -0
- package/dist/plugins/parental/index.js.map +1 -0
- package/dist/plugins/parental/parental-plugin.d.ts +88 -0
- package/dist/plugins/parental/parental-plugin.d.ts.map +1 -0
- package/dist/plugins/parental/parental-plugin.js +666 -0
- package/dist/plugins/parental/parental-plugin.js.map +1 -0
- package/dist/plugins/parental/stores/index.d.ts +7 -0
- package/dist/plugins/parental/stores/index.d.ts.map +1 -0
- package/dist/plugins/parental/stores/index.js +7 -0
- package/dist/plugins/parental/stores/index.js.map +1 -0
- package/dist/plugins/parental/stores/postgres-store.d.ts +10 -0
- package/dist/plugins/parental/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/parental/stores/postgres-store.js +209 -0
- package/dist/plugins/parental/stores/postgres-store.js.map +1 -0
- package/dist/plugins/parental/types.d.ts +154 -0
- package/dist/plugins/parental/types.d.ts.map +1 -0
- package/dist/plugins/parental/types.js +10 -0
- package/dist/plugins/parental/types.js.map +1 -0
- package/dist/plugins/profiles/__tests__/profiles-plugin.test.d.ts +11 -0
- package/dist/plugins/profiles/__tests__/profiles-plugin.test.d.ts.map +1 -0
- package/dist/plugins/profiles/__tests__/profiles-plugin.test.js +243 -0
- package/dist/plugins/profiles/__tests__/profiles-plugin.test.js.map +1 -0
- package/dist/plugins/profiles/index.d.ts +12 -0
- package/dist/plugins/profiles/index.d.ts.map +1 -0
- package/dist/plugins/profiles/index.js +13 -0
- package/dist/plugins/profiles/index.js.map +1 -0
- package/dist/plugins/profiles/profiles-plugin.d.ts +71 -0
- package/dist/plugins/profiles/profiles-plugin.d.ts.map +1 -0
- package/dist/plugins/profiles/profiles-plugin.js +481 -0
- package/dist/plugins/profiles/profiles-plugin.js.map +1 -0
- package/dist/plugins/profiles/stores/index.d.ts +9 -0
- package/dist/plugins/profiles/stores/index.d.ts.map +1 -0
- package/dist/plugins/profiles/stores/index.js +9 -0
- package/dist/plugins/profiles/stores/index.js.map +1 -0
- package/dist/plugins/profiles/stores/postgres-store.d.ts +18 -0
- package/dist/plugins/profiles/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/profiles/stores/postgres-store.js +310 -0
- package/dist/plugins/profiles/stores/postgres-store.js.map +1 -0
- package/dist/plugins/profiles/types.d.ts +289 -0
- package/dist/plugins/profiles/types.d.ts.map +1 -0
- package/dist/plugins/profiles/types.js +10 -0
- package/dist/plugins/profiles/types.js.map +1 -0
- package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.d.ts +11 -0
- package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.d.ts.map +1 -0
- package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.js +305 -0
- package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.js.map +1 -0
- package/dist/plugins/subscriptions/index.d.ts +12 -0
- package/dist/plugins/subscriptions/index.d.ts.map +1 -0
- package/dist/plugins/subscriptions/index.js +13 -0
- package/dist/plugins/subscriptions/index.js.map +1 -0
- package/dist/plugins/subscriptions/stores/index.d.ts +9 -0
- package/dist/plugins/subscriptions/stores/index.d.ts.map +1 -0
- package/dist/plugins/subscriptions/stores/index.js +9 -0
- package/dist/plugins/subscriptions/stores/index.js.map +1 -0
- package/dist/plugins/subscriptions/stores/postgres-store.d.ts +14 -0
- package/dist/plugins/subscriptions/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/subscriptions/stores/postgres-store.js +359 -0
- package/dist/plugins/subscriptions/stores/postgres-store.js.map +1 -0
- package/dist/plugins/subscriptions/subscriptions-plugin.d.ts +82 -0
- package/dist/plugins/subscriptions/subscriptions-plugin.d.ts.map +1 -0
- package/dist/plugins/subscriptions/subscriptions-plugin.js +449 -0
- package/dist/plugins/subscriptions/subscriptions-plugin.js.map +1 -0
- package/dist/plugins/subscriptions/types.d.ts +308 -0
- package/dist/plugins/subscriptions/types.d.ts.map +1 -0
- package/dist/plugins/subscriptions/types.js +10 -0
- package/dist/plugins/subscriptions/types.js.map +1 -0
- package/dist/plugins/usage/__tests__/usage-plugin.test.d.ts +11 -0
- package/dist/plugins/usage/__tests__/usage-plugin.test.d.ts.map +1 -0
- package/dist/plugins/usage/__tests__/usage-plugin.test.js +218 -0
- package/dist/plugins/usage/__tests__/usage-plugin.test.js.map +1 -0
- package/dist/plugins/usage/index.d.ts +12 -0
- package/dist/plugins/usage/index.d.ts.map +1 -0
- package/dist/plugins/usage/index.js +13 -0
- package/dist/plugins/usage/index.js.map +1 -0
- package/dist/plugins/usage/stores/index.d.ts +9 -0
- package/dist/plugins/usage/stores/index.d.ts.map +1 -0
- package/dist/plugins/usage/stores/index.js +9 -0
- package/dist/plugins/usage/stores/index.js.map +1 -0
- package/dist/plugins/usage/stores/postgres-store.d.ts +14 -0
- package/dist/plugins/usage/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/usage/stores/postgres-store.js +146 -0
- package/dist/plugins/usage/stores/postgres-store.js.map +1 -0
- package/dist/plugins/usage/types.d.ts +195 -0
- package/dist/plugins/usage/types.d.ts.map +1 -0
- package/dist/plugins/usage/types.js +10 -0
- package/dist/plugins/usage/types.js.map +1 -0
- package/dist/plugins/usage/usage-plugin.d.ts +51 -0
- package/dist/plugins/usage/usage-plugin.d.ts.map +1 -0
- package/dist/plugins/usage/usage-plugin.js +412 -0
- package/dist/plugins/usage/usage-plugin.js.map +1 -0
- package/dist/plugins/users/__tests__/postgres-store.test.d.ts +10 -0
- package/dist/plugins/users/__tests__/postgres-store.test.d.ts.map +1 -0
- package/dist/plugins/users/__tests__/postgres-store.test.js +229 -0
- package/dist/plugins/users/__tests__/postgres-store.test.js.map +1 -0
- package/dist/plugins/users/__tests__/users-plugin.test.js +3 -0
- package/dist/plugins/users/__tests__/users-plugin.test.js.map +1 -1
- package/dist/plugins/users/index.d.ts +2 -2
- package/dist/plugins/users/index.d.ts.map +1 -1
- package/dist/plugins/users/index.js +1 -1
- package/dist/plugins/users/index.js.map +1 -1
- package/dist/plugins/users/stores/postgres-store.d.ts.map +1 -1
- package/dist/plugins/users/stores/postgres-store.js +76 -0
- package/dist/plugins/users/stores/postgres-store.js.map +1 -1
- package/dist/plugins/users/types.d.ts +74 -6
- package/dist/plugins/users/types.d.ts.map +1 -1
- package/dist/plugins/users/users-plugin.d.ts +15 -1
- package/dist/plugins/users/users-plugin.d.ts.map +1 -1
- package/dist/plugins/users/users-plugin.js +29 -0
- package/dist/plugins/users/users-plugin.js.map +1 -1
- package/dist-ui/assets/index-CynOqPkb.js +469 -0
- package/dist-ui/assets/index-CynOqPkb.js.map +1 -0
- package/dist-ui/index.html +1 -1
- package/dist-ui-lib/api/controlPanelApi.d.ts +46 -0
- package/dist-ui-lib/components/StatCard.d.ts +16 -0
- package/dist-ui-lib/dashboard/widgets/NotificationsStatsWidget.d.ts +12 -0
- package/dist-ui-lib/dashboard/widgets/index.d.ts +1 -0
- package/dist-ui-lib/index.js +1822 -1611
- package/dist-ui-lib/index.js.map +1 -1
- package/dist-ui-lib/pages/NotificationsPage.d.ts +9 -0
- package/dist-ui-lib/utils/formatters.d.ts +19 -0
- package/package.json +3 -2
- package/src/index.ts +178 -0
- package/src/plugins/bans/bans-plugin.ts +15 -3
- package/src/plugins/devices/__tests__/devices-plugin.test.ts +551 -0
- package/src/plugins/devices/__tests__/token-utils.test.ts +264 -0
- package/src/plugins/devices/adapters/compute-adapter.ts +139 -0
- package/src/plugins/devices/adapters/index.ts +13 -0
- package/src/plugins/devices/adapters/mobile-adapter.ts +179 -0
- package/src/plugins/devices/devices-plugin.ts +538 -0
- package/src/plugins/devices/index.ts +69 -0
- package/src/plugins/devices/stores/index.ts +9 -0
- package/src/plugins/devices/stores/postgres-store.ts +304 -0
- package/src/plugins/devices/token-utils.ts +213 -0
- package/src/plugins/devices/types.ts +351 -0
- package/src/plugins/index.ts +218 -0
- package/src/plugins/notifications/__tests__/notifications-manager.test.ts +637 -0
- package/src/plugins/notifications/index.ts +91 -0
- package/src/plugins/notifications/notifications-manager.ts +773 -0
- package/src/plugins/notifications/notifications-plugin.ts +398 -0
- package/src/plugins/notifications/types.ts +207 -0
- package/src/plugins/parental/__tests__/parental-plugin.test.ts +465 -0
- package/src/plugins/parental/adapters/index.ts +8 -0
- package/src/plugins/parental/adapters/kids-adapter.ts +206 -0
- package/src/plugins/parental/index.ts +55 -0
- package/src/plugins/parental/parental-plugin.ts +759 -0
- package/src/plugins/parental/stores/index.ts +7 -0
- package/src/plugins/parental/stores/postgres-store.ts +304 -0
- package/src/plugins/parental/types.ts +180 -0
- package/src/plugins/profiles/__tests__/profiles-plugin.test.ts +321 -0
- package/src/plugins/profiles/index.ts +49 -0
- package/src/plugins/profiles/profiles-plugin.ts +546 -0
- package/src/plugins/profiles/stores/index.ts +9 -0
- package/src/plugins/profiles/stores/postgres-store.ts +439 -0
- package/src/plugins/profiles/types.ts +338 -0
- package/src/plugins/subscriptions/__tests__/subscriptions-plugin.test.ts +404 -0
- package/src/plugins/subscriptions/index.ts +51 -0
- package/src/plugins/subscriptions/stores/index.ts +9 -0
- package/src/plugins/subscriptions/stores/postgres-store.ts +482 -0
- package/src/plugins/subscriptions/subscriptions-plugin.ts +530 -0
- package/src/plugins/subscriptions/types.ts +355 -0
- package/src/plugins/usage/__tests__/usage-plugin.test.ts +288 -0
- package/src/plugins/usage/index.ts +39 -0
- package/src/plugins/usage/stores/index.ts +9 -0
- package/src/plugins/usage/stores/postgres-store.ts +213 -0
- package/src/plugins/usage/types.ts +222 -0
- package/src/plugins/usage/usage-plugin.ts +484 -0
- package/src/plugins/users/__tests__/postgres-store.test.ts +326 -0
- package/src/plugins/users/__tests__/users-plugin.test.ts +3 -0
- package/src/plugins/users/index.ts +6 -0
- package/src/plugins/users/stores/postgres-store.ts +104 -0
- package/src/plugins/users/types.ts +82 -6
- package/src/plugins/users/users-plugin.ts +37 -0
- package/ui/src/App.tsx +5 -1
- package/ui/src/api/controlPanelApi.ts +103 -6
- package/ui/src/components/StatCard.tsx +58 -0
- package/ui/src/dashboard/builtInWidgets.tsx +3 -1
- package/ui/src/dashboard/widgets/NotificationsStatsWidget.tsx +167 -0
- package/ui/src/dashboard/widgets/index.ts +1 -0
- package/ui/src/pages/NotificationsPage.tsx +417 -0
- package/ui/src/utils/formatters.ts +33 -0
- package/dist-ui/assets/index-D7DoZ9rL.js +0 -478
- package/dist-ui/assets/index-D7DoZ9rL.js.map +0 -1
|
@@ -347,6 +347,51 @@ export interface TestProviderResponse {
|
|
|
347
347
|
};
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
+
// ==================
|
|
351
|
+
// Notifications API Types
|
|
352
|
+
// ==================
|
|
353
|
+
|
|
354
|
+
export interface NotificationsConnectionHealth {
|
|
355
|
+
isConnected: boolean;
|
|
356
|
+
isHealthy: boolean;
|
|
357
|
+
lastEventAt: string | null;
|
|
358
|
+
timeSinceLastEvent: number;
|
|
359
|
+
channelCount: number;
|
|
360
|
+
isReconnecting: boolean;
|
|
361
|
+
reconnectAttempts: number;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export interface NotificationsStatsResponse {
|
|
365
|
+
totalConnections: number;
|
|
366
|
+
currentConnections: number;
|
|
367
|
+
eventsProcessed: number;
|
|
368
|
+
eventsRouted: number;
|
|
369
|
+
eventsParseFailed: number;
|
|
370
|
+
eventsDroppedNoClients: number;
|
|
371
|
+
reconnectionAttempts: number;
|
|
372
|
+
lastReconnectionAt?: string;
|
|
373
|
+
clientsByType: {
|
|
374
|
+
device: number;
|
|
375
|
+
user: number;
|
|
376
|
+
};
|
|
377
|
+
connectionHealth: NotificationsConnectionHealth;
|
|
378
|
+
channels: string[];
|
|
379
|
+
lastEventAt?: string;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export interface NotificationsClient {
|
|
383
|
+
id: string;
|
|
384
|
+
deviceId?: string;
|
|
385
|
+
userId?: string;
|
|
386
|
+
connectedAt: string;
|
|
387
|
+
durationMs: number;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export interface NotificationsClientsResponse {
|
|
391
|
+
clients: NotificationsClient[];
|
|
392
|
+
total: number;
|
|
393
|
+
}
|
|
394
|
+
|
|
350
395
|
// ==================
|
|
351
396
|
// Rate Limit Config Types
|
|
352
397
|
// ==================
|
|
@@ -470,7 +515,7 @@ class ControlPanelApi {
|
|
|
470
515
|
const params = new URLSearchParams();
|
|
471
516
|
if (options.limit) params.set('limit', options.limit.toString());
|
|
472
517
|
if (options.page) params.set('page', options.page.toString());
|
|
473
|
-
if (options.search) params.set('
|
|
518
|
+
if (options.search) params.set('q', options.search);
|
|
474
519
|
|
|
475
520
|
const response = await fetch(`${this.baseUrl}/api/users?${params}`);
|
|
476
521
|
if (!response.ok) {
|
|
@@ -500,10 +545,18 @@ class ControlPanelApi {
|
|
|
500
545
|
}
|
|
501
546
|
|
|
502
547
|
async banUser(email: string, reason: string, expiresAt?: string): Promise<void> {
|
|
503
|
-
|
|
548
|
+
// Convert expiresAt datetime to duration in seconds
|
|
549
|
+
let duration: number | undefined;
|
|
550
|
+
if (expiresAt) {
|
|
551
|
+
const expiresDate = new Date(expiresAt);
|
|
552
|
+
const now = new Date();
|
|
553
|
+
duration = Math.max(0, Math.floor((expiresDate.getTime() - now.getTime()) / 1000));
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const response = await fetch(`${this.baseUrl}/api/bans/email/${encodeURIComponent(email)}`, {
|
|
504
557
|
method: 'POST',
|
|
505
558
|
headers: { 'Content-Type': 'application/json' },
|
|
506
|
-
body: JSON.stringify({
|
|
559
|
+
body: JSON.stringify({ reason, duration }),
|
|
507
560
|
});
|
|
508
561
|
if (!response.ok) {
|
|
509
562
|
const error = await response.json().catch(() => ({}));
|
|
@@ -512,7 +565,7 @@ class ControlPanelApi {
|
|
|
512
565
|
}
|
|
513
566
|
|
|
514
567
|
async unbanUser(email: string): Promise<void> {
|
|
515
|
-
const response = await fetch(`${this.baseUrl}/api/bans/${encodeURIComponent(email)}`, {
|
|
568
|
+
const response = await fetch(`${this.baseUrl}/api/bans/email/${encodeURIComponent(email)}`, {
|
|
516
569
|
method: 'DELETE',
|
|
517
570
|
});
|
|
518
571
|
if (!response.ok) {
|
|
@@ -521,11 +574,13 @@ class ControlPanelApi {
|
|
|
521
574
|
}
|
|
522
575
|
|
|
523
576
|
async checkBan(email: string): Promise<{ banned: boolean; ban?: Ban }> {
|
|
524
|
-
const response = await fetch(`${this.baseUrl}/api/bans/
|
|
577
|
+
const response = await fetch(`${this.baseUrl}/api/bans/email/${encodeURIComponent(email)}`);
|
|
525
578
|
if (!response.ok) {
|
|
526
579
|
throw new Error(`Ban check failed: ${response.statusText}`);
|
|
527
580
|
}
|
|
528
|
-
|
|
581
|
+
const data = await response.json();
|
|
582
|
+
// Backend returns { email, isBanned }, transform to expected shape
|
|
583
|
+
return { banned: data.isBanned };
|
|
529
584
|
}
|
|
530
585
|
|
|
531
586
|
// ==================
|
|
@@ -821,6 +876,48 @@ class ControlPanelApi {
|
|
|
821
876
|
}
|
|
822
877
|
return response.json();
|
|
823
878
|
}
|
|
879
|
+
|
|
880
|
+
// ==================
|
|
881
|
+
// Notifications API
|
|
882
|
+
// ==================
|
|
883
|
+
|
|
884
|
+
async getNotificationsStats(): Promise<NotificationsStatsResponse> {
|
|
885
|
+
const response = await fetch(`${this.baseUrl}/api/notifications/stats`);
|
|
886
|
+
if (!response.ok) {
|
|
887
|
+
throw new Error(`Notifications stats request failed: ${response.statusText}`);
|
|
888
|
+
}
|
|
889
|
+
return response.json();
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
async getNotificationsClients(): Promise<NotificationsClientsResponse> {
|
|
893
|
+
const response = await fetch(`${this.baseUrl}/api/notifications/clients`);
|
|
894
|
+
if (!response.ok) {
|
|
895
|
+
throw new Error(`Notifications clients request failed: ${response.statusText}`);
|
|
896
|
+
}
|
|
897
|
+
return response.json();
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
async disconnectNotificationsClient(clientId: string): Promise<{ success: boolean }> {
|
|
901
|
+
const response = await fetch(`${this.baseUrl}/api/notifications/clients/${encodeURIComponent(clientId)}`, {
|
|
902
|
+
method: 'DELETE',
|
|
903
|
+
});
|
|
904
|
+
if (!response.ok) {
|
|
905
|
+
const error = await response.json().catch(() => ({}));
|
|
906
|
+
throw new Error(error.error || `Disconnect client failed: ${response.statusText}`);
|
|
907
|
+
}
|
|
908
|
+
return response.json();
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
async forceNotificationsReconnect(): Promise<{ success: boolean; message: string }> {
|
|
912
|
+
const response = await fetch(`${this.baseUrl}/api/notifications/reconnect`, {
|
|
913
|
+
method: 'POST',
|
|
914
|
+
});
|
|
915
|
+
if (!response.ok) {
|
|
916
|
+
const error = await response.json().catch(() => ({}));
|
|
917
|
+
throw new Error(error.error || `Force reconnect failed: ${response.statusText}`);
|
|
918
|
+
}
|
|
919
|
+
return response.json();
|
|
920
|
+
}
|
|
824
921
|
}
|
|
825
922
|
|
|
826
923
|
export const api = new ControlPanelApi();
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StatCard Component
|
|
3
|
+
*
|
|
4
|
+
* A reusable card component for displaying statistics with an icon,
|
|
5
|
+
* value, label, and optional sub-value.
|
|
6
|
+
*
|
|
7
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Box, Card, CardContent } from '@mui/material';
|
|
11
|
+
import { Text } from '@qwickapps/react-framework';
|
|
12
|
+
|
|
13
|
+
export interface StatCardProps {
|
|
14
|
+
icon: React.ReactNode;
|
|
15
|
+
label: string;
|
|
16
|
+
value: string | number;
|
|
17
|
+
subValue?: string;
|
|
18
|
+
color?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function StatCard({
|
|
22
|
+
icon,
|
|
23
|
+
label,
|
|
24
|
+
value,
|
|
25
|
+
subValue,
|
|
26
|
+
color = 'var(--theme-primary)',
|
|
27
|
+
}: StatCardProps) {
|
|
28
|
+
return (
|
|
29
|
+
<Card sx={{ bgcolor: 'var(--theme-surface)', height: '100%' }}>
|
|
30
|
+
<CardContent sx={{ py: 1.5, '&:last-child': { pb: 1.5 } }}>
|
|
31
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
|
32
|
+
<Box sx={{ color }}>{icon}</Box>
|
|
33
|
+
<Box sx={{ flex: 1, minWidth: 0 }}>
|
|
34
|
+
<Text
|
|
35
|
+
variant="h4"
|
|
36
|
+
content={String(value)}
|
|
37
|
+
customColor="var(--theme-text-primary)"
|
|
38
|
+
fontWeight="600"
|
|
39
|
+
/>
|
|
40
|
+
<Text
|
|
41
|
+
variant="caption"
|
|
42
|
+
content={label}
|
|
43
|
+
customColor="var(--theme-text-secondary)"
|
|
44
|
+
/>
|
|
45
|
+
{subValue && (
|
|
46
|
+
<Text
|
|
47
|
+
variant="caption"
|
|
48
|
+
content={subValue}
|
|
49
|
+
customColor="var(--theme-text-secondary)"
|
|
50
|
+
sx={{ display: 'block', mt: 0.25 }}
|
|
51
|
+
/>
|
|
52
|
+
)}
|
|
53
|
+
</Box>
|
|
54
|
+
</Box>
|
|
55
|
+
</CardContent>
|
|
56
|
+
</Card>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { ServiceHealthWidget, IntegrationStatusWidget, AuthStatusWidget } from './widgets';
|
|
13
|
+
import { ServiceHealthWidget, IntegrationStatusWidget, AuthStatusWidget, NotificationsStatsWidget } from './widgets';
|
|
14
14
|
import type { WidgetComponent } from './WidgetComponentRegistry';
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -21,6 +21,7 @@ export const builtInWidgetComponents: Record<string, React.ComponentType> = {
|
|
|
21
21
|
ServiceHealthWidget: ServiceHealthWidget,
|
|
22
22
|
IntegrationStatusWidget: IntegrationStatusWidget,
|
|
23
23
|
AuthStatusWidget: AuthStatusWidget,
|
|
24
|
+
NotificationsStatsWidget: NotificationsStatsWidget,
|
|
24
25
|
};
|
|
25
26
|
|
|
26
27
|
/**
|
|
@@ -35,5 +36,6 @@ export function getBuiltInWidgetComponents(): WidgetComponent[] {
|
|
|
35
36
|
{ name: 'ServiceHealthWidget', component: ServiceHealthWidget },
|
|
36
37
|
{ name: 'IntegrationStatusWidget', component: IntegrationStatusWidget },
|
|
37
38
|
{ name: 'AuthStatusWidget', component: AuthStatusWidget },
|
|
39
|
+
{ name: 'NotificationsStatsWidget', component: NotificationsStatsWidget },
|
|
38
40
|
];
|
|
39
41
|
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notifications Stats Widget
|
|
3
|
+
*
|
|
4
|
+
* Displays realtime notifications connection statistics on the Control Panel dashboard.
|
|
5
|
+
* Shows active clients, events processed, and connection health.
|
|
6
|
+
*
|
|
7
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useState, useEffect } from 'react';
|
|
11
|
+
import { Box, Card, CardContent, Chip, LinearProgress } from '@mui/material';
|
|
12
|
+
import { GridLayout, Text } from '@qwickapps/react-framework';
|
|
13
|
+
import WifiIcon from '@mui/icons-material/Wifi';
|
|
14
|
+
import WifiOffIcon from '@mui/icons-material/WifiOff';
|
|
15
|
+
import DevicesIcon from '@mui/icons-material/Devices';
|
|
16
|
+
import PersonIcon from '@mui/icons-material/Person';
|
|
17
|
+
import SendIcon from '@mui/icons-material/Send';
|
|
18
|
+
import ErrorIcon from '@mui/icons-material/Error';
|
|
19
|
+
import { api } from '../../api/controlPanelApi';
|
|
20
|
+
import type { NotificationsStatsResponse } from '../../api/controlPanelApi';
|
|
21
|
+
import { StatCard } from '../../components/StatCard';
|
|
22
|
+
import { formatNumber, formatDuration } from '../../utils/formatters';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Notifications Stats Widget Component
|
|
26
|
+
*/
|
|
27
|
+
export function NotificationsStatsWidget() {
|
|
28
|
+
const [stats, setStats] = useState<NotificationsStatsResponse | null>(null);
|
|
29
|
+
const [error, setError] = useState<string | null>(null);
|
|
30
|
+
const [loading, setLoading] = useState(true);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
const fetchStats = async () => {
|
|
34
|
+
try {
|
|
35
|
+
const data = await api.getNotificationsStats();
|
|
36
|
+
setStats(data);
|
|
37
|
+
setError(null);
|
|
38
|
+
} catch (err) {
|
|
39
|
+
// Check if it's a 404 (plugin not enabled)
|
|
40
|
+
if (err instanceof Error && err.message.includes('404')) {
|
|
41
|
+
setError('Notifications plugin not enabled');
|
|
42
|
+
} else {
|
|
43
|
+
setError(err instanceof Error ? err.message : 'Failed to fetch stats');
|
|
44
|
+
}
|
|
45
|
+
} finally {
|
|
46
|
+
setLoading(false);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
fetchStats();
|
|
51
|
+
const interval = setInterval(fetchStats, 5000); // Refresh every 5 seconds
|
|
52
|
+
return () => clearInterval(interval);
|
|
53
|
+
}, []);
|
|
54
|
+
|
|
55
|
+
if (loading) {
|
|
56
|
+
return (
|
|
57
|
+
<Card sx={{ bgcolor: 'var(--theme-surface)' }}>
|
|
58
|
+
<CardContent>
|
|
59
|
+
<LinearProgress />
|
|
60
|
+
</CardContent>
|
|
61
|
+
</Card>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (error) {
|
|
66
|
+
return (
|
|
67
|
+
<Card sx={{ bgcolor: 'var(--theme-surface)', border: '1px solid var(--theme-border)' }}>
|
|
68
|
+
<CardContent>
|
|
69
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
70
|
+
<WifiOffIcon sx={{ color: 'var(--theme-text-secondary)' }} />
|
|
71
|
+
<Text variant="body2" customColor="var(--theme-text-secondary)" content={error} />
|
|
72
|
+
</Box>
|
|
73
|
+
</CardContent>
|
|
74
|
+
</Card>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!stats) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const isHealthy = stats.connectionHealth.isHealthy;
|
|
83
|
+
const healthColor = isHealthy ? 'var(--theme-success)' : 'var(--theme-warning)';
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<Box>
|
|
87
|
+
{/* Connection Status Bar */}
|
|
88
|
+
<Card sx={{ bgcolor: 'var(--theme-surface)', mb: 2 }}>
|
|
89
|
+
<CardContent sx={{ py: 1, '&:last-child': { pb: 1 } }}>
|
|
90
|
+
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
|
91
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
92
|
+
{isHealthy ? (
|
|
93
|
+
<WifiIcon sx={{ color: healthColor, fontSize: 20 }} />
|
|
94
|
+
) : (
|
|
95
|
+
<WifiOffIcon sx={{ color: healthColor, fontSize: 20 }} />
|
|
96
|
+
)}
|
|
97
|
+
<Text
|
|
98
|
+
variant="body2"
|
|
99
|
+
content={isHealthy ? 'Connected' : 'Reconnecting...'}
|
|
100
|
+
customColor={healthColor}
|
|
101
|
+
fontWeight="500"
|
|
102
|
+
/>
|
|
103
|
+
{stats.connectionHealth.isReconnecting && (
|
|
104
|
+
<Chip
|
|
105
|
+
label={`Attempt ${stats.connectionHealth.reconnectAttempts}`}
|
|
106
|
+
size="small"
|
|
107
|
+
sx={{
|
|
108
|
+
bgcolor: 'var(--theme-warning)20',
|
|
109
|
+
color: 'var(--theme-warning)',
|
|
110
|
+
fontSize: '0.7rem',
|
|
111
|
+
height: 18,
|
|
112
|
+
}}
|
|
113
|
+
/>
|
|
114
|
+
)}
|
|
115
|
+
</Box>
|
|
116
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
|
117
|
+
<Text
|
|
118
|
+
variant="caption"
|
|
119
|
+
content={`${stats.channels.length} channel${stats.channels.length !== 1 ? 's' : ''}`}
|
|
120
|
+
customColor="var(--theme-text-secondary)"
|
|
121
|
+
/>
|
|
122
|
+
{stats.lastEventAt && (
|
|
123
|
+
<Text
|
|
124
|
+
variant="caption"
|
|
125
|
+
content={`Last event: ${formatDuration(stats.connectionHealth.timeSinceLastEvent)} ago`}
|
|
126
|
+
customColor="var(--theme-text-secondary)"
|
|
127
|
+
/>
|
|
128
|
+
)}
|
|
129
|
+
</Box>
|
|
130
|
+
</Box>
|
|
131
|
+
</CardContent>
|
|
132
|
+
</Card>
|
|
133
|
+
|
|
134
|
+
{/* Stats Grid */}
|
|
135
|
+
<GridLayout columns={4} spacing="small" equalHeight>
|
|
136
|
+
<StatCard
|
|
137
|
+
icon={<DevicesIcon sx={{ fontSize: 28 }} />}
|
|
138
|
+
label="Active Clients"
|
|
139
|
+
value={stats.currentConnections}
|
|
140
|
+
subValue={`${stats.totalConnections} total`}
|
|
141
|
+
color="var(--theme-primary)"
|
|
142
|
+
/>
|
|
143
|
+
<StatCard
|
|
144
|
+
icon={<PersonIcon sx={{ fontSize: 28 }} />}
|
|
145
|
+
label="By Device"
|
|
146
|
+
value={stats.clientsByType.device}
|
|
147
|
+
subValue={`${stats.clientsByType.user} by user`}
|
|
148
|
+
color="var(--theme-info)"
|
|
149
|
+
/>
|
|
150
|
+
<StatCard
|
|
151
|
+
icon={<SendIcon sx={{ fontSize: 28 }} />}
|
|
152
|
+
label="Events Routed"
|
|
153
|
+
value={formatNumber(stats.eventsRouted)}
|
|
154
|
+
subValue={`${formatNumber(stats.eventsProcessed)} processed`}
|
|
155
|
+
color="var(--theme-success)"
|
|
156
|
+
/>
|
|
157
|
+
<StatCard
|
|
158
|
+
icon={<ErrorIcon sx={{ fontSize: 28 }} />}
|
|
159
|
+
label="Dropped"
|
|
160
|
+
value={formatNumber(stats.eventsDroppedNoClients)}
|
|
161
|
+
subValue={`${stats.eventsParseFailed} parse errors`}
|
|
162
|
+
color={stats.eventsDroppedNoClients > 0 ? 'var(--theme-warning)' : 'var(--theme-text-secondary)'}
|
|
163
|
+
/>
|
|
164
|
+
</GridLayout>
|
|
165
|
+
</Box>
|
|
166
|
+
);
|
|
167
|
+
}
|