Ekimart078 commited on
Commit
18dfb91
·
1 Parent(s): 8c0f057

Redesign WebSocket test interface for improved usability and aesthetics, and update `uvicorn` dependency to include `[standard]` extension.

Browse files
Files changed (2) hide show
  1. main.py +645 -69
  2. requirements.txt +1 -1
main.py CHANGED
@@ -96,89 +96,665 @@ async def get_test_page():
96
  """Page de test pour les connexions WebSocket"""
97
  return """
98
  <!DOCTYPE html>
99
- <html>
100
- <head>
101
- <title>WebSocket Notification Test</title>
102
- <style>
103
- body { font-family: Arial, sans-serif; padding: 20px; }
104
- .container { max-width: 800px; margin: 0 auto; }
105
- .messages { height: 300px; overflow-y: auto; border: 1px solid #ccc; padding: 10px; margin: 10px 0; }
106
- .message { margin: 5px 0; padding: 5px; border-radius: 3px; }
107
- .notification { background-color: #e7f3ff; }
108
- .system { background-color: #f0f0f0; }
109
- input, button { margin: 5px; padding: 5px; }
110
- </style>
111
- </head>
112
- <body>
113
- <div class="container">
114
- <h1>WebSocket Notification Test</h1>
115
- <div>
116
- <input type="text" id="userId" placeholder="User ID" value="user123">
117
- <input type="text" id="roles" placeholder="Roles (comma-separated)" value="admin,user">
118
- <button onclick="connect()">Connect</button>
119
- <button onclick="disconnect()">Disconnect</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  </div>
121
- <div id="messages" class="messages"></div>
122
- <div>
123
- <input type="text" id="messageInput" placeholder="Type a message...">
124
- <button onclick="sendMessage()">Send</button>
 
 
 
 
125
  </div>
126
  </div>
127
 
128
- <script>
129
- let ws = null;
 
 
 
 
 
 
 
 
130
 
131
- function connect() {
132
- const userId = document.getElementById('userId').value;
133
- const roles = document.getElementById('roles').value;
134
- const protocol = window.location.protocol === "https:" ? "wss" : "ws";
135
- const url = `${protocol}://${window.location.host}/ws/${userId}?roles=${roles}`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
 
137
- ws = new WebSocket(url);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
 
139
- ws.onmessage = function(event) {
140
- const data = JSON.parse(event.data);
141
- addMessage(data, 'notification');
142
- };
 
 
 
 
143
 
144
- ws.onopen = function() {
145
- addMessage({message: 'Connected to WebSocket'}, 'system');
146
- };
 
 
 
 
 
147
 
148
- ws.onclose = function() {
149
- addMessage({message: 'Disconnected from WebSocket'}, 'system');
150
- };
 
 
 
151
  }
 
 
 
 
152
 
153
- function disconnect() {
154
- if (ws) {
155
- ws.close();
156
- ws = null;
157
- }
158
- }
 
 
 
 
 
 
 
 
159
 
160
- function sendMessage() {
161
- if (ws && ws.readyState === WebSocket.OPEN) {
162
- const message = document.getElementById('messageInput').value;
163
- ws.send(JSON.stringify({message: message}));
164
- document.getElementById('messageInput').value = '';
165
- }
166
- }
167
 
168
- function addMessage(data, type) {
169
- const messages = document.getElementById('messages');
170
- const messageDiv = document.createElement('div');
171
- messageDiv.className = `message ${type}`;
172
- messageDiv.innerHTML = `<strong>${data.type || 'message'}:</strong> ${data.message || JSON.stringify(data)}`;
173
- messages.appendChild(messageDiv);
174
- messages.scrollTop = messages.scrollHeight;
175
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
 
177
- // Auto-connect on page load
178
- connect();
179
- </script>
180
- </body>
181
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  """
183
 
184
 
 
96
  """Page de test pour les connexions WebSocket"""
97
  return """
98
  <!DOCTYPE html>
99
+ <html>
100
+ <head>
101
+ <title>WebSocket Notification Service - Test Interface</title>
102
+ <style>
103
+ * {
104
+ margin: 0;
105
+ padding: 0;
106
+ box-sizing: border-box;
107
+ }
108
+
109
+ body {
110
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
111
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
112
+ min-height: 100vh;
113
+ padding: 20px;
114
+ color: #333;
115
+ }
116
+
117
+ .container {
118
+ max-width: 1200px;
119
+ margin: 0 auto;
120
+ background: white;
121
+ border-radius: 15px;
122
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
123
+ overflow: hidden;
124
+ }
125
+
126
+ .header {
127
+ background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
128
+ padding: 30px;
129
+ text-align: center;
130
+ color: white;
131
+ }
132
+
133
+ .header h1 {
134
+ font-size: 2.5em;
135
+ margin-bottom: 10px;
136
+ font-weight: 300;
137
+ }
138
+
139
+ .header p {
140
+ font-size: 1.1em;
141
+ opacity: 0.9;
142
+ }
143
+
144
+ .main-content {
145
+ padding: 30px;
146
+ }
147
+
148
+ .section {
149
+ margin-bottom: 30px;
150
+ padding: 25px;
151
+ background: #f8f9fa;
152
+ border-radius: 10px;
153
+ border-left: 4px solid #4facfe;
154
+ }
155
+
156
+ .section h2 {
157
+ color: #2c3e50;
158
+ margin-bottom: 15px;
159
+ font-size: 1.4em;
160
+ }
161
+
162
+ .connection-panel {
163
+ display: grid;
164
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
165
+ gap: 20px;
166
+ margin-bottom: 20px;
167
+ }
168
+
169
+ .input-group {
170
+ display: flex;
171
+ flex-direction: column;
172
+ }
173
+
174
+ .input-group label {
175
+ margin-bottom: 5px;
176
+ font-weight: 600;
177
+ color: #555;
178
+ }
179
+
180
+ .input-group input {
181
+ padding: 12px;
182
+ border: 2px solid #e0e0e0;
183
+ border-radius: 8px;
184
+ font-size: 14px;
185
+ transition: border-color 0.3s;
186
+ }
187
+
188
+ .input-group input:focus {
189
+ outline: none;
190
+ border-color: #4facfe;
191
+ }
192
+
193
+ .btn-group {
194
+ display: flex;
195
+ gap: 10px;
196
+ flex-wrap: wrap;
197
+ }
198
+
199
+ .btn {
200
+ padding: 12px 24px;
201
+ border: none;
202
+ border-radius: 8px;
203
+ font-size: 14px;
204
+ font-weight: 600;
205
+ cursor: pointer;
206
+ transition: all 0.3s;
207
+ text-transform: uppercase;
208
+ letter-spacing: 0.5px;
209
+ }
210
+
211
+ .btn-primary {
212
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
213
+ color: white;
214
+ }
215
+
216
+ .btn-primary:hover {
217
+ transform: translateY(-2px);
218
+ box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
219
+ }
220
+
221
+ .btn-secondary {
222
+ background: #6c757d;
223
+ color: white;
224
+ }
225
+
226
+ .btn-secondary:hover {
227
+ background: #5a6268;
228
+ transform: translateY(-2px);
229
+ }
230
+
231
+ .btn-danger {
232
+ background: #dc3545;
233
+ color: white;
234
+ }
235
+
236
+ .btn-danger:hover {
237
+ background: #c82333;
238
+ transform: translateY(-2px);
239
+ }
240
+
241
+ .btn-success {
242
+ background: #28a745;
243
+ color: white;
244
+ }
245
+
246
+ .btn-success:hover {
247
+ background: #218838;
248
+ transform: translateY(-2px);
249
+ }
250
+
251
+ .status-indicator {
252
+ display: inline-block;
253
+ width: 12px;
254
+ height: 12px;
255
+ border-radius: 50%;
256
+ margin-right: 8px;
257
+ }
258
+
259
+ .status-connected {
260
+ background: #28a745;
261
+ box-shadow: 0 0 10px rgba(40, 167, 69, 0.5);
262
+ }
263
+
264
+ .status-disconnected {
265
+ background: #dc3545;
266
+ }
267
+
268
+ .messages {
269
+ height: 400px;
270
+ overflow-y: auto;
271
+ border: 2px solid #e0e0e0;
272
+ border-radius: 10px;
273
+ padding: 15px;
274
+ background: white;
275
+ font-family: 'Courier New', monospace;
276
+ font-size: 13px;
277
+ }
278
+
279
+ .message {
280
+ margin: 8px 0;
281
+ padding: 10px;
282
+ border-radius: 6px;
283
+ border-left: 4px solid #ddd;
284
+ }
285
+
286
+ .message-notification {
287
+ background: #e7f3ff;
288
+ border-left-color: #4facfe;
289
+ }
290
+
291
+ .message-system {
292
+ background: #f8f9fa;
293
+ border-left-color: #6c757d;
294
+ }
295
+
296
+ .message-error {
297
+ background: #f8d7da;
298
+ border-left-color: #dc3545;
299
+ }
300
+
301
+ .message-success {
302
+ background: #d4edda;
303
+ border-left-color: #28a745;
304
+ }
305
+
306
+ .message-timestamp {
307
+ font-size: 11px;
308
+ color: #666;
309
+ float: right;
310
+ }
311
+
312
+ .api-section {
313
+ display: grid;
314
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
315
+ gap: 20px;
316
+ }
317
+
318
+ .api-card {
319
+ background: white;
320
+ border-radius: 10px;
321
+ padding: 20px;
322
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
323
+ border: 1px solid #e0e0e0;
324
+ }
325
+
326
+ .api-card h3 {
327
+ color: #2c3e50;
328
+ margin-bottom: 15px;
329
+ font-size: 1.2em;
330
+ }
331
+
332
+ .api-card textarea {
333
+ width: 100%;
334
+ height: 100px;
335
+ padding: 10px;
336
+ border: 1px solid #e0e0e0;
337
+ border-radius: 6px;
338
+ font-family: 'Courier New', monospace;
339
+ font-size: 12px;
340
+ resize: vertical;
341
+ }
342
+
343
+ .stats-grid {
344
+ display: grid;
345
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
346
+ gap: 20px;
347
+ }
348
+
349
+ .stat-card {
350
+ background: white;
351
+ padding: 20px;
352
+ border-radius: 10px;
353
+ text-align: center;
354
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
355
+ }
356
+
357
+ .stat-number {
358
+ font-size: 2em;
359
+ font-weight: bold;
360
+ color: #4facfe;
361
+ margin-bottom: 5px;
362
+ }
363
+
364
+ .stat-label {
365
+ color: #666;
366
+ font-size: 0.9em;
367
+ }
368
+
369
+ .json-display {
370
+ background: #f8f9fa;
371
+ padding: 15px;
372
+ border-radius: 6px;
373
+ border: 1px solid #e0e0e0;
374
+ font-family: 'Courier New', monospace;
375
+ font-size: 12px;
376
+ white-space: pre-wrap;
377
+ max-height: 300px;
378
+ overflow-y: auto;
379
+ }
380
+
381
+ @media (max-width: 768px) {
382
+ .connection-panel {
383
+ grid-template-columns: 1fr;
384
+ }
385
+
386
+ .btn-group {
387
+ flex-direction: column;
388
+ }
389
+
390
+ .api-section {
391
+ grid-template-columns: 1fr;
392
+ }
393
+ }
394
+ </style>
395
+ </head>
396
+ <body>
397
+ <div class="container">
398
+ <div class="header">
399
+ <h1>WebSocket Notification Service</h1>
400
+ <p>Interface de test pour les notifications en temps réel</p>
401
+ </div>
402
+
403
+ <div class="main-content">
404
+ <!-- Section Connexion -->
405
+ <div class="section">
406
+ <h2>🔌 Connexion WebSocket</h2>
407
+ <div class="connection-panel">
408
+ <div class="input-group">
409
+ <label for="userId">User ID</label>
410
+ <input type="text" id="userId" placeholder="user123" value="user123">
411
+ </div>
412
+ <div class="input-group">
413
+ <label for="roles">Rôles (séparés par des virgules)</label>
414
+ <input type="text" id="roles" placeholder="admin,user" value="admin,user">
415
+ </div>
416
  </div>
417
+ <div class="btn-group">
418
+ <button class="btn btn-primary" onclick="connect()">
419
+ <span class="status-indicator" id="statusIndicator"></span>
420
+ Se connecter
421
+ </button>
422
+ <button class="btn btn-secondary" onclick="disconnect()">Déconnecter</button>
423
+ <button class="btn btn-success" onclick="getStats()">Statistiques</button>
424
+ <button class="btn btn-success" onclick="getClients()">Clients connectés</button>
425
  </div>
426
  </div>
427
 
428
+ <!-- Section Messages -->
429
+ <div class="section">
430
+ <h2>💬 Messages</h2>
431
+ <div id="messages" class="messages"></div>
432
+ <div style="margin-top: 15px; display: flex; gap: 10px;">
433
+ <input type="text" id="messageInput" placeholder="Tapez votre message..." style="flex: 1; padding: 10px; border: 1px solid #e0e0e0; border-radius: 6px;">
434
+ <button class="btn btn-primary" onclick="sendMessage()">Envoyer</button>
435
+ <button class="btn btn-secondary" onclick="clearMessages()">Effacer</button>
436
+ </div>
437
+ </div>
438
 
439
+ <!-- Section API REST -->
440
+ <div class="section">
441
+ <h2>🚀 Tests API REST</h2>
442
+ <div class="api-section">
443
+ <div class="api-card">
444
+ <h3>Notification utilisateur</h3>
445
+ <div class="input-group">
446
+ <label>User ID cible</label>
447
+ <input type="text" id="targetUserId" placeholder="user123" value="user123">
448
+ </div>
449
+ <div class="input-group">
450
+ <label>Message</label>
451
+ <textarea id="userNotificationMessage" placeholder='{"type": "info", "title": "Test", "content": "Message de test"}'></textarea>
452
+ </div>
453
+ <button class="btn btn-primary" onclick="sendUserNotification()">Envoyer</button>
454
+ </div>
455
+
456
+ <div class="api-card">
457
+ <h3>Notification par rôle</h3>
458
+ <div class="input-group">
459
+ <label>Rôle cible</label>
460
+ <input type="text" id="targetRole" placeholder="admin" value="admin">
461
+ </div>
462
+ <div class="input-group">
463
+ <label>Message</label>
464
+ <textarea id="roleNotificationMessage" placeholder='{"type": "warning", "title": "Alerte", "content": "Message pour les admins"}'></textarea>
465
+ </div>
466
+ <button class="btn btn-primary" onclick="sendRoleNotification()">Envoyer</button>
467
+ </div>
468
+
469
+ <div class="api-card">
470
+ <h3>Diffusion globale</h3>
471
+ <div class="input-group">
472
+ <label>Message</label>
473
+ <textarea id="broadcastMessage" placeholder='{"type": "announcement", "title": "Annonce", "content": "Message global"}'></textarea>
474
+ </div>
475
+ <button class="btn btn-primary" onclick="sendBroadcast()">Diffuser</button>
476
+ </div>
477
+
478
+ <div class="api-card">
479
+ <h3>Notification ciblée</h3>
480
+ <div class="input-group">
481
+ <label>Type de cible</label>
482
+ <select id="targetType" style="width: 100%; padding: 8px; border: 1px solid #e0e0e0; border-radius: 6px;">
483
+ <option value="user">Utilisateur</option>
484
+ <option value="role">Rôle</option>
485
+ <option value="global">Global</option>
486
+ </select>
487
+ </div>
488
+ <div class="input-group">
489
+ <label>Valeur cible</label>
490
+ <input type="text" id="targetValue" placeholder="user123 ou admin" value="user123">
491
+ </div>
492
+ <div class="input-group">
493
+ <label>Message</label>
494
+ <textarea id="targetedMessage" placeholder='{"type": "targeted", "title": "Ciblé", "content": "Message ciblé"}'></textarea>
495
+ </div>
496
+ <button class="btn btn-primary" onclick="sendTargetedNotification()">Envoyer</button>
497
+ </div>
498
+ </div>
499
+ </div>
500
 
501
+ <!-- Section Statistiques -->
502
+ <div class="section">
503
+ <h2>📊 Statistiques du service</h2>
504
+ <div class="stats-grid" id="statsGrid">
505
+ <div class="stat-card">
506
+ <div class="stat-number" id="connectedClients">0</div>
507
+ <div class="stat-label">Clients connectés</div>
508
+ </div>
509
+ <div class="stat-card">
510
+ <div class="stat-number" id="totalNotifications">0</div>
511
+ <div class="stat-label">Notifications envoyées</div>
512
+ </div>
513
+ <div class="stat-card">
514
+ <div class="stat-number" id="activeSubscriptions">0</div>
515
+ <div class="stat-label">Abonnements actifs</div>
516
+ </div>
517
+ </div>
518
+ <div style="margin-top: 20px;">
519
+ <h3>Détails des clients connectés</h3>
520
+ <div id="clientDetails" class="json-display">Cliquez sur "Clients connectés" pour voir les détails</div>
521
+ </div>
522
+ </div>
523
+ </div>
524
+ </div>
525
+
526
+ <script>
527
+ let ws = null;
528
+ let sessionId = null;
529
+ const baseUrl = window.location.origin;
530
+
531
+ // Initialisation des messages par défaut
532
+ document.getElementById('userNotificationMessage').value = JSON.stringify({
533
+ type: "info",
534
+ title: "Test utilisateur",
535
+ content: "Ceci est un message de test pour un utilisateur spécifique"
536
+ }, null, 2);
537
+
538
+ document.getElementById('roleNotificationMessage').value = JSON.stringify({
539
+ type: "warning",
540
+ title: "Alerte admin",
541
+ content: "Message important pour les administrateurs"
542
+ }, null, 2);
543
+
544
+ document.getElementById('broadcastMessage').value = JSON.stringify({
545
+ type: "announcement",
546
+ title: "Annonce générale",
547
+ content: "Message diffusé à tous les clients connectés"
548
+ }, null, 2);
549
+
550
+ document.getElementById('targetedMessage').value = JSON.stringify({
551
+ type: "targeted",
552
+ title: "Message ciblé",
553
+ content: "Message envoyé via l'API de notification ciblée"
554
+ }, null, 2);
555
+
556
+ function connect() {
557
+ const userId = document.getElementById('userId').value;
558
+ const roles = document.getElementById('roles').value;
559
+ const protocol = window.location.protocol === "https:" ? "wss" : "ws";
560
+ const url = `${protocol}://${window.location.host}/ws/${userId}?roles=${roles}`;
561
+
562
+ ws = new WebSocket(url);
563
+
564
+ ws.onmessage = function(event) {
565
+ const data = JSON.parse(event.data);
566
+ addMessage(data, 'notification');
567
+ };
568
+
569
+ ws.onopen = function() {
570
+ updateConnectionStatus(true);
571
+ addMessage({message: 'Connexion WebSocket établie', type: 'system'}, 'system');
572
+ getStats(); // Rafraîchir les stats
573
+ };
574
+
575
+ ws.onclose = function() {
576
+ updateConnectionStatus(false);
577
+ addMessage({message: 'Connexion WebSocket fermée', type: 'system'}, 'system');
578
+ };
579
+
580
+ ws.onerror = function(error) {
581
+ addMessage({message: 'Erreur WebSocket: ' + error, type: 'error'}, 'error');
582
+ };
583
+ }
584
 
585
+ function disconnect() {
586
+ if (ws) {
587
+ ws.close();
588
+ ws = null;
589
+ sessionId = null;
590
+ updateConnectionStatus(false);
591
+ }
592
+ }
593
 
594
+ function updateConnectionStatus(connected) {
595
+ const indicator = document.getElementById('statusIndicator');
596
+ if (connected) {
597
+ indicator.className = 'status-indicator status-connected';
598
+ } else {
599
+ indicator.className = 'status-indicator status-disconnected';
600
+ }
601
+ }
602
 
603
+ function sendMessage() {
604
+ if (ws && ws.readyState === WebSocket.OPEN) {
605
+ const message = document.getElementById('messageInput').value;
606
+ if (message.trim()) {
607
+ ws.send(JSON.stringify({message: message}));
608
+ document.getElementById('messageInput').value = '';
609
  }
610
+ } else {
611
+ addMessage({message: 'Connexion WebSocket non établie', type: 'error'}, 'error');
612
+ }
613
+ }
614
 
615
+ function addMessage(data, type) {
616
+ const messages = document.getElementById('messages');
617
+ const messageDiv = document.createElement('div');
618
+ messageDiv.className = `message message-${type}`;
619
+
620
+ const timestamp = new Date().toLocaleTimeString();
621
+ messageDiv.innerHTML = `
622
+ <span class="message-timestamp">${timestamp}</span>
623
+ <strong>${data.type || 'message'}:</strong> ${data.message || JSON.stringify(data, null, 2)}
624
+ `;
625
+
626
+ messages.appendChild(messageDiv);
627
+ messages.scrollTop = messages.scrollHeight;
628
+ }
629
 
630
+ function clearMessages() {
631
+ document.getElementById('messages').innerHTML = '';
632
+ }
 
 
 
 
633
 
634
+ // Fonctions API REST
635
+ async function sendUserNotification() {
636
+ const userId = document.getElementById('targetUserId').value;
637
+ const message = document.getElementById('userNotificationMessage').value;
638
+
639
+ try {
640
+ const response = await fetch(`${baseUrl}/notifications/user/${userId}`, {
641
+ method: 'POST',
642
+ headers: {'Content-Type': 'application/json'},
643
+ body: message
644
+ });
645
+
646
+ const result = await response.json();
647
+ addMessage({message: `Notification utilisateur envoyée: ${JSON.stringify(result)}`}, 'success');
648
+ } catch (error) {
649
+ addMessage({message: `Erreur: ${error.message}`}, 'error');
650
+ }
651
+ }
652
+
653
+ async function sendRoleNotification() {
654
+ const role = document.getElementById('targetRole').value;
655
+ const message = document.getElementById('roleNotificationMessage').value;
656
+
657
+ try {
658
+ const response = await fetch(`${baseUrl}/notifications/role/${role}`, {
659
+ method: 'POST',
660
+ headers: {'Content-Type': 'application/json'},
661
+ body: message
662
+ });
663
+
664
+ const result = await response.json();
665
+ addMessage({message: `Notification rôle envoyée: ${JSON.stringify(result)}`}, 'success');
666
+ } catch (error) {
667
+ addMessage({message: `Erreur: ${error.message}`}, 'error');
668
+ }
669
+ }
670
+
671
+ async function sendBroadcast() {
672
+ const message = document.getElementById('broadcastMessage').value;
673
+
674
+ try {
675
+ const response = await fetch(`${baseUrl}/notifications/broadcast`, {
676
+ method: 'POST',
677
+ headers: {'Content-Type': 'application/json'},
678
+ body: message
679
+ });
680
+
681
+ const result = await response.json();
682
+ addMessage({message: `Diffusion envoyée: ${JSON.stringify(result)}`}, 'success');
683
+ } catch (error) {
684
+ addMessage({message: `Erreur: ${error.message}`}, 'error');
685
+ }
686
+ }
687
+
688
+ async function sendTargetedNotification() {
689
+ const targetType = document.getElementById('targetType').value;
690
+ const targetValue = document.getElementById('targetValue').value;
691
+ const message = JSON.parse(document.getElementById('targetedMessage').value);
692
+
693
+ const payload = {
694
+ target_type: targetType,
695
+ target_value: targetValue,
696
+ notification: message
697
+ };
698
+
699
+ try {
700
+ const response = await fetch(`${baseUrl}/notifications/send`, {
701
+ method: 'POST',
702
+ headers: {'Content-Type': 'application/json'},
703
+ body: JSON.stringify(payload)
704
+ });
705
+
706
+ const result = await response.json();
707
+ addMessage({message: `Notification ciblée envoyée: ${JSON.stringify(result)}`}, 'success');
708
+ } catch (error) {
709
+ addMessage({message: `Erreur: ${error.message}`}, 'error');
710
+ }
711
+ }
712
+
713
+ async function getStats() {
714
+ try {
715
+ const response = await fetch(`${baseUrl}/stats`);
716
+ const stats = await response.json();
717
+
718
+ document.getElementById('connectedClients').textContent = stats.connected_clients || 0;
719
+ document.getElementById('totalNotifications').textContent = stats.total_notifications || 0;
720
+ document.getElementById('activeSubscriptions').textContent = stats.active_subscriptions || 0;
721
+
722
+ addMessage({message: `Statistiques mises à jour: ${JSON.stringify(stats)}`}, 'system');
723
+ } catch (error) {
724
+ addMessage({message: `Erreur récupération stats: ${error.message}`}, 'error');
725
+ }
726
+ }
727
+
728
+ async function getClients() {
729
+ try {
730
+ const response = await fetch(`${baseUrl}/clients`);
731
+ const data = await response.json();
732
+
733
+ document.getElementById('clientDetails').textContent = JSON.stringify(data, null, 2);
734
+ addMessage({message: `${data.clients.length} clients connectés`}, 'system');
735
+ } catch (error) {
736
+ addMessage({message: `Erreur récupération clients: ${error.message}`}, 'error');
737
+ }
738
+ }
739
 
740
+ // Gestion des touches Enter
741
+ document.getElementById('messageInput').addEventListener('keypress', function(e) {
742
+ if (e.key === 'Enter') {
743
+ sendMessage();
744
+ }
745
+ });
746
+
747
+ // Connexion automatique au chargement de la page
748
+ window.onload = function() {
749
+ connect();
750
+ getStats();
751
+ };
752
+
753
+ // Actualisation automatique des stats toutes les 30 secondes
754
+ setInterval(getStats, 30000);
755
+ </script>
756
+ </body>
757
+ </html>
758
  """
759
 
760
 
requirements.txt CHANGED
@@ -2,4 +2,4 @@ colorama
2
  PyYAML
3
  pydantic
4
  fastapi
5
- uvicorn
 
2
  PyYAML
3
  pydantic
4
  fastapi
5
+ uvicorn[standard]