droplyvictor89 commited on
Commit
76ed3d6
·
verified ·
1 Parent(s): 82731f2

Upload 5 files

Browse files
Files changed (2) hide show
  1. Dockerfile +19 -10
  2. index.html +1194 -1007
Dockerfile CHANGED
@@ -1,14 +1,23 @@
1
- FROM python:3.9
2
 
3
- # Setăm folderul de lucru
4
- WORKDIR /code
5
 
6
- # Copiem lista de librării și le instalăm
7
- COPY ./requirements.txt /code/requirements.txt
8
- RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
 
 
 
 
9
 
10
- # Copiem restul fișierelor (app.py, credentials.json, etc.)
11
- COPY . .
12
 
13
- # Pornim serverul pe portul 7860
14
- CMD ["python", "app.py"]
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
 
3
+ WORKDIR /app
 
4
 
5
+ # Copiem fișierele
6
+ COPY requirements.txt .
7
+ COPY app.py .
8
+ COPY index.html .
9
+ COPY sw.js .
10
+ COPY logo.png .
11
+ COPY notification.mp3 .
12
 
13
+ # Instalăm dependențele
14
+ RUN pip install --no-cache-dir -r requirements.txt
15
 
16
+ # Creăm directorul pentru uploads
17
+ RUN mkdir -p uploads
18
+
19
+ # Expunem portul 7860 (standard HuggingFace)
20
+ EXPOSE 7860
21
+
22
+ # Rulăm aplicația
23
+ CMD ["python", "app.py"]
index.html CHANGED
@@ -1,280 +1,432 @@
1
  <!DOCTYPE html>
2
- <html lang="ro">
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <link rel="icon" type="image/png" href="/logo.png">
7
- <title>DROPLY! v2.1</title>
8
  <style>
9
- /* --- DESIGN SISTEMIC --- */
10
- :root {
11
- --bg: #000;
12
- --card: #0a0a0a;
13
- --accent: #fff;
14
- --border: #1a1a1a;
15
- --text-muted: #666;
16
- --danger: #ff4444;
17
- --terminal: #00ff41;
18
- --success: #00ff00;
19
  }
20
 
21
- * { box-sizing: border-box; }
 
 
 
 
 
 
 
22
 
23
  body {
24
- background-color: var(--bg);
25
- color: var(--accent);
26
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
27
- margin: 0;
28
  display: flex;
29
  height: 100vh;
30
  overflow: hidden;
 
31
  }
32
 
33
- /* --- SIDEBAR --- */
 
34
  nav {
35
- width: 240px;
36
- background: #050505;
37
- border-right: 1px solid var(--border);
38
  display: none;
39
  flex-direction: column;
40
- padding: 20px 0;
41
- flex-shrink: 0;
42
  }
43
 
44
- .nav-logo {
45
- padding: 0 25px 30px;
46
- font-size: 20px;
47
- font-weight: 900;
48
- letter-spacing: 2px;
 
 
 
 
 
 
49
  }
 
50
  .nav-version {
51
- padding: 0 25px 10px;
52
- font-size: 9px;
53
- color: var(--terminal);
54
- font-family: monospace;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  }
56
 
57
  .nav-item {
58
- padding: 15px 25px;
59
  cursor: pointer;
60
- color: var(--text-muted);
61
  text-transform: uppercase;
62
- font-size: 11px;
63
- font-weight: bold;
64
- transition: 0.3s;
65
- border-left: 3px solid transparent;
 
 
 
 
 
 
 
66
  }
67
 
68
- .nav-item:hover, .nav-item.active {
69
- color: #fff;
70
- background: #111;
71
- border-left-color: #fff;
72
  }
73
 
74
- /* --- ZONA PRINCIPALĂ --- */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  main {
76
- flex-grow: 1;
77
  overflow-y: auto;
78
- padding: 40px;
79
  display: flex;
80
  flex-direction: column;
81
  align-items: center;
82
  }
83
 
84
- .section {
85
- width: 100%;
86
- max-width: 600px;
87
- display: none;
88
- animation: fadeIn 0.4s ease;
 
 
 
 
89
  }
90
- .section.active { display: block; }
91
 
92
- @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
 
 
 
 
 
 
 
 
 
93
 
94
- /* --- CARDURI & INPUTS --- */
95
- .card {
96
- background: var(--card);
97
- border: 1px solid var(--border);
98
- padding: 25px;
99
- border-radius: 4px;
100
- margin-bottom: 20px;
101
- word-wrap: break-word;
102
- overflow-wrap: break-word;
103
  }
104
 
105
- h2 {
106
- font-size: 16px;
107
- margin-bottom: 20px;
108
- text-transform: uppercase;
109
- border-left: 3px solid #fff;
110
- padding-left: 10px;
111
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
 
113
- label {
114
- display: block;
115
- font-size: 10px;
116
- color: var(--text-muted);
117
- margin-bottom: 8px;
118
- text-transform: uppercase;
 
 
119
  }
120
 
121
  input, textarea, select {
122
- width: 100%;
123
- background: #000;
124
- border: 1px solid var(--border);
125
- color: #fff;
126
- padding: 12px;
127
- margin-bottom: 15px;
128
- border-radius: 2px;
129
- font-size: 14px;
130
- box-sizing: border-box;
131
  outline: none;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  font-family: inherit;
133
  }
134
-
135
- input:focus, textarea:focus, select:focus { border-color: var(--terminal); }
136
 
 
 
137
  button {
138
- background: #fff;
139
- color: #000;
140
- border: none;
141
- padding: 15px;
142
- font-weight: bold;
143
- text-transform: uppercase;
144
- cursor: pointer;
145
- width: 100%;
146
- transition: 0.2s;
147
- font-size: 12px;
 
148
  }
149
- button:hover { background: #ccc; }
150
-
 
 
 
 
 
 
 
 
151
  .btn-secondary {
152
- background: transparent;
153
- color: #fff;
154
- border: 1px solid #333;
155
  }
156
- .btn-secondary:hover { background: #111; }
157
-
 
 
 
 
158
  .btn-danger {
159
- background: var(--danger);
160
- color: #fff;
 
 
 
 
 
 
 
161
  }
162
- .btn-danger:hover { background: #cc0000; }
163
 
164
- .btn-small {
165
- width: auto;
166
- padding: 5px 10px;
167
- font-size: 10px;
168
- margin-left: 5px;
169
  }
170
 
171
- /* --- TERMINAL LOGS --- */
 
172
  #terminalBox {
173
- background: #000;
174
- border: 1px solid #111;
175
- color: var(--terminal);
176
  font-family: 'Courier New', monospace;
177
- font-size: 11px;
178
- padding: 15px;
179
- height: 150px;
180
  overflow-y: auto;
181
- margin-top: 15px;
182
- border-radius: 4px;
183
- line-height: 1.5;
184
- }
185
-
186
- /* --- MESAJE & GRUP --- */
187
- .msg-card, .group-msg {
188
- border-bottom: 1px solid #111;
189
- padding: 10px 0;
190
- word-wrap: break-word;
191
- overflow-wrap: break-word;
192
- word-break: break-word;
193
- position: relative;
194
  }
 
 
195
 
 
 
 
 
 
 
 
 
 
 
196
  .msg-actions {
197
  position: absolute;
198
- top: 10px;
199
  right: 0;
200
  }
201
-
202
- .group-header {
203
- font-size: 10px;
204
- color: var(--terminal);
205
- margin-bottom: 2px;
 
 
206
  }
207
- .group-time {
208
- font-size: 9px;
209
- color: #444;
 
210
  }
211
 
212
- /* --- CONTACTE & LISTE --- */
213
- .item-list {
214
- border-bottom: 1px solid #111;
215
- padding: 10px 0;
216
- display: flex;
217
- justify-content: space-between;
218
- align-items: center;
219
  }
220
- .item-info { font-size: 13px; }
221
 
222
- /* --- BADGES --- */
 
223
  .badge {
224
- background: #111;
225
- border: 1px solid var(--terminal);
226
- color: var(--terminal);
227
- padding: 5px 10px;
228
- font-size: 10px;
229
- display: inline-block;
230
- margin-bottom: 15px;
231
  cursor: pointer;
 
 
 
 
232
  }
233
- .badge.active { background: var(--terminal); color: #000; }
234
-
 
 
 
 
 
 
 
 
 
235
  .admin-badge {
236
- background: #ff0000;
237
- color: #fff;
238
- border: none;
239
  }
240
 
241
- /* --- NOTIFICĂRI --- */
 
242
  #notify {
243
- position: fixed;
244
- top: 20px;
245
- right: 20px;
246
- background: #fff;
247
- color: #000;
248
- padding: 10px 20px;
249
- font-weight: bold;
250
- z-index: 1000;
251
  display: none;
252
- border-radius: 4px;
253
- box-shadow: 0 4px 12px rgba(255,255,255,0.2);
 
254
  }
255
-
256
- /* Warning Banner */
257
  #warningBanner {
258
  position: fixed;
259
  top: 0;
260
  left: 0;
261
  right: 0;
262
- background: #ff0000;
263
- color: #fff;
264
- padding: 15px;
265
  text-align: center;
266
- font-weight: bold;
267
  z-index: 2000;
268
  display: none;
269
- animation: pulse 1s infinite;
270
- }
271
-
272
- @keyframes pulse {
273
- 0%, 100% { opacity: 1; }
274
- 50% { opacity: 0.7; }
275
  }
276
 
277
- /* --- MODAL POPUP --- */
 
278
  .modal {
279
  display: none;
280
  position: fixed;
@@ -282,66 +434,132 @@
282
  left: 0;
283
  right: 0;
284
  bottom: 0;
285
- background: rgba(0,0,0,0.9);
286
  z-index: 1500;
287
  align-items: center;
288
  justify-content: center;
289
  }
290
-
291
- .modal.active { display: flex; }
292
-
 
 
293
  .modal-content {
294
- background: var(--card);
295
- border: 1px solid var(--border);
296
- padding: 30px;
297
- max-width: 500px;
298
  width: 90%;
299
  max-height: 80vh;
300
  overflow-y: auto;
301
  position: relative;
302
- border-radius: 4px;
303
  }
304
-
305
  .modal-close {
306
  position: absolute;
307
- top: 15px;
308
- right: 15px;
309
- font-size: 24px;
310
  cursor: pointer;
311
- color: var(--text-muted);
 
 
 
312
  }
313
- .modal-close:hover { color: #fff; }
314
 
315
- /* --- PING INDICATOR --- */
316
- .ping-indicator {
317
- display: inline-block;
318
- width: 8px;
319
- height: 8px;
320
- border-radius: 50%;
321
- background: var(--success);
322
- margin-right: 5px;
323
- animation: blink 2s infinite;
324
  }
 
 
325
 
326
- @keyframes blink {
 
 
 
 
 
 
 
 
 
327
  0%, 100% { opacity: 1; }
328
  50% { opacity: 0.3; }
329
  }
330
 
331
- /* --- RESPONSIVE --- */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
332
  @media (max-width: 768px) {
333
- body { flex-direction: column; }
334
- nav {
335
- width: 100%;
336
- height: auto;
337
- flex-direction: row;
338
- overflow-x: auto;
339
- border-right: none;
340
- border-bottom: 1px solid var(--border);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
341
  }
342
- .nav-logo, .nav-version { display: none; }
343
- main { padding: 20px; }
344
- .section { max-width: 100%; }
345
  }
346
  </style>
347
  </head>
@@ -349,206 +567,254 @@
349
 
350
  <audio id="notifSound" src="/notification.mp3" preload="auto"></audio>
351
 
352
- <div id="notify">ACȚIUNE REUȘITĂ</div>
353
- <div id="warningBanner">
354
- ⚠️ ATENȚIE! Serverul DROPLY! intră în mentenanță. Ne cerem scuze pentru inconvenient! ⚠️
355
- </div>
356
 
357
- <!-- MODAL pentru vizualizare mesaj inbox -->
358
  <div id="messageModal" class="modal">
359
  <div class="modal-content">
360
- <span class="modal-close" onclick="closeMessageModal()">&times;</span>
361
- <h2 id="modalFrom">De la:</h2>
362
- <div id="modalTime" style="font-size: 11px; color: #666; margin-bottom: 15px;"></div>
363
- <div id="modalContent" style="margin-bottom: 15px; white-space: pre-wrap;"></div>
364
  <div id="modalFile"></div>
365
  </div>
366
  </div>
367
 
368
  <nav id="sidebar">
369
- <div class="nav-logo">DROPLY!</div>
370
- <div class="nav-version">v2.1 STABLE</div>
371
- <div class="nav-item active" onclick="showSection('home')">Dashboard</div>
372
- <div class="nav-item" onclick="showSection('inbox')">Inbox</div>
373
- <div class="nav-item" onclick="showSection('group')">Grup</div>
374
- <div class="nav-item" onclick="showSection('send')">Trimite</div>
375
- <div class="nav-item" onclick="showSection('contacts')">Agendă</div>
376
- <div class="nav-item" onclick="showSection('settings')">Setări</div>
377
- <div class="nav-item" id="adminNavItem" style="display:none; color: #ff0000;" onclick="showSection('admin')">Admin Panel</div>
378
- <div class="nav-item" style="margin-top: auto; color: var(--danger);" onclick="logout()">Ieșire</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
379
  </nav>
380
 
381
- <main> <!-- SECȚIUNE LOGIN -->
 
382
  <div id="authSection" class="section active">
383
  <div class="card">
384
- <h2>Acces DROPLY! v2.1</h2>
385
- <label>Email</label>
386
- <input type="text" id="loginEmail" placeholder="Ex: user@droply.md">
387
- <label>Parolă</label>
388
  <input type="password" id="loginPass">
389
- <button onclick="login()">Conectare</button>
 
 
390
  </div>
391
  <div class="card">
392
- <h2>Creează Cont DROPLY!</h2>
393
  <input type="text" id="regUser" placeholder="Username">
394
- <input type="password" id="regPass" placeholder="Parolă">
395
- <button class="btn-secondary" onclick="register()">Generează Cont</button>
 
 
396
  </div>
397
  </div>
398
 
399
  <!-- DASHBOARD -->
400
  <div id="home" class="section">
401
  <div class="card">
402
- <h2 id="userDisplay">Salut!</h2>
403
- <div id="idBadge" style="font-family: monospace; color: #555;">ID: 0670...</div>
404
- <div id="adminBadgeDisplay" style="display:none; margin-top: 10px;">
405
  <span class="badge admin-badge">ADMIN ACCESS</span>
406
  </div>
407
 
408
- <h2 style="margin-top: 30px; border-left: 3px solid var(--terminal);">
409
- <span class="ping-indicator"></span>Live System Logs
410
- </h2>
411
  <div id="terminalBox">
412
- [SYSTEM] Waiting for authentication...
413
  </div>
414
-
415
- <button style="margin-top: 20px;" onclick="showAbout()">📋 About v2.1</button>
416
  </div>
417
  </div>
418
 
419
  <!-- INBOX -->
420
  <div id="inbox" class="section">
421
  <div class="card">
422
- <h2>Inbox & AI</h2>
423
 
424
- <div id="saraToggle" class="badge" onclick="toggleSara()">SARA AI: OFF</div>
 
 
425
 
426
- <div id="inboxList" style="max-height: 400px; overflow-y: auto;">
427
- Inbox gol. ┐('~` )┌
428
  </div>
429
  </div>
430
  </div>
431
 
432
- <!-- GRUP GLOBAL -->
433
  <div id="group" class="section">
434
  <div class="card">
435
- <h2>Grup Global DROPLY!</h2>
436
 
437
- <div id="groupAccessInfo" style="margin-bottom: 15px; padding: 10px; background: #111; border-left: 3px solid var(--terminal);">
438
- <div id="groupMemberText" style="display:none;">
439
- Ai acces la grup
440
  </div>
441
- <div id="groupNonMemberText" style="display:none;">
442
- Nu ai acces la grup. Poți vizualiza mesajele dar nu poți trimite.
443
- <button style="margin-top: 10px;" onclick="requestGroupInvite()">Cere Invitație</button>
 
 
444
  </div>
445
  </div>
446
 
447
- <div id="groupList" style="max-height: 300px; overflow-y: auto; margin-bottom: 15px;"></div>
448
 
449
  <div id="groupInputSection">
450
- <textarea id="groupMsgInput" placeholder="Scrie mesaj..." style="height: 80px;"></textarea>
451
- <button onclick="sendGroup()">Trimite în Grup</button>
 
 
452
  </div>
453
  </div>
454
  </div>
455
 
456
- <!-- TRIMITE MESAJ PRIVAT -->
457
  <div id="send" class="section">
458
  <div class="card">
459
- <h2>Trimite Mesaj Privat</h2>
460
- <label>ID Destinatar</label>
461
- <input type="text" id="targetDest" placeholder="Ex: a1b2c3d4e5f6">
462
- <label>Mesaj</label>
463
- <textarea id="targetMsg" placeholder="Scrie mesajul..." style="height: 100px;"></textarea>
464
- <label>Fișier Atașat (Opțional)</label>
465
  <input type="file" id="targetFile">
466
- <button onclick="sendMail()">📤 Trimite</button>
 
 
467
 
468
- <button class="btn-secondary" style="margin-top: 15px;" onclick="reportBug()">
469
- 🐛 Raportare Bug
470
  </button>
471
  </div>
472
  </div>
473
 
474
- <!-- AGENDĂ CONTACTE -->
475
  <div id="contacts" class="section">
476
  <div class="card">
477
- <h2>Agendă Contacte</h2>
478
- <input type="text" id="cName" placeholder="Nume contact">
479
- <input type="text" id="cId" placeholder="ID utilizator">
480
- <button onclick="saveContact()">Salvează Contact</button>
 
 
481
 
482
- <h2 style="margin-top: 30px;">Lista Contacte</h2>
483
  <div id="contactList"></div>
484
  </div>
485
  </div>
486
 
487
- <!-- SETĂRI -->
488
  <div id="settings" class="section">
489
  <div class="card">
490
- <h2>Setări Cont</h2>
491
 
492
- <label>Vizibilitate Profil</label>
493
  <select id="setVis">
494
  <option value="public">Public</option>
495
- <option value="private">Privat</option>
496
  </select>
497
 
498
- <label>Cod Secret (pentru profil privat)</label>
499
- <input type="text" id="setSecret" placeholder="Ex: ABC123">
500
 
501
- <label>Notificări Push</label>
502
  <select id="setNotif">
503
- <option value="1">Activate</option>
504
- <option value="0">Dezactivate</option>
505
  </select>
506
 
507
- <button onclick="updateSettings()">💾 Salvează Setări</button>
 
 
508
  </div>
509
 
510
  <div class="card">
511
- <h2>Resetare Parolă</h2>
512
- <label>Parolă Veche</label>
513
- <input type="password" id="oldPass" placeholder="Parolă actuală">
514
- <label>Parolă Nouă</label>
515
- <input type="password" id="newPass" placeholder="Parolă nouă">
516
- <button onclick="changePassword()">🔒 Schimbă Parola</button>
 
 
517
  </div>
518
 
519
  <div class="card">
520
- <h2>Server Status</h2>
521
- <div style="display: flex; align-items: center; margin-bottom: 15px;">
522
- <span class="ping-indicator"></span>
523
- <span id="serverPing">Checking...</span>
524
  </div>
525
- <button onclick="checkPing()">🔄 Verifică Ping</button>
 
 
526
  </div>
527
  </div>
528
 
529
- <!-- ADMIN PANEL -->
530
  <div id="admin" class="section">
531
  <div class="card">
532
- <h2 style="color: #ff0000;">🔐 Admin Panel</h2>
533
 
534
- <h2 style="margin-top: 20px;">Utilizatori Conectați</h2>
535
- <div id="adminUsersList" style="max-height: 200px; overflow-y: auto;"></div>
536
 
537
- <button onclick="loadAdminData()" style="margin-top: 15px;">🔄 Refresh Date</button>
 
 
538
  </div>
539
 
540
  <div class="card">
541
- <h2>Cereri Invitații Grup</h2>
542
- <div id="adminInvitesList" style="max-height: 200px; overflow-y: auto;"></div>
543
  </div>
544
 
545
  <div class="card">
546
- <h2 style="color: #ff0000;">⚠️ Maintenance Mode</h2>
547
- <p style="font-size: 12px; color: #666; margin-bottom: 15px;">
548
- Această acțiune va trimite un warning către TOȚI utilizatorii și va închide serverul în 60 de secunde.
 
 
549
  </p>
550
  <button class="btn-danger" onclick="triggerMaintenance()">
551
- 🚨 ACTIVEAZĂ MENTENANȚĂ
552
  </button>
553
  </div>
554
  </div>
@@ -556,787 +822,708 @@
556
  </main>
557
 
558
  <script>
559
- let lastMsgCount = 0;
560
- let isUserLogged = false;
561
- let saraEnabled = false;
562
- let currentUserData = null;
563
- let serviceWorkerRegistration = null;
564
 
565
- // ===== SERVICE WORKER & NOTIFICĂRI ÎMBUNĂTĂȚITE =====
566
-
567
- if ('serviceWorker' in navigator) {
568
- navigator.serviceWorker.register('/sw.js')
569
- .then(registration => {
570
- console.log('✅ Service Worker înregistrat:', registration);
571
- serviceWorkerRegistration = registration;
572
- addLog('SYSTEM: Service Worker active');
573
-
574
- // Cere permisiune pentru notificări
575
- requestNotificationPermission();
576
-
577
- // Ascultă mesaje de la Service Worker
578
- navigator.serviceWorker.addEventListener('message', event => {
579
- console.log('📩 Message from SW:', event.data);
580
-
581
- if (event.data.type === 'PLAY_SOUND') {
582
- playNotificationSound();
583
- }
584
-
585
- if (event.data.type === 'CHECK_INBOX') {
586
- refreshInbox();
587
- }
588
- });
589
- })
590
- .catch(err => {
591
- console.error('❌ Service Worker eșuat:', err);
592
- addLog('ERROR: Service Worker failed');
593
- });
594
- }
595
 
596
- // Funcție pentru cerere permisiune notificări
597
- async function requestNotificationPermission() {
598
- if (!('Notification' in window)) {
599
- console.log('Browser-ul nu suportă notificări');
600
- addLog('WARNING: Browser does not support notifications');
601
- return false;
602
- }
603
-
604
- if (Notification.permission === 'granted') {
605
- console.log('✅ Notificări deja permise');
606
- addLog('SYSTEM: Notifications enabled');
607
- return true;
608
- }
609
-
610
- if (Notification.permission !== 'denied') {
611
- const permission = await Notification.requestPermission();
612
- if (permission === 'granted') {
613
- console.log('✅ Permisiune notificări acordată');
614
- addLog('SYSTEM: Notifications enabled');
615
-
616
- // Test notificare
617
- new Notification('DROPLY! v2.1', {
618
- body: 'Notificările funcționează! 🎉',
619
- icon: '/logo.png',
620
- tag: 'test-notification'
621
- });
622
- return true;
623
  } else {
624
- console.log('❌ Permisiune notificări refuzată');
625
- addLog('WARNING: Notification permission denied');
626
- return false;
627
  }
628
  }
629
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
630
  return false;
631
  }
632
-
633
- // Funcție pentru redare sunet notificare
634
- function playNotificationSound() {
635
- try {
636
- const audio = document.getElementById('notifSound');
637
- if (audio) {
638
- audio.currentTime = 0;
639
- audio.play().catch(e => {
640
- console.log('Autoplay blocat:', e);
641
- });
642
- }
643
- } catch (e) {
644
- console.error('Eroare sunet:', e);
645
- }
646
  }
647
-
648
- // Funcție pentru trimitere notificare către Service Worker
649
- function sendNotificationToSW(from, content) {
650
- if (serviceWorkerRegistration && serviceWorkerRegistration.active) {
651
- serviceWorkerRegistration.active.postMessage({
652
- type: 'NEW_MESSAGE',
653
- from: from,
654
- content: content
655
  });
 
656
  }
657
  }
658
-
659
- // Auto-login
660
- window.addEventListener('DOMContentLoaded', async () => {
661
- const sessionToken = localStorage.getItem('droply_session_token');
662
-
663
- if (sessionToken) {
664
- addLog('SYSTEM: Checking saved session...');
665
-
666
- try {
667
- const response = await fetch('/api/check_session', {
668
- method: 'POST',
669
- headers: {'Content-Type': 'application/json'},
670
- body: JSON.stringify({session_token: sessionToken})
671
- });
672
-
673
- const data = await response.json();
674
-
675
- if (data.success) {
676
- performLogin(data);
677
- } else {
678
- localStorage.removeItem('droply_session_token');
679
- addLog('SYSTEM: Session expired, please login.');
680
- }
681
- } catch(e) {
682
- addLog('ERROR: Failed to check session');
683
- }
684
- } else {
685
- addLog('SYSTEM: Waiting for authentication...');
686
  }
687
-
688
- // Check pentru shutdown warning
689
- setInterval(checkShutdown, 5000);
690
- });
 
 
 
 
 
 
 
 
691
 
692
- // ===== FUNCȚII UTILITARE =====
 
 
693
 
694
- function showSection(id) {
695
- document.querySelectorAll('.section').forEach(s => s.classList.remove('active'));
696
- document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
697
- document.getElementById(id).classList.add('active');
698
 
699
- const items = document.querySelectorAll('.nav-item');
700
- items.forEach(item => {
701
- if(item.innerText.toLowerCase().includes(id) ||
702
- (id === 'home' && item.innerText.includes('Dashboard')) ||
703
- (id === 'admin' && item.innerText.includes('Admin'))) {
704
- item.classList.add('active');
 
 
 
 
 
 
 
 
705
  }
706
- });
707
-
708
- addLog(`Navigated to: ${id.toUpperCase()}`);
709
-
710
- // Dacă e admin panel, încarcă datele
711
- if(id === 'admin' && currentUserData && currentUserData.is_admin) {
712
- loadAdminData();
713
  }
714
  }
 
 
 
715
 
716
- function addLog(text) {
717
- const terminal = document.getElementById('terminalBox');
718
- const time = new Date().toLocaleTimeString();
719
- terminal.innerHTML += `<div>[${time}] ${text}</div>`;
720
- terminal.scrollTop = terminal.scrollHeight;
721
- }
722
 
723
- function showNotify(text) {
724
- const n = document.getElementById('notify');
725
- n.innerText = text;
726
- n.style.display = 'block';
727
- setTimeout(() => n.style.display = 'none', 3000);
 
 
 
 
 
 
 
 
 
 
728
  }
729
-
730
- function logout() {
731
- localStorage.removeItem('droply_session_token');
732
- location.reload();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
733
  }
 
734
 
735
- // ===== LOGIN & REGISTER =====
 
 
736
 
737
- async function login() {
738
- const email = document.getElementById('loginEmail').value;
739
- const pass = document.getElementById('loginPass').value;
740
-
741
- const r = await fetch('/api/login', {
742
- method: 'POST',
743
- headers: {'Content-Type': 'application/json'},
744
- body: JSON.stringify({email: email, password: pass})
745
- });
746
-
747
- const d = await r.json();
748
-
749
- if(d.success) {
750
- localStorage.setItem('droply_session_token', d.session_token);
751
- performLogin(d);
752
- } else {
753
- addLog(`FAILED: Login attempt for ${email}`);
754
- alert("Eroare la logare!");
755
- }
756
  }
757
-
758
- function performLogin(data) {
759
- isUserLogged = true;
760
- currentUserData = data.user;
761
-
762
- document.getElementById('authSection').classList.remove('active');
763
- document.getElementById('sidebar').style.display = 'flex';
764
- document.getElementById('home').classList.add('active');
765
- document.getElementById('userDisplay').innerText = data.email;
766
- document.getElementById('idBadge').innerText = "ID: " + data.user.id;
767
- document.getElementById('setVis').value = data.user.visibility;
768
- document.getElementById('setSecret').value = data.user.secret_code;
769
- document.getElementById('setNotif').value = data.user.notif_enabled;
770
-
771
- // Sara AI
772
- if(data.user.sara_ai) {
773
- saraEnabled = true;
774
- document.getElementById('saraToggle').classList.add('active');
775
- document.getElementById('saraToggle').innerText = "SARA AI: ONLINE";
776
- }
777
-
778
- // Admin access
779
- if(data.user.is_admin) {
780
- document.getElementById('adminNavItem').style.display = 'block';
781
- document.getElementById('adminBadgeDisplay').style.display = 'block';
782
- addLog('ADMIN: Full access granted');
783
- }
784
-
785
- // Group access
786
- if(data.user.is_group_member) {
787
- document.getElementById('groupMemberText').style.display = 'block';
788
- document.getElementById('groupNonMemberText').style.display = 'none';
789
- } else {
790
- document.getElementById('groupMemberText').style.display = 'none';
791
- document.getElementById('groupNonMemberText').style.display = 'block';
792
- document.getElementById('groupInputSection').style.display = 'none';
793
- }
794
-
795
- addLog(`SUCCESS: Session started for ${data.email}`);
796
- addLog(`UID IDENTIFIED: ${data.user.id}`);
797
-
798
- // Auto-refresh
799
- setInterval(() => {
800
- refreshInbox();
801
- refreshGroup();
802
- }, 500);
803
-
804
- refreshInbox();
805
- loadContacts();
806
- checkPing();
807
  }
808
-
809
- async function register() {
810
- const r = await fetch('/api/register', {
811
- method: 'POST',
812
- headers: {'Content-Type': 'application/json'},
813
- body: JSON.stringify({
814
- username: document.getElementById('regUser').value,
815
- password: document.getElementById('regPass').value
816
- })
817
- });
818
- const d = await r.json();
819
- if(d.success) {
820
- alert("Cont creat! Email: " + d.email + " | ID: " + d.uid);
821
- addLog(`IDENTITY GENERATED: ${d.uid}`);
822
- } else {
823
- alert("Eroare: " + d.error);
824
- }
825
  }
826
-
827
- // ===== SARA AI =====
828
 
829
- function toggleSara() {
830
- saraEnabled = !saraEnabled;
831
- const btn = document.getElementById('saraToggle');
832
- if(saraEnabled) {
833
- btn.classList.add('active');
834
- btn.innerText = "SARA AI: ONLINE";
835
- addLog("SARA AI: Activated auto-responder.");
836
- updateSettings();
837
- } else {
838
- btn.classList.remove('active');
839
- btn.innerText = "SARA AI: OFF";
840
- addLog("SARA AI: Deactivated.");
841
- updateSettings();
842
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
843
  }
 
844
 
 
845
 
846
- // ===== INBOX =====
 
847
 
848
- async function refreshInbox() {
849
- if(!isUserLogged) return;
 
850
 
851
- try {
852
- const r = await fetch('/api/get_inbox');
853
- const list = await r.json();
854
-
855
- // Verificare mesaje noi
856
- if(list.length > lastMsgCount) {
857
- if(lastMsgCount !== 0 && currentUserData && currentUserData.notif_enabled) {
858
- const lastMessage = list[list.length-1];
859
-
860
- // Redăm sunetul
861
- playNotificationSound();
862
-
863
- // Arătăm banner-ul
864
- showNotify("MESAJ NOU!");
865
-
866
- // Log în terminal
867
- addLog(`INCOMING: New message from ${lastMessage.from}`);
868
-
869
- // Browser notification (funcționează și când tab-ul e în fundal)
870
- if('Notification' in window && Notification.permission === 'granted') {
871
- // Verificăm dacă documentul e vizibil
872
- if (document.hidden) {
873
- // Tab-ul e în fundal, folosim Service Worker
874
- sendNotificationToSW(lastMessage.from, lastMessage.content);
875
- } else {
876
- // Tab-ul e activ, notificare directă
877
- new Notification('DROPLY! - Mesaj Nou', {
878
- body: `De la: ${lastMessage.from}\n${lastMessage.content.substring(0, 50)}${lastMessage.content.length > 50 ? '...' : ''}`,
879
- icon: '/logo.png',
880
- badge: '/logo.png',
881
- vibrate: [200, 100, 200],
882
- tag: 'new-message-' + Date.now(),
883
- requireInteraction: false
884
- });
885
- }
886
  }
887
  }
888
- lastMsgCount = list.length;
889
  }
890
-
891
- const container = document.getElementById('inboxList');
892
- container.innerHTML = list.length ? "" : "Inbox gol. ┐('~` )┌";
893
-
894
- list.reverse().forEach(m => {
895
- const div = document.createElement('div');
896
- div.className = "msg-card";
897
- div.innerHTML = `
898
- <div class="msg-actions">
899
- <button class="btn-small btn-danger" onclick="deleteMessage(${m.id})">🗑️</button>
900
- </div>
901
- <small style="cursor: pointer;" onclick="openMessageModal(${m.id}, '${m.from}', '${m.timestamp}', \`${m.content.replace(/`/g, '\\`')}\`, '${m.file || ''}')">
902
- ${m.from} | ${m.timestamp}
903
- </small><br>
904
- <div style="cursor: pointer; margin-top: 5px;" onclick="openMessageModal(${m.id}, '${m.from}', '${m.timestamp}', \`${m.content.replace(/`/g, '\\`')}\`, '${m.file || ''}')">
905
- ${m.content.substring(0, 50)}${m.content.length > 50 ? '...' : ''}
906
- </div>
907
- `;
908
- container.appendChild(div);
909
- });
910
- } catch (e) {
911
- console.error("Refresh inbox error:", e);
912
  }
913
- }
914
 
915
- function openMessageModal(id, from, time, content, file) {
916
- document.getElementById('modalFrom').innerText = `De la: ${from}`;
917
- document.getElementById('modalTime').innerText = time;
918
- document.getElementById('modalContent').innerText = content;
919
 
920
- if(file) {
921
- document.getElementById('modalFile').innerHTML = `
922
- <a href="/download/${file}" target="_blank" download style="color:#fff; background:#222; padding:8px; text-decoration:none; display:inline-block; border-radius:2px;">
923
- 📎 Descarcă: ${file}
924
- </a>
 
 
 
 
 
 
925
  `;
926
- } else {
927
- document.getElementById('modalFile').innerHTML = '';
928
- }
929
 
930
- document.getElementById('messageModal').classList.add('active');
 
 
 
931
  }
 
932
 
933
- function closeMessageModal() {
934
- document.getElementById('messageModal').classList.remove('active');
 
 
 
 
 
 
 
 
 
 
 
935
  }
 
 
 
 
936
 
937
- async function deleteMessage(msgId) {
938
- if(!confirm('Ștergi acest mesaj?')) return;
939
-
940
- await fetch('/api/delete_message', {
941
- method: 'POST',
942
- headers: {'Content-Type': 'application/json'},
943
- body: JSON.stringify({message_id: msgId})
944
- });
945
-
946
- showNotify("MESAJ ȘTERS");
947
- refreshInbox();
948
- }
949
 
950
- // ===== SEND MAIL =====
 
951
 
952
- async function sendMail() {
953
- const fd = new FormData();
954
- const dest = document.getElementById('targetDest').value;
955
- fd.append('destinatar', dest);
956
- fd.append('mesaj', document.getElementById('targetMsg').value);
957
- const file = document.getElementById('targetFile').files[0];
958
- if(file) fd.append('fisier', file);
959
-
960
- addLog(`SENDING: Data packet to ${dest}...`);
961
-
962
- const r = await fetch('/api/send_mail', { method: 'POST', body: fd });
963
- const d = await r.json();
964
-
965
- if(d.success) {
966
- showNotify("TRIMIS!");
967
- document.getElementById('targetMsg').value = "";
968
- document.getElementById('targetFile').value = "";
969
- addLog(`SUCCESS: Packet delivered to ${dest}`);
970
- } else {
971
- addLog(`ERROR: ${d.error}`);
972
- alert("Eroare: " + d.error);
973
- }
 
 
 
 
 
 
 
 
 
 
 
974
  }
 
975
 
976
- function reportBug() {
977
- const now = new Date();
978
- const dateStr = `${now.getDate()}/${now.getMonth()+1}/${now.getFullYear()}`;
979
- const timeStr = `${now.getHours()}:${String(now.getMinutes()).padStart(2, '0')}`;
980
-
981
- document.getElementById('targetDest').value = 'admin001';
982
- document.getElementById('targetMsg').value = `Raportare Bug: [${dateStr} | Ora ${timeStr}]\n\n`;
983
-
984
- showSection('send');
985
- showNotify("Completează raportarea!");
986
- addLog("BUG REPORT: Template loaded");
987
- }
 
 
988
 
989
- // ===== GRUP =====
 
 
990
 
991
- async function sendGroup() {
992
- const msg = document.getElementById('groupMsgInput').value;
993
- if(!msg) return;
994
-
995
- const r = await fetch('/api/group/send', {
996
- method: 'POST',
997
- headers: {'Content-Type': 'application/json'},
998
- body: JSON.stringify({content: msg})
999
- });
1000
-
1001
- const d = await r.json();
1002
-
1003
- if(d.success) {
1004
- document.getElementById('groupMsgInput').value = "";
1005
- refreshGroup();
1006
- } else {
1007
- alert("Eroare: " + d.error);
1008
- }
1009
  }
 
1010
 
1011
- async function refreshGroup() {
1012
- if(!isUserLogged) return;
1013
-
1014
- try {
1015
- const r = await fetch('/api/group/get');
1016
- const msgs = await r.json();
1017
- const box = document.getElementById('groupList');
1018
-
1019
- box.innerHTML = msgs.map(m => `
1020
- <div class="group-msg">
1021
- <div class="group-header">
1022
- ${m.user} <span class="group-time">[${m.timestamp}]</span>
1023
- ${currentUserData && currentUserData.is_admin ?
1024
- `<button class="btn-small btn-danger" onclick="deleteGroupMessage(${m.id})" style="float:right;">🗑️</button>`
1025
- : ''}
1026
- </div>
1027
- <div style="color:#fff;">${m.content}</div>
1028
  </div>
1029
- `).join('');
1030
- } catch (e) {
1031
- console.error("Group refresh error:", e);
1032
- }
1033
- }
1034
 
1035
- async function deleteGroupMessage(msgId) {
1036
- if(!confirm('Ștergi acest mesaj din grup?')) return;
1037
-
1038
- await fetch('/api/group/delete', {
1039
- method: 'POST',
1040
- headers: {'Content-Type': 'application/json'},
1041
- body: JSON.stringify({message_id: msgId})
1042
- });
1043
-
1044
- showNotify("MESAJ ȘTERS");
1045
- refreshGroup();
1046
- }
1047
 
1048
- async function requestGroupInvite() {
1049
- const r = await fetch('/api/group/request_invite', { method: 'POST' });
1050
- const d = await r.json();
1051
-
1052
- if(d.success) {
1053
- showNotify("CERERE TRIMISĂ!");
1054
- addLog("GROUP: Invitation request sent to admin");
1055
- } else {
1056
- alert("Eroare: " + d.error);
1057
- }
1058
  }
 
 
 
1059
 
1060
- // ===== CONTACTE =====
 
 
 
1061
 
1062
- function saveContact() {
1063
- const name = document.getElementById('cName').value;
1064
- const id = document.getElementById('cId').value;
1065
- if(!name || !id) return;
1066
-
1067
- let contacts = JSON.parse(localStorage.getItem('droply_contacts') || '[]');
1068
- contacts.push({name, id});
1069
- localStorage.setItem('droply_contacts', JSON.stringify(contacts));
1070
-
1071
- document.getElementById('cName').value = "";
1072
- document.getElementById('cId').value = "";
1073
- loadContacts();
1074
- showNotify("CONTACT SALVAT");
1075
- addLog(`CONTACT SAVED: ${name} (${id})`);
1076
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1077
 
1078
- function loadContacts() {
1079
- const contacts = JSON.parse(localStorage.getItem('droply_contacts') || '[]');
1080
- const container = document.getElementById('contactList');
1081
- container.innerHTML = "";
1082
-
1083
- contacts.forEach((c, index) => {
1084
- const div = document.createElement('div');
1085
- div.className = "item-list";
1086
- div.innerHTML = `
1087
- <div class="item-info"><b>${c.name}</b><br><small>${c.id}</small></div>
1088
- <div>
1089
- <button class="btn-small" onclick="useContact('${c.id}')">USE</button>
1090
- <button class="btn-small btn-danger" onclick="delContact(${index})">X</button>
1091
- </div>
1092
- `;
1093
- container.appendChild(div);
1094
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1095
  }
 
 
 
 
1096
 
1097
- function useContact(id) {
1098
- document.getElementById('targetDest').value = id;
1099
- showSection('send');
 
 
 
 
1100
  }
1101
 
1102
- function delContact(i) {
1103
- let contacts = JSON.parse(localStorage.getItem('droply_contacts') || '[]');
1104
- contacts.splice(i, 1);
1105
- localStorage.setItem('droply_contacts', JSON.stringify(contacts));
1106
- loadContacts();
1107
- addLog(`CONTACT DELETED`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1108
  }
 
1109
 
 
1110
 
1111
- // ===== SETĂRI =====
 
1112
 
1113
- async function updateSettings() {
1114
- const notif = parseInt(document.getElementById('setNotif').value);
1115
-
1116
- await fetch('/api/update_settings', {
1117
- method: 'POST',
1118
- headers: {'Content-Type': 'application/json'},
1119
- body: JSON.stringify({
1120
- visibility: document.getElementById('setVis').value,
1121
- secret_code: document.getElementById('setSecret').value,
1122
- sara_ai: saraEnabled,
1123
- notif_enabled: notif
1124
- })
1125
- });
1126
-
1127
- // Actualizăm currentUserData
1128
- if(currentUserData) {
1129
- currentUserData.notif_enabled = notif;
1130
- }
1131
-
1132
- showNotify("SETĂRI SALVATE");
1133
- addLog(`SETTINGS: Profile updated. Sara AI: ${saraEnabled}, Notif: ${notif ? 'ON' : 'OFF'}`);
1134
- }
1135
-
1136
- async function changePassword() {
1137
- const oldPass = document.getElementById('oldPass').value;
1138
- const newPass = document.getElementById('newPass').value;
1139
-
1140
- if(!oldPass || !newPass) {
1141
- alert("Completează ambele câmpuri!");
1142
- return;
1143
- }
1144
 
1145
- const r = await fetch('/api/change_password', {
1146
- method: 'POST',
1147
- headers: {'Content-Type': 'application/json'},
1148
- body: JSON.stringify({old_password: oldPass, new_password: newPass})
 
 
 
 
 
 
1149
  });
1150
 
1151
- const d = await r.json();
1152
-
1153
- if(d.success) {
1154
- showNotify("PAROLĂ SCHIMBATĂ!");
1155
- document.getElementById('oldPass').value = "";
1156
- document.getElementById('newPass').value = "";
1157
- addLog("SECURITY: Password changed successfully");
1158
- } else {
1159
- alert("Eroare: " + d.error);
1160
- }
1161
- }
1162
-
1163
- async function checkPing() {
1164
- try {
1165
- const start = Date.now();
1166
- const r = await fetch('/api/server_ping');
1167
- const ping = Date.now() - start;
1168
-
1169
- document.getElementById('serverPing').innerText = `Server Online - Ping: ${ping}ms`;
1170
- addLog(`PING: Server responded in ${ping}ms`);
1171
- } catch(e) {
1172
- document.getElementById('serverPing').innerText = 'Server Offline';
1173
- addLog('PING: Server not responding');
1174
- }
1175
  }
1176
-
1177
- // ===== ADMIN PANEL =====
1178
 
1179
- async function loadAdminData() {
1180
- if(!currentUserData || !currentUserData.is_admin) return;
1181
-
1182
- // Load users
1183
- const rUsers = await fetch('/api/admin/get_users');
1184
- const dUsers = await rUsers.json();
1185
 
1186
- if(dUsers.success) {
1187
- const container = document.getElementById('adminUsersList');
1188
- container.innerHTML = '';
1189
-
1190
- dUsers.users.forEach(u => {
1191
  const div = document.createElement('div');
1192
  div.className = 'item-list';
1193
  div.innerHTML = `
1194
  <div class="item-info">
1195
- <b>${u.email}</b><br>
1196
- <small>ID: ${u.id} | IP: ${u.ip}</small>
1197
  </div>
 
1198
  `;
1199
  container.appendChild(div);
1200
  });
1201
-
1202
- addLog(`ADMIN: Loaded ${dUsers.users.length} users`);
1203
- }
1204
-
1205
- // Load group invitations
1206
- const rInvites = await fetch('/api/admin/get_invitations');
1207
- const dInvites = await rInvites.json();
1208
-
1209
- if(dInvites.success) {
1210
- const container = document.getElementById('adminInvitesList');
1211
- container.innerHTML = '';
1212
-
1213
- if(dInvites.invitations.length === 0) {
1214
- container.innerHTML = '<p style="color:#666; font-size:12px;">Nu există cereri pendinte</p>';
1215
- } else {
1216
- dInvites.invitations.forEach(inv => {
1217
- const div = document.createElement('div');
1218
- div.className = 'item-list';
1219
- div.innerHTML = `
1220
- <div class="item-info">
1221
- <b>${inv.user_email}</b><br>
1222
- <small>${inv.timestamp}</small>
1223
- </div>
1224
- <button class="btn-small" onclick="approveInvite(${inv.id})">✅ Aprobă</button>
1225
- `;
1226
- container.appendChild(div);
1227
- });
1228
- }
1229
  }
1230
  }
 
1231
 
1232
- async function approveInvite(inviteId) {
1233
- const r = await fetch('/api/admin/approve_invitation', {
1234
- method: 'POST',
1235
- headers: {'Content-Type': 'application/json'},
1236
- body: JSON.stringify({invitation_id: inviteId})
1237
- });
1238
-
1239
- const d = await r.json();
1240
-
1241
- if(d.success) {
1242
- showNotify("INVITAȚIE APROBATĂ!");
1243
- loadAdminData();
1244
- addLog("ADMIN: Group invitation approved");
1245
- }
1246
  }
 
1247
 
1248
- async function triggerMaintenance() {
1249
- if(!confirm('ATENȚIE! Această acțiune va închide serverul în 60 de secunde! Continui?')) {
1250
- return;
1251
- }
 
 
 
 
 
 
 
1252
 
1253
- const r = await fetch('/api/admin/broadcast_warning', { method: 'POST' });
1254
- const d = await r.json();
1255
 
1256
- if(d.success) {
1257
- showNotify("⚠️ MAINTENANCE ACTIVAT!");
1258
- addLog("ADMIN: Maintenance mode activated - shutdown in 60s");
1259
-
1260
- // Arătăm banner-ul
1261
- document.getElementById('warningBanner').style.display = 'block';
1262
 
1263
- // Countdown
1264
- let seconds = 60;
1265
- const countdown = setInterval(() => {
1266
- seconds--;
1267
  document.getElementById('warningBanner').innerText =
1268
- `⚠️ ATENȚIE! Serverul se închide în ${seconds} secunde! ⚠️`;
1269
-
1270
- if(seconds <= 0) {
1271
- clearInterval(countdown);
1272
- document.getElementById('warningBanner').innerText =
1273
- '🔴 SERVER OFFLINE - Mentenanță în curs...';
1274
- }
1275
- }, 1000);
1276
- }
1277
- }
1278
-
1279
- async function checkShutdown() {
1280
- try {
1281
- const r = await fetch('/api/check_shutdown');
1282
- const d = await r.json();
1283
-
1284
- if(d.shutdown_scheduled && !document.getElementById('warningBanner').style.display) {
1285
- document.getElementById('warningBanner').style.display = 'block';
1286
- addLog("WARNING: Server shutdown scheduled!");
1287
  }
1288
- } catch(e) {
1289
- // Server might be down
1290
- }
1291
  }
 
1292
 
1293
- // ===== ABOUT =====
1294
-
1295
- function showAbout() {
1296
- alert(`
1297
- ╔════════════════════════════════════╗
1298
- ║ DROPLY! v2.1 - CHANGELOG ║
1299
- ╚════════════════════════════════════╝
1300
-
1301
- ✨ FUNCȚII NOI v2.1:
1302
-
1303
- 🔐 SETĂRI:
1304
- ✅ Resetare parolă din setări
1305
- ✅ Activare/Dezactivare notificări push
1306
- ✅ Verificare ping server
1307
-
1308
- 👥 GRUP:
1309
- ✅ Acces controlat de admin
1310
- ✅ Cerere invitație pentru grup
1311
- ✅ Ștergere mesaje (doar admin)
1312
- ✅ Vizualizare mesaje pentru non-membri
1313
-
1314
- 📧 INBOX:
1315
- ✅ Pop-up detaliat pentru mesaje
1316
- ✅ Ștergere mesaje din inbox
1317
- ✅ Vizualizare fișiere atașate
1318
-
1319
- 📤 TRIMITERE:
1320
- ✅ Simplificare (fără cod secret)
1321
- ✅ Buton raportare bug rapid
1322
-
1323
- 🔧 ADMIN PANEL (doar admin):
1324
- ✅ Vizualizare utilizatori + IP
1325
- ✅ Aprobare invitații grup
1326
- ✅ Warning broadcast + shutdown
1327
-
1328
- 🎯 ÎMBUNĂTĂȚIRI:
1329
- ✅ Auto-login persistent
1330
- ✅ Notificări browser
1331
- ✅ Service Worker pentru fundal
1332
- ✅ UI mai clean și responsive
1333
-
1334
- ═══════════════════════════════════
1335
-
1336
- 📝 Admin: victor89@admindroply.md
1337
- 🔑 Parolă: 122012
1338
- `);
1339
- }
1340
 
1341
  </script>
1342
  </body>
 
1
  <!DOCTYPE html>
2
+ <html lang="en">
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <link rel="icon" type="image/png" href="/logo.png">
7
+ <title>DROPLY! - Ultra Secure Messaging</title>
8
  <style>
9
+ /* ==================== ULTRA BOLD BLACK & WHITE DESIGN ==================== */
10
+ /* Inspired by D! logo - Minimalist, Bold, Premium */
11
+
12
+ * {
13
+ margin: 0;
14
+ padding: 0;
15
+ box-sizing: border-box;
 
 
 
16
  }
17
 
18
+ :root {
19
+ --black: #000000;
20
+ --white: #ffffff;
21
+ --gray-dark: #1a1a1a;
22
+ --gray-medium: #333333;
23
+ --gray-light: #666666;
24
+ --gray-lighter: #999999;
25
+ }
26
 
27
  body {
28
+ background: var(--black);
29
+ color: var(--white);
30
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
 
31
  display: flex;
32
  height: 100vh;
33
  overflow: hidden;
34
+ font-weight: 500;
35
  }
36
 
37
+ /* ==================== SIDEBAR ==================== */
38
+
39
  nav {
40
+ width: 280px;
41
+ background: var(--black);
42
+ border-right: 2px solid var(--white);
43
  display: none;
44
  flex-direction: column;
45
+ position: relative;
 
46
  }
47
 
48
+ .nav-header {
49
+ padding: 48px 32px 32px;
50
+ border-bottom: 2px solid var(--white);
51
+ }
52
+
53
+ .nav-logo {
54
+ font-size: 48px;
55
+ font-weight: 900;
56
+ letter-spacing: -2px;
57
+ margin-bottom: 8px;
58
+ line-height: 1;
59
  }
60
+
61
  .nav-version {
62
+ font-size: 10px;
63
+ color: var(--gray-lighter);
64
+ font-weight: 700;
65
+ letter-spacing: 2px;
66
+ }
67
+
68
+ /* Language Toggle */
69
+ .lang-switch {
70
+ display: flex;
71
+ gap: 0;
72
+ margin-top: 24px;
73
+ border: 2px solid var(--white);
74
+ }
75
+
76
+ .lang-btn {
77
+ flex: 1;
78
+ padding: 12px;
79
+ background: transparent;
80
+ border: none;
81
+ color: var(--white);
82
+ font-size: 12px;
83
+ font-weight: 900;
84
+ letter-spacing: 1px;
85
+ cursor: pointer;
86
+ transition: all 0.2s;
87
+ }
88
+
89
+ .lang-btn:first-child {
90
+ border-right: 2px solid var(--white);
91
+ }
92
+
93
+ .lang-btn.active {
94
+ background: var(--white);
95
+ color: var(--black);
96
+ }
97
+
98
+ .lang-btn:hover:not(.active) {
99
+ background: var(--gray-dark);
100
+ }
101
+
102
+ /* Navigation Items */
103
+ .nav-items {
104
+ flex: 1;
105
+ padding: 24px 0;
106
+ overflow-y: auto;
107
  }
108
 
109
  .nav-item {
110
+ padding: 20px 32px;
111
  cursor: pointer;
112
+ color: var(--gray-lighter);
113
  text-transform: uppercase;
114
+ font-size: 12px;
115
+ font-weight: 900;
116
+ letter-spacing: 2px;
117
+ transition: all 0.2s;
118
+ border-left: 4px solid transparent;
119
+ }
120
+
121
+ .nav-item:hover {
122
+ color: var(--white);
123
+ background: var(--gray-dark);
124
+ border-left-color: var(--white);
125
  }
126
 
127
+ .nav-item.active {
128
+ color: var(--white);
129
+ background: var(--gray-dark);
130
+ border-left-color: var(--white);
131
  }
132
 
133
+ .nav-footer {
134
+ border-top: 2px solid var(--white);
135
+ padding: 24px 32px;
136
+ }
137
+
138
+ .nav-item.logout {
139
+ color: var(--gray-light);
140
+ }
141
+
142
+ .nav-item.logout:hover {
143
+ color: var(--white);
144
+ background: var(--gray-dark);
145
+ }
146
+
147
+ /* ==================== MAIN CONTENT ==================== */
148
+
149
  main {
150
+ flex: 1;
151
  overflow-y: auto;
152
+ padding: 80px;
153
  display: flex;
154
  flex-direction: column;
155
  align-items: center;
156
  }
157
 
158
+ .section {
159
+ width: 100%;
160
+ max-width: 700px;
161
+ display: none;
162
+ animation: slideIn 0.3s ease;
163
+ }
164
+
165
+ .section.active {
166
+ display: block;
167
  }
 
168
 
169
+ @keyframes slideIn {
170
+ from {
171
+ opacity: 0;
172
+ transform: translateY(20px);
173
+ }
174
+ to {
175
+ opacity: 1;
176
+ transform: translateY(0);
177
+ }
178
+ }
179
 
180
+ /* ==================== CARDS ==================== */
181
+
182
+ .card {
183
+ background: var(--black);
184
+ border: 2px solid var(--white);
185
+ padding: 40px;
186
+ margin-bottom: 32px;
187
+ transition: all 0.2s;
 
188
  }
189
 
190
+ .card:hover {
191
+ border-color: var(--gray-lighter);
 
 
 
 
192
  }
193
+
194
+ h2 {
195
+ font-size: 24px;
196
+ font-weight: 900;
197
+ margin-bottom: 32px;
198
+ text-transform: uppercase;
199
+ letter-spacing: 2px;
200
+ padding-bottom: 16px;
201
+ border-bottom: 2px solid var(--white);
202
+ }
203
+
204
+ h3 {
205
+ font-size: 14px;
206
+ font-weight: 900;
207
+ margin: 24px 0 16px;
208
+ text-transform: uppercase;
209
+ letter-spacing: 2px;
210
+ color: var(--gray-lighter);
211
+ }
212
+
213
+ /* ==================== FORMS ==================== */
214
 
215
+ label {
216
+ display: block;
217
+ font-size: 11px;
218
+ color: var(--gray-lighter);
219
+ margin-bottom: 12px;
220
+ text-transform: uppercase;
221
+ letter-spacing: 2px;
222
+ font-weight: 700;
223
  }
224
 
225
  input, textarea, select {
226
+ width: 100%;
227
+ background: var(--black);
228
+ border: 2px solid var(--white);
229
+ color: var(--white);
230
+ padding: 16px 20px;
231
+ margin-bottom: 24px;
232
+ font-size: 16px;
233
+ font-family: inherit;
234
+ font-weight: 500;
235
  outline: none;
236
+ transition: all 0.2s;
237
+ }
238
+
239
+ input:focus, textarea:focus, select:focus {
240
+ border-color: var(--gray-lighter);
241
+ }
242
+
243
+ input::placeholder, textarea::placeholder {
244
+ color: var(--gray-light);
245
+ }
246
+
247
+ textarea {
248
+ resize: vertical;
249
+ min-height: 120px;
250
  font-family: inherit;
251
  }
 
 
252
 
253
+ /* ==================== BUTTONS ==================== */
254
+
255
  button {
256
+ background: var(--white);
257
+ color: var(--black);
258
+ border: 2px solid var(--white);
259
+ padding: 18px 32px;
260
+ font-weight: 900;
261
+ text-transform: uppercase;
262
+ letter-spacing: 2px;
263
+ cursor: pointer;
264
+ width: 100%;
265
+ transition: all 0.2s;
266
+ font-size: 13px;
267
  }
268
+
269
+ button:hover {
270
+ background: var(--black);
271
+ color: var(--white);
272
+ }
273
+
274
+ button:active {
275
+ transform: scale(0.98);
276
+ }
277
+
278
  .btn-secondary {
279
+ background: var(--black);
280
+ color: var(--white);
281
+ border: 2px solid var(--white);
282
  }
283
+
284
+ .btn-secondary:hover {
285
+ background: var(--white);
286
+ color: var(--black);
287
+ }
288
+
289
  .btn-danger {
290
+ background: var(--black);
291
+ color: var(--gray-lighter);
292
+ border: 2px solid var(--gray-lighter);
293
+ }
294
+
295
+ .btn-danger:hover {
296
+ background: var(--white);
297
+ color: var(--black);
298
+ border-color: var(--white);
299
  }
 
300
 
301
+ .btn-small {
302
+ width: auto;
303
+ padding: 8px 16px;
304
+ font-size: 10px;
305
+ margin-left: 12px;
306
  }
307
 
308
+ /* ==================== TERMINAL ==================== */
309
+
310
  #terminalBox {
311
+ background: var(--black);
312
+ border: 2px solid var(--white);
313
+ color: var(--gray-lighter);
314
  font-family: 'Courier New', monospace;
315
+ font-size: 12px;
316
+ padding: 24px;
317
+ height: 200px;
318
  overflow-y: auto;
319
+ margin-top: 24px;
320
+ line-height: 1.8;
321
+ font-weight: 500;
 
 
 
 
 
 
 
 
 
 
322
  }
323
+
324
+ /* ==================== LISTS ==================== */
325
 
326
+ .msg-card, .group-msg {
327
+ border-bottom: 2px solid var(--gray-dark);
328
+ padding: 20px 0;
329
+ position: relative;
330
+ }
331
+
332
+ .msg-card:hover, .group-msg:hover {
333
+ border-bottom-color: var(--gray-medium);
334
+ }
335
+
336
  .msg-actions {
337
  position: absolute;
338
+ top: 20px;
339
  right: 0;
340
  }
341
+
342
+ .item-list {
343
+ border-bottom: 2px solid var(--gray-dark);
344
+ padding: 20px 0;
345
+ display: flex;
346
+ justify-content: space-between;
347
+ align-items: center;
348
  }
349
+
350
+ .item-info {
351
+ font-size: 14px;
352
+ font-weight: 600;
353
  }
354
 
355
+ .item-info small {
356
+ color: var(--gray-light);
357
+ font-size: 12px;
358
+ font-weight: 500;
 
 
 
359
  }
 
360
 
361
+ /* ==================== BADGES ==================== */
362
+
363
  .badge {
364
+ background: var(--black);
365
+ border: 2px solid var(--white);
366
+ color: var(--white);
367
+ padding: 12px 20px;
368
+ font-size: 11px;
369
+ display: inline-block;
370
+ margin-bottom: 24px;
371
  cursor: pointer;
372
+ font-weight: 900;
373
+ letter-spacing: 2px;
374
+ transition: all 0.2s;
375
+ text-transform: uppercase;
376
  }
377
+
378
+ .badge:hover {
379
+ background: var(--white);
380
+ color: var(--black);
381
+ }
382
+
383
+ .badge.active {
384
+ background: var(--white);
385
+ color: var(--black);
386
+ }
387
+
388
  .admin-badge {
389
+ border-color: var(--white);
390
+ background: var(--white);
391
+ color: var(--black);
392
  }
393
 
394
+ /* ==================== NOTIFICATIONS ==================== */
395
+
396
  #notify {
397
+ position: fixed;
398
+ top: 40px;
399
+ right: 40px;
400
+ background: var(--white);
401
+ color: var(--black);
402
+ padding: 20px 32px;
403
+ font-weight: 900;
404
+ z-index: 1000;
405
  display: none;
406
+ font-size: 13px;
407
+ letter-spacing: 2px;
408
+ border: 2px solid var(--black);
409
  }
410
+
 
411
  #warningBanner {
412
  position: fixed;
413
  top: 0;
414
  left: 0;
415
  right: 0;
416
+ background: var(--white);
417
+ color: var(--black);
418
+ padding: 24px;
419
  text-align: center;
420
+ font-weight: 900;
421
  z-index: 2000;
422
  display: none;
423
+ font-size: 14px;
424
+ letter-spacing: 2px;
425
+ border-bottom: 2px solid var(--black);
 
 
 
426
  }
427
 
428
+ /* ==================== MODAL ==================== */
429
+
430
  .modal {
431
  display: none;
432
  position: fixed;
 
434
  left: 0;
435
  right: 0;
436
  bottom: 0;
437
+ background: rgba(0, 0, 0, 0.95);
438
  z-index: 1500;
439
  align-items: center;
440
  justify-content: center;
441
  }
442
+
443
+ .modal.active {
444
+ display: flex;
445
+ }
446
+
447
  .modal-content {
448
+ background: var(--black);
449
+ border: 2px solid var(--white);
450
+ padding: 48px;
451
+ max-width: 700px;
452
  width: 90%;
453
  max-height: 80vh;
454
  overflow-y: auto;
455
  position: relative;
 
456
  }
457
+
458
  .modal-close {
459
  position: absolute;
460
+ top: 24px;
461
+ right: 24px;
462
+ font-size: 32px;
463
  cursor: pointer;
464
+ color: var(--gray-lighter);
465
+ font-weight: 900;
466
+ line-height: 1;
467
+ transition: all 0.2s;
468
  }
 
469
 
470
+ .modal-close:hover {
471
+ color: var(--white);
 
 
 
 
 
 
 
472
  }
473
+
474
+ /* ==================== PING ==================== */
475
 
476
+ .ping {
477
+ display: inline-block;
478
+ width: 10px;
479
+ height: 10px;
480
+ background: var(--white);
481
+ margin-right: 12px;
482
+ animation: pulse 2s infinite;
483
+ }
484
+
485
+ @keyframes pulse {
486
  0%, 100% { opacity: 1; }
487
  50% { opacity: 0.3; }
488
  }
489
 
490
+ /* ==================== SCROLLBAR ==================== */
491
+
492
+ ::-webkit-scrollbar {
493
+ width: 12px;
494
+ }
495
+
496
+ ::-webkit-scrollbar-track {
497
+ background: var(--black);
498
+ }
499
+
500
+ ::-webkit-scrollbar-thumb {
501
+ background: var(--gray-medium);
502
+ border: 2px solid var(--black);
503
+ }
504
+
505
+ ::-webkit-scrollbar-thumb:hover {
506
+ background: var(--gray-lighter);
507
+ }
508
+
509
+ /* ==================== RESPONSIVE ==================== */
510
+
511
  @media (max-width: 768px) {
512
+ body {
513
+ flex-direction: column;
514
+ }
515
+
516
+ nav {
517
+ width: 100%;
518
+ border-right: none;
519
+ border-bottom: 2px solid var(--white);
520
+ }
521
+
522
+ .nav-header {
523
+ padding: 24px;
524
+ }
525
+
526
+ .nav-logo {
527
+ font-size: 32px;
528
+ }
529
+
530
+ .nav-items {
531
+ display: flex;
532
+ overflow-x: auto;
533
+ padding: 0;
534
+ }
535
+
536
+ .nav-item {
537
+ flex-shrink: 0;
538
+ padding: 20px 24px;
539
+ border-left: none;
540
+ border-bottom: 4px solid transparent;
541
+ }
542
+
543
+ .nav-item.active {
544
+ border-left: none;
545
+ border-bottom-color: var(--white);
546
+ }
547
+
548
+ .nav-footer {
549
+ display: none;
550
+ }
551
+
552
+ main {
553
+ padding: 40px 24px;
554
+ }
555
+
556
+ .card {
557
+ padding: 32px 24px;
558
+ }
559
+
560
+ h2 {
561
+ font-size: 20px;
562
  }
 
 
 
563
  }
564
  </style>
565
  </head>
 
567
 
568
  <audio id="notifSound" src="/notification.mp3" preload="auto"></audio>
569
 
570
+ <div id="notify">SUCCESS</div>
571
+ <div id="warningBanner">⚠ SERVER MAINTENANCE - INCONVENIENCE REGRETTED</div>
 
 
572
 
573
+ <!-- MESSAGE MODAL -->
574
  <div id="messageModal" class="modal">
575
  <div class="modal-content">
576
+ <span class="modal-close" onclick="closeMessageModal()">×</span>
577
+ <h2 id="modalFrom" class="lang" data-en="FROM:" data-ro="DE LA:">FROM:</h2>
578
+ <div id="modalTime" style="font-size: 12px; color: var(--gray-light); margin-bottom: 24px; font-weight: 500;"></div>
579
+ <div id="modalContent" style="margin-bottom: 24px; line-height: 1.8; font-weight: 500;"></div>
580
  <div id="modalFile"></div>
581
  </div>
582
  </div>
583
 
584
  <nav id="sidebar">
585
+ <div class="nav-header">
586
+ <div class="nav-logo">D!</div>
587
+ <div class="nav-version">v2.1 ULTRA</div>
588
+
589
+ <div class="lang-switch">
590
+ <button class="lang-btn active" onclick="switchLanguage('en')">EN</button>
591
+ <button class="lang-btn" onclick="switchLanguage('ro')">RO</button>
592
+ </div>
593
+ </div>
594
+
595
+ <div class="nav-items">
596
+ <div class="nav-item active" onclick="showSection('home')">
597
+ <span class="lang" data-en="DASHBOARD" data-ro="PANOU">DASHBOARD</span>
598
+ </div>
599
+ <div class="nav-item" onclick="showSection('inbox')">
600
+ <span class="lang" data-en="INBOX" data-ro="MESAJE">INBOX</span>
601
+ </div>
602
+ <div class="nav-item" onclick="showSection('group')">
603
+ <span class="lang" data-en="GROUP" data-ro="GRUP">GROUP</span>
604
+ </div>
605
+ <div class="nav-item" onclick="showSection('send')">
606
+ <span class="lang" data-en="SEND" data-ro="TRIMITE">SEND</span>
607
+ </div>
608
+ <div class="nav-item" onclick="showSection('contacts')">
609
+ <span class="lang" data-en="CONTACTS" data-ro="AGENDĂ">CONTACTS</span>
610
+ </div>
611
+ <div class="nav-item" onclick="showSection('settings')">
612
+ <span class="lang" data-en="SETTINGS" data-ro="SETĂRI">SETTINGS</span>
613
+ </div>
614
+ <div class="nav-item" id="adminNavItem" style="display:none;" onclick="showSection('admin')">
615
+ <span class="lang" data-en="ADMIN" data-ro="ADMIN">ADMIN</span>
616
+ </div>
617
+ </div>
618
+
619
+ <div class="nav-footer">
620
+ <div class="nav-item logout" onclick="logout()">
621
+ <span class="lang" data-en="LOGOUT" data-ro="IEȘIRE">LOGOUT</span>
622
+ </div>
623
+ </div>
624
  </nav>
625
 
626
+ <main>
627
+ <!-- AUTH SECTION -->
628
  <div id="authSection" class="section active">
629
  <div class="card">
630
+ <h2 class="lang" data-en="ACCESS" data-ro="ACCES">ACCESS</h2>
631
+ <label class="lang" data-en="EMAIL" data-ro="EMAIL">EMAIL</label>
632
+ <input type="text" id="loginEmail" placeholder="user@droply.md">
633
+ <label class="lang" data-en="PASSWORD" data-ro="PAROLĂ">PASSWORD</label>
634
  <input type="password" id="loginPass">
635
+ <button onclick="login()">
636
+ <span class="lang" data-en="LOGIN" data-ro="CONECTARE">LOGIN</span>
637
+ </button>
638
  </div>
639
  <div class="card">
640
+ <h2 class="lang" data-en="CREATE ACCOUNT" data-ro="CREEAZĂ CONT">CREATE ACCOUNT</h2>
641
  <input type="text" id="regUser" placeholder="Username">
642
+ <input type="password" id="regPass" placeholder="Password">
643
+ <button class="btn-secondary" onclick="register()">
644
+ <span class="lang" data-en="GENERATE" data-ro="GENEREAZĂ">GENERATE</span>
645
+ </button>
646
  </div>
647
  </div>
648
 
649
  <!-- DASHBOARD -->
650
  <div id="home" class="section">
651
  <div class="card">
652
+ <h2 id="userDisplay">HELLO!</h2>
653
+ <div id="idBadge" style="font-family: monospace; color: var(--gray-light); font-size: 14px; font-weight: 700;">ID: 0670...</div>
654
+ <div id="adminBadgeDisplay" style="display:none; margin-top: 24px;">
655
  <span class="badge admin-badge">ADMIN ACCESS</span>
656
  </div>
657
 
658
+ <h3 class="lang" data-en="LIVE SYSTEM LOGS" data-ro="JURNALE SISTEM LIVE">LIVE SYSTEM LOGS</h3>
 
 
659
  <div id="terminalBox">
660
+ <span class="ping"></span>[SYSTEM] Waiting for authentication...
661
  </div>
 
 
662
  </div>
663
  </div>
664
 
665
  <!-- INBOX -->
666
  <div id="inbox" class="section">
667
  <div class="card">
668
+ <h2 class="lang" data-en="INBOX & AI" data-ro="INBOX & AI">INBOX & AI</h2>
669
 
670
+ <div id="saraToggle" class="badge" onclick="toggleSara()">
671
+ <span class="lang" data-en="SARA AI: OFF" data-ro="SARA AI: OPRIT">SARA AI: OFF</span>
672
+ </div>
673
 
674
+ <div id="inboxList" style="max-height: 450px; overflow-y: auto;">
675
+ <span class="lang" data-en="Empty inbox." data-ro="Inbox gol.">Empty inbox.</span>
676
  </div>
677
  </div>
678
  </div>
679
 
680
+ <!-- GROUP -->
681
  <div id="group" class="section">
682
  <div class="card">
683
+ <h2 class="lang" data-en="GLOBAL GROUP" data-ro="GRUP GLOBAL">GLOBAL GROUP</h2>
684
 
685
+ <div id="groupAccessInfo" style="margin-bottom: 24px; padding: 20px; background: var(--gray-dark); border-left: 4px solid var(--white);">
686
+ <div id="groupMemberText" style="display:none; font-size: 13px; font-weight: 600;">
687
+ <span class="lang" data-en="✓ GROUP ACCESS GRANTED" data-ro="✓ AI ACCES LA GRUP">✓ GROUP ACCESS GRANTED</span>
688
  </div>
689
+ <div id="groupNonMemberText" style="display:none; color: var(--gray-lighter); font-size: 13px; font-weight: 600;">
690
+ <span class="lang" data-en="NO ACCESS - VIEW ONLY" data-ro="⚠ FĂRĂ ACCES - DOAR VIZUALIZARE">⚠ NO ACCESS - VIEW ONLY</span>
691
+ <button style="margin-top: 16px;" onclick="requestGroupInvite()">
692
+ <span class="lang" data-en="REQUEST" data-ro="CERE">REQUEST</span>
693
+ </button>
694
  </div>
695
  </div>
696
 
697
+ <div id="groupList" style="max-height: 400px; overflow-y: auto; margin-bottom: 24px;"></div>
698
 
699
  <div id="groupInputSection">
700
+ <textarea id="groupMsgInput" placeholder="Type message..." style="height: 100px;"></textarea>
701
+ <button onclick="sendGroup()">
702
+ <span class="lang" data-en="SEND" data-ro="TRIMITE">SEND</span>
703
+ </button>
704
  </div>
705
  </div>
706
  </div>
707
 
708
+ <!-- SEND -->
709
  <div id="send" class="section">
710
  <div class="card">
711
+ <h2 class="lang" data-en="SEND MESSAGE" data-ro="TRIMITE MESAJ">SEND MESSAGE</h2>
712
+ <label class="lang" data-en="RECIPIENT ID" data-ro="ID DESTINATAR">RECIPIENT ID</label>
713
+ <input type="text" id="targetDest" placeholder="a1b2c3d4e5f6">
714
+ <label class="lang" data-en="MESSAGE" data-ro="MESAJ">MESSAGE</label>
715
+ <textarea id="targetMsg" placeholder="Type your message..." style="height: 140px;"></textarea>
716
+ <label class="lang" data-en="ATTACHMENT (Optional)" data-ro="FIȘIER (Opțional)">ATTACHMENT (Optional)</label>
717
  <input type="file" id="targetFile">
718
+ <button onclick="sendMail()">
719
+ <span class="lang" data-en="SEND" data-ro="TRIMITE">SEND</span>
720
+ </button>
721
 
722
+ <button class="btn-secondary" style="margin-top: 20px;" onclick="reportBug()">
723
+ <span class="lang" data-en="REPORT BUG" data-ro="RAPORTARE BUG">REPORT BUG</span>
724
  </button>
725
  </div>
726
  </div>
727
 
728
+ <!-- CONTACTS -->
729
  <div id="contacts" class="section">
730
  <div class="card">
731
+ <h2 class="lang" data-en="CONTACTS" data-ro="CONTACTE">CONTACTS</h2>
732
+ <input type="text" id="cName" placeholder="Contact name">
733
+ <input type="text" id="cId" placeholder="User ID">
734
+ <button onclick="saveContact()">
735
+ <span class="lang" data-en="SAVE" data-ro="SALVEAZĂ">SAVE</span>
736
+ </button>
737
 
738
+ <h3 class="lang" data-en="CONTACT LIST" data-ro="LISTĂ CONTACTE">CONTACT LIST</h3>
739
  <div id="contactList"></div>
740
  </div>
741
  </div>
742
 
743
+ <!-- SETTINGS -->
744
  <div id="settings" class="section">
745
  <div class="card">
746
+ <h2 class="lang" data-en="SETTINGS" data-ro="SETĂRI">SETTINGS</h2>
747
 
748
+ <label class="lang" data-en="VISIBILITY" data-ro="VIZIBILITATE">VISIBILITY</label>
749
  <select id="setVis">
750
  <option value="public">Public</option>
751
+ <option value="private">Private</option>
752
  </select>
753
 
754
+ <label class="lang" data-en="SECRET CODE" data-ro="COD SECRET">SECRET CODE</label>
755
+ <input type="text" id="setSecret" placeholder="ABC123">
756
 
757
+ <label class="lang" data-en="NOTIFICATIONS" data-ro="NOTIFICĂRI">NOTIFICATIONS</label>
758
  <select id="setNotif">
759
+ <option value="1" class="lang" data-en="Enabled" data-ro="Active">Enabled</option>
760
+ <option value="0" class="lang" data-en="Disabled" data-ro="Dezactivate">Disabled</option>
761
  </select>
762
 
763
+ <button onclick="updateSettings()">
764
+ <span class="lang" data-en="SAVE" data-ro="SALVEAZĂ">SAVE</span>
765
+ </button>
766
  </div>
767
 
768
  <div class="card">
769
+ <h2 class="lang" data-en="PASSWORD" data-ro="PAROLĂ">PASSWORD</h2>
770
+ <label class="lang" data-en="OLD PASSWORD" data-ro="PAROLĂ VECHE">OLD PASSWORD</label>
771
+ <input type="password" id="oldPass">
772
+ <label class="lang" data-en="NEW PASSWORD" data-ro="PAROLĂ NOUĂ">NEW PASSWORD</label>
773
+ <input type="password" id="newPass">
774
+ <button onclick="changePassword()">
775
+ <span class="lang" data-en="CHANGE" data-ro="SCHIMBĂ">CHANGE</span>
776
+ </button>
777
  </div>
778
 
779
  <div class="card">
780
+ <h2 class="lang" data-en="SERVER" data-ro="SERVER">SERVER</h2>
781
+ <div style="display: flex; align-items: center; margin-bottom: 24px; font-size: 14px; font-weight: 600;">
782
+ <span class="ping"></span>
783
+ <span id="serverPing" class="lang" data-en="Checking..." data-ro="Verificare...">Checking...</span>
784
  </div>
785
+ <button onclick="checkPing()">
786
+ <span class="lang" data-en="CHECK PING" data-ro="VERIFICĂ">CHECK PING</span>
787
+ </button>
788
  </div>
789
  </div>
790
 
791
+ <!-- ADMIN -->
792
  <div id="admin" class="section">
793
  <div class="card">
794
+ <h2 class="lang" data-en="ADMIN PANEL" data-ro="PANOU ADMIN">ADMIN PANEL</h2>
795
 
796
+ <h3 class="lang" data-en="USERS" data-ro="UTILIZATORI">USERS</h3>
797
+ <div id="adminUsersList" style="max-height: 300px; overflow-y: auto;"></div>
798
 
799
+ <button onclick="loadAdminData()" style="margin-top: 24px;">
800
+ <span class="lang" data-en="REFRESH" data-ro="REÎMPROSPĂTEAZĂ">REFRESH</span>
801
+ </button>
802
  </div>
803
 
804
  <div class="card">
805
+ <h2 class="lang" data-en="INVITATIONS" data-ro="INVITAȚII">INVITATIONS</h2>
806
+ <div id="adminInvitesList" style="max-height: 300px; overflow-y: auto;"></div>
807
  </div>
808
 
809
  <div class="card">
810
+ <h2 class="lang" data-en="MAINTENANCE" data-ro="MENTENANȚĂ">MAINTENANCE</h2>
811
+ <p style="font-size: 13px; color: var(--gray-lighter); margin-bottom: 24px; line-height: 1.6; font-weight: 500;" class="lang"
812
+ data-en="Warning: Server will shut down in 60 seconds."
813
+ data-ro="Atenție: Serverul se va închide în 60 secunde.">
814
+ Warning: Server will shut down in 60 seconds.
815
  </p>
816
  <button class="btn-danger" onclick="triggerMaintenance()">
817
+ <span class="lang" data-en="ACTIVATE" data-ro="ACTIVEAZĂ">ACTIVATE</span>
818
  </button>
819
  </div>
820
  </div>
 
822
  </main>
823
 
824
  <script>
825
+ // DROPLY! v2.1 ULTRA - Complete JavaScript
 
 
 
 
826
 
827
+ // Continue în următorul mesaj cu JavaScript complet...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
828
 
829
+ let lastMsgCount = 0;
830
+ let isUserLogged = false;
831
+ let saraEnabled = false;
832
+ let currentUserData = null;
833
+ let serviceWorkerRegistration = null;
834
+ let currentLang = localStorage.getItem('droply_language') || 'en';
835
+
836
+ // ==================== MULTI-LANGUAGE ====================
837
+
838
+ function switchLanguage(lang) {
839
+ currentLang = lang;
840
+ localStorage.setItem('droply_language', lang);
841
+
842
+ document.querySelectorAll('.lang').forEach(el => {
843
+ const text = el.getAttribute(`data-${lang}`);
844
+ if (text) {
845
+ if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') {
846
+ const placeholder = el.getAttribute(`data-placeholder-${lang}`);
847
+ if (placeholder) el.placeholder = placeholder;
 
 
 
 
 
 
 
 
848
  } else {
849
+ el.textContent = text;
 
 
850
  }
851
  }
852
+ });
853
+
854
+ document.querySelectorAll('.lang-btn').forEach(btn => btn.classList.remove('active'));
855
+ document.querySelector(`.lang-btn[onclick*="${lang}"]`).classList.add('active');
856
+
857
+ addLog(`LANGUAGE: Switched to ${lang.toUpperCase()}`);
858
+ }
859
+
860
+ window.addEventListener('DOMContentLoaded', () => {
861
+ switchLanguage(currentLang);
862
+ });
863
+
864
+ // ==================== SERVICE WORKER ====================
865
+
866
+ if ('serviceWorker' in navigator) {
867
+ navigator.serviceWorker.register('/sw.js')
868
+ .then(registration => {
869
+ serviceWorkerRegistration = registration;
870
+ addLog('SYSTEM: Service Worker active');
871
+ requestNotificationPermission();
872
+
873
+ navigator.serviceWorker.addEventListener('message', event => {
874
+ if (event.data.type === 'PLAY_SOUND') playNotificationSound();
875
+ if (event.data.type === 'CHECK_INBOX') refreshInbox();
876
+ });
877
+ })
878
+ .catch(err => addLog('ERROR: Service Worker failed'));
879
+ }
880
+
881
+ async function requestNotificationPermission() {
882
+ if (!('Notification' in window)) {
883
+ addLog('WARNING: Notifications not supported');
884
  return false;
885
  }
886
+
887
+ if (Notification.permission === 'granted') {
888
+ addLog('SYSTEM: Notifications enabled');
889
+ return true;
 
 
 
 
 
 
 
 
 
 
890
  }
891
+
892
+ if (Notification.permission !== 'denied') {
893
+ const permission = await Notification.requestPermission();
894
+ if (permission === 'granted') {
895
+ addLog('SYSTEM: Notifications enabled');
896
+ new Notification('DROPLY!', {
897
+ body: currentLang === 'en' ? 'Notifications working!' : 'Notificări active!',
898
+ icon: '/logo.png'
899
  });
900
+ return true;
901
  }
902
  }
903
+ return false;
904
+ }
905
+
906
+ function playNotificationSound() {
907
+ try {
908
+ const audio = document.getElementById('notifSound');
909
+ if (audio) {
910
+ audio.currentTime = 0;
911
+ audio.play().catch(e => console.log('Autoplay blocked'));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
912
  }
913
+ } catch (e) {}
914
+ }
915
+
916
+ function sendNotificationToSW(from, content) {
917
+ if (serviceWorkerRegistration?.active) {
918
+ serviceWorkerRegistration.active.postMessage({
919
+ type: 'NEW_MESSAGE',
920
+ from: from,
921
+ content: content
922
+ });
923
+ }
924
+ }
925
 
926
+ // Auto-login
927
+ window.addEventListener('DOMContentLoaded', async () => {
928
+ const sessionToken = localStorage.getItem('droply_session_token');
929
 
930
+ if (sessionToken) {
931
+ addLog('SYSTEM: Checking session...');
 
 
932
 
933
+ try {
934
+ const response = await fetch('/api/check_session', {
935
+ method: 'POST',
936
+ headers: {'Content-Type': 'application/json'},
937
+ body: JSON.stringify({session_token: sessionToken})
938
+ });
939
+
940
+ const data = await response.json();
941
+
942
+ if (data.success) {
943
+ performLogin(data);
944
+ } else {
945
+ localStorage.removeItem('droply_session_token');
946
+ addLog('SYSTEM: Session expired');
947
  }
948
+ } catch(e) {
949
+ addLog('ERROR: Session check failed');
 
 
 
 
 
950
  }
951
  }
952
+
953
+ setInterval(checkShutdown, 5000);
954
+ });
955
 
956
+ // ==================== UTILITIES ====================
 
 
 
 
 
957
 
958
+ function showSection(id) {
959
+ document.querySelectorAll('.section').forEach(s => s.classList.remove('active'));
960
+ document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
961
+ document.getElementById(id).classList.add('active');
962
+
963
+ document.querySelectorAll('.nav-item').forEach(item => {
964
+ if(item.onclick?.toString().includes(id)) {
965
+ item.classList.add('active');
966
+ }
967
+ });
968
+
969
+ addLog(`NAV: ${id.toUpperCase()}`);
970
+
971
+ if(id === 'admin' && currentUserData?.is_admin) {
972
+ loadAdminData();
973
  }
974
+ }
975
+
976
+ function addLog(text) {
977
+ const terminal = document.getElementById('terminalBox');
978
+ const time = new Date().toLocaleTimeString();
979
+ terminal.innerHTML += `<div>[${time}] ${text}</div>`;
980
+ terminal.scrollTop = terminal.scrollHeight;
981
+ }
982
+
983
+ function showNotify(text) {
984
+ const n = document.getElementById('notify');
985
+ n.innerText = text;
986
+ n.style.display = 'block';
987
+ setTimeout(() => n.style.display = 'none', 3000);
988
+ }
989
+
990
+ function logout() {
991
+ localStorage.removeItem('droply_session_token');
992
+ location.reload();
993
+ }
994
+
995
+ // ==================== AUTH ====================
996
+
997
+ async function login() {
998
+ const email = document.getElementById('loginEmail').value;
999
+ const pass = document.getElementById('loginPass').value;
1000
+
1001
+ const r = await fetch('/api/login', {
1002
+ method: 'POST',
1003
+ headers: {'Content-Type': 'application/json'},
1004
+ body: JSON.stringify({email: email, password: pass})
1005
+ });
1006
+
1007
+ const d = await r.json();
1008
+
1009
+ if(d.success) {
1010
+ localStorage.setItem('droply_session_token', d.session_token);
1011
+ performLogin(d);
1012
+ } else {
1013
+ addLog(`FAILED: Login`);
1014
+ alert(currentLang === 'en' ? 'Login failed!' : 'Logare eșuată!');
1015
  }
1016
+ }
1017
 
1018
+ function performLogin(data) {
1019
+ isUserLogged = true;
1020
+ currentUserData = data.user;
1021
 
1022
+ document.getElementById('authSection').classList.remove('active');
1023
+ document.getElementById('sidebar').style.display = 'flex';
1024
+ document.getElementById('home').classList.add('active');
1025
+ document.getElementById('userDisplay').innerText = data.email.toUpperCase();
1026
+ document.getElementById('idBadge').innerText = "ID: " + data.user.id;
1027
+ document.getElementById('setVis').value = data.user.visibility;
1028
+ document.getElementById('setSecret').value = data.user.secret_code;
1029
+ document.getElementById('setNotif').value = data.user.notif_enabled;
1030
+
1031
+ if(data.user.sara_ai) {
1032
+ saraEnabled = true;
1033
+ document.getElementById('saraToggle').classList.add('active');
1034
+ document.getElementById('saraToggle').querySelector('.lang').textContent =
1035
+ currentLang === 'en' ? 'SARA AI: ON' : 'SARA AI: ACTIV';
 
 
 
 
 
1036
  }
1037
+
1038
+ if(data.user.is_admin) {
1039
+ document.getElementById('adminNavItem').style.display = 'block';
1040
+ document.getElementById('adminBadgeDisplay').style.display = 'block';
1041
+ addLog('ADMIN: Access granted');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1042
  }
1043
+
1044
+ if(data.user.is_group_member) {
1045
+ document.getElementById('groupMemberText').style.display = 'block';
1046
+ document.getElementById('groupNonMemberText').style.display = 'none';
1047
+ } else {
1048
+ document.getElementById('groupMemberText').style.display = 'none';
1049
+ document.getElementById('groupNonMemberText').style.display = 'block';
1050
+ document.getElementById('groupInputSection').style.display = 'none';
 
 
 
 
 
 
 
 
 
1051
  }
 
 
1052
 
1053
+ addLog(`SUCCESS: ${data.email}`);
1054
+ addLog(`UID: ${data.user.id}`);
1055
+
1056
+ setInterval(() => {
1057
+ refreshInbox();
1058
+ refreshGroup();
1059
+ }, 500);
1060
+
1061
+ refreshInbox();
1062
+ loadContacts();
1063
+ checkPing();
1064
+ }
1065
+
1066
+ async function register() {
1067
+ const r = await fetch('/api/register', {
1068
+ method: 'POST',
1069
+ headers: {'Content-Type': 'application/json'},
1070
+ body: JSON.stringify({
1071
+ username: document.getElementById('regUser').value,
1072
+ password: document.getElementById('regPass').value
1073
+ })
1074
+ });
1075
+ const d = await r.json();
1076
+ if(d.success) {
1077
+ alert(`${currentLang === 'en' ? 'Account created!' : 'Cont creat!'}\nEmail: ${d.email}\nID: ${d.uid}`);
1078
+ addLog(`CREATED: ${d.uid}`);
1079
+ } else {
1080
+ alert(currentLang === 'en' ? 'Error!' : 'Eroare!');
1081
+ }
1082
+ }
1083
+
1084
+ // ==================== SARA AI ====================
1085
+
1086
+ function toggleSara() {
1087
+ saraEnabled = !saraEnabled;
1088
+ const btn = document.getElementById('saraToggle');
1089
+ if(saraEnabled) {
1090
+ btn.classList.add('active');
1091
+ btn.querySelector('.lang').textContent = currentLang === 'en' ? 'SARA AI: ON' : 'SARA AI: ACTIV';
1092
+ addLog("SARA: Activated");
1093
+ updateSettings();
1094
+ } else {
1095
+ btn.classList.remove('active');
1096
+ btn.querySelector('.lang').textContent = currentLang === 'en' ? 'SARA AI: OFF' : 'SARA AI: OPRIT';
1097
+ addLog("SARA: Deactivated");
1098
+ updateSettings();
1099
  }
1100
+ }
1101
 
1102
+ // ==================== INBOX ====================
1103
 
1104
+ async function refreshInbox() {
1105
+ if(!isUserLogged) return;
1106
 
1107
+ try {
1108
+ const r = await fetch('/api/get_inbox');
1109
+ const list = await r.json();
1110
 
1111
+ if(list.length > lastMsgCount) {
1112
+ if(lastMsgCount !== 0 && currentUserData?.notif_enabled) {
1113
+ const lastMessage = list[list.length-1];
1114
+
1115
+ playNotificationSound();
1116
+ showNotify(currentLang === 'en' ? 'NEW MESSAGE!' : 'MESAJ NOU!');
1117
+ addLog(`INCOMING: ${lastMessage.from}`);
1118
+
1119
+ if('Notification' in window && Notification.permission === 'granted') {
1120
+ if (document.hidden) {
1121
+ sendNotificationToSW(lastMessage.from, lastMessage.content);
1122
+ } else {
1123
+ new Notification('DROPLY!', {
1124
+ body: `${currentLang === 'en' ? 'From' : 'De la'}: ${lastMessage.from}\n${lastMessage.content.substring(0, 50)}`,
1125
+ icon: '/logo.png',
1126
+ vibrate: [200, 100, 200]
1127
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1128
  }
1129
  }
 
1130
  }
1131
+ lastMsgCount = list.length;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1132
  }
 
1133
 
1134
+ const container = document.getElementById('inboxList');
1135
+ container.innerHTML = list.length ? "" : `<span class="lang" data-en="Empty inbox." data-ro="Inbox gol.">Empty inbox.</span>`;
 
 
1136
 
1137
+ list.reverse().forEach(m => {
1138
+ const div = document.createElement('div');
1139
+ div.className = "msg-card";
1140
+ div.innerHTML = `
1141
+ <div class="msg-actions">
1142
+ <button class="btn-small btn-danger" onclick="deleteMessage(${m.id})">×</button>
1143
+ </div>
1144
+ <div style="cursor: pointer; font-weight: 600;" onclick="openMessageModal(${m.id}, '${m.from}', '${m.timestamp}', \`${m.content.replace(/`/g, '\\`').replace(/\n/g, ' ')}\`, '${m.file || ''}')">
1145
+ <small style="color: var(--gray-lighter); font-size: 12px;">${m.from} | ${m.timestamp}</small><br>
1146
+ <div style="margin-top: 8px;">${m.content.substring(0, 100)}${m.content.length > 100 ? '...' : ''}</div>
1147
+ </div>
1148
  `;
1149
+ container.appendChild(div);
1150
+ });
 
1151
 
1152
+ // Re-apply translations
1153
+ switchLanguage(currentLang);
1154
+ } catch (e) {
1155
+ console.error("Refresh inbox error:", e);
1156
  }
1157
+ }
1158
 
1159
+ function openMessageModal(id, from, time, content, file) {
1160
+ document.getElementById('modalFrom').innerHTML = `<span class="lang" data-en="FROM:" data-ro="DE LA:">FROM:</span> ${from}`;
1161
+ document.getElementById('modalTime').innerText = time;
1162
+ document.getElementById('modalContent').innerText = content;
1163
+
1164
+ if(file) {
1165
+ document.getElementById('modalFile').innerHTML = `
1166
+ <button onclick="window.open('/download/${file}', '_blank')" style="margin-top: 16px;">
1167
+ <span class="lang" data-en="DOWNLOAD FILE" data-ro="DESCARCĂ FIȘIER">DOWNLOAD FILE</span>
1168
+ </button>
1169
+ `;
1170
+ } else {
1171
+ document.getElementById('modalFile').innerHTML = '';
1172
  }
1173
+
1174
+ document.getElementById('messageModal').classList.add('active');
1175
+ switchLanguage(currentLang);
1176
+ }
1177
 
1178
+ function closeMessageModal() {
1179
+ document.getElementById('messageModal').classList.remove('active');
1180
+ }
 
 
 
 
 
 
 
 
 
1181
 
1182
+ async function deleteMessage(msgId) {
1183
+ if(!confirm(currentLang === 'en' ? 'Delete this message?' : 'Ștergi acest mesaj?')) return;
1184
 
1185
+ await fetch('/api/delete_message', {
1186
+ method: 'POST',
1187
+ headers: {'Content-Type': 'application/json'},
1188
+ body: JSON.stringify({message_id: msgId})
1189
+ });
1190
+
1191
+ showNotify(currentLang === 'en' ? 'DELETED' : 'ȘTERS');
1192
+ refreshInbox();
1193
+ }
1194
+
1195
+ // ==================== SEND ====================
1196
+
1197
+ async function sendMail() {
1198
+ const fd = new FormData();
1199
+ const dest = document.getElementById('targetDest').value;
1200
+ fd.append('destinatar', dest);
1201
+ fd.append('mesaj', document.getElementById('targetMsg').value);
1202
+ const file = document.getElementById('targetFile').files[0];
1203
+ if(file) fd.append('fisier', file);
1204
+
1205
+ addLog(`SENDING to ${dest}...`);
1206
+
1207
+ const r = await fetch('/api/send_mail', { method: 'POST', body: fd });
1208
+ const d = await r.json();
1209
+
1210
+ if(d.success) {
1211
+ showNotify(currentLang === 'en' ? 'SENT!' : 'TRIMIS!');
1212
+ document.getElementById('targetMsg').value = "";
1213
+ document.getElementById('targetFile').value = "";
1214
+ addLog(`SUCCESS: Sent to ${dest}`);
1215
+ } else {
1216
+ addLog(`ERROR: ${d.error}`);
1217
+ alert(currentLang === 'en' ? 'Error!' : 'Eroare!');
1218
  }
1219
+ }
1220
 
1221
+ function reportBug() {
1222
+ const now = new Date();
1223
+ const dateStr = `${now.getDate()}/${now.getMonth()+1}/${now.getFullYear()}`;
1224
+ const timeStr = `${now.getHours()}:${String(now.getMinutes()).padStart(2, '0')}`;
1225
+
1226
+ document.getElementById('targetDest').value = 'admin001';
1227
+ document.getElementById('targetMsg').value = `Bug Report: [${dateStr} | ${timeStr}]\n\n`;
1228
+
1229
+ showSection('send');
1230
+ showNotify(currentLang === 'en' ? 'FILL REPORT' : 'COMPLETEAZĂ');
1231
+ addLog("BUG REPORT: Template loaded");
1232
+ }
1233
+
1234
+ // ==================== GROUP ====================
1235
 
1236
+ async function sendGroup() {
1237
+ const msg = document.getElementById('groupMsgInput').value;
1238
+ if(!msg) return;
1239
 
1240
+ const r = await fetch('/api/group/send', {
1241
+ method: 'POST',
1242
+ headers: {'Content-Type': 'application/json'},
1243
+ body: JSON.stringify({content: msg})
1244
+ });
1245
+
1246
+ const d = await r.json();
1247
+
1248
+ if(d.success) {
1249
+ document.getElementById('groupMsgInput').value = "";
1250
+ refreshGroup();
1251
+ } else {
1252
+ alert(currentLang === 'en' ? 'No access!' : 'Fără acces!');
 
 
 
 
 
1253
  }
1254
+ }
1255
 
1256
+ async function refreshGroup() {
1257
+ if(!isUserLogged) return;
1258
+
1259
+ try {
1260
+ const r = await fetch('/api/group/get');
1261
+ const msgs = await r.json();
1262
+ const box = document.getElementById('groupList');
1263
+
1264
+ box.innerHTML = msgs.map(m => `
1265
+ <div class="group-msg">
1266
+ <div style="font-size: 12px; color: var(--gray-lighter); font-weight: 600; margin-bottom: 8px;">
1267
+ ${m.user} <span style="color: var(--gray-light); font-weight: 500;">[${m.timestamp}]</span>
1268
+ ${currentUserData?.is_admin ?
1269
+ `<button class="btn-small btn-danger" onclick="deleteGroupMessage(${m.id})" style="float:right;">×</button>`
1270
+ : ''}
 
 
1271
  </div>
1272
+ <div style="font-weight: 500;">${m.content}</div>
1273
+ </div>
1274
+ `).join('');
1275
+ } catch (e) {}
1276
+ }
1277
 
1278
+ async function deleteGroupMessage(msgId) {
1279
+ if(!confirm(currentLang === 'en' ? 'Delete?' : 'Ștergi?')) return;
1280
+
1281
+ await fetch('/api/group/delete', {
1282
+ method: 'POST',
1283
+ headers: {'Content-Type': 'application/json'},
1284
+ body: JSON.stringify({message_id: msgId})
1285
+ });
1286
+
1287
+ showNotify(currentLang === 'en' ? 'DELETED' : 'ȘTERS');
1288
+ refreshGroup();
1289
+ }
1290
 
1291
+ async function requestGroupInvite() {
1292
+ const r = await fetch('/api/group/request_invite', { method: 'POST' });
1293
+ const d = await r.json();
1294
+
1295
+ if(d.success) {
1296
+ showNotify(currentLang === 'en' ? 'REQUEST SENT!' : 'CERERE TRIMISĂ!');
1297
+ addLog("GROUP: Invitation requested");
1298
+ } else {
1299
+ alert(d.error);
 
1300
  }
1301
+ }
1302
+
1303
+ // ==================== CONTACTS ====================
1304
 
1305
+ function saveContact() {
1306
+ const name = document.getElementById('cName').value;
1307
+ const id = document.getElementById('cId').value;
1308
+ if(!name || !id) return;
1309
 
1310
+ let contacts = JSON.parse(localStorage.getItem('droply_contacts') || '[]');
1311
+ contacts.push({name, id});
1312
+ localStorage.setItem('droply_contacts', JSON.stringify(contacts));
1313
+
1314
+ document.getElementById('cName').value = "";
1315
+ document.getElementById('cId').value = "";
1316
+ loadContacts();
1317
+ showNotify(currentLang === 'en' ? 'SAVED' : 'SALVAT');
1318
+ addLog(`CONTACT: ${name}`);
1319
+ }
1320
+
1321
+ function loadContacts() {
1322
+ const contacts = JSON.parse(localStorage.getItem('droply_contacts') || '[]');
1323
+ const container = document.getElementById('contactList');
1324
+ container.innerHTML = "";
1325
+
1326
+ contacts.forEach((c, index) => {
1327
+ const div = document.createElement('div');
1328
+ div.className = "item-list";
1329
+ div.innerHTML = `
1330
+ <div class="item-info"><strong>${c.name}</strong><br><small>${c.id}</small></div>
1331
+ <div>
1332
+ <button class="btn-small" onclick="useContact('${c.id}')">USE</button>
1333
+ <button class="btn-small btn-danger" onclick="delContact(${index})">×</button>
1334
+ </div>
1335
+ `;
1336
+ container.appendChild(div);
1337
+ });
1338
+ }
1339
 
1340
+ function useContact(id) {
1341
+ document.getElementById('targetDest').value = id;
1342
+ showSection('send');
1343
+ }
1344
+
1345
+ function delContact(i) {
1346
+ let contacts = JSON.parse(localStorage.getItem('droply_contacts') || '[]');
1347
+ contacts.splice(i, 1);
1348
+ localStorage.setItem('droply_contacts', JSON.stringify(contacts));
1349
+ loadContacts();
1350
+ addLog(`CONTACT: Deleted`);
1351
+ }
1352
+
1353
+ // ==================== SETTINGS ====================
1354
+
1355
+ async function updateSettings() {
1356
+ const notif = parseInt(document.getElementById('setNotif').value);
1357
+
1358
+ await fetch('/api/update_settings', {
1359
+ method: 'POST',
1360
+ headers: {'Content-Type': 'application/json'},
1361
+ body: JSON.stringify({
1362
+ visibility: document.getElementById('setVis').value,
1363
+ secret_code: document.getElementById('setSecret').value,
1364
+ sara_ai: saraEnabled,
1365
+ notif_enabled: notif
1366
+ })
1367
+ });
1368
+
1369
+ if(currentUserData) {
1370
+ currentUserData.notif_enabled = notif;
1371
  }
1372
+
1373
+ showNotify(currentLang === 'en' ? 'SAVED' : 'SALVAT');
1374
+ addLog(`SETTINGS: Updated`);
1375
+ }
1376
 
1377
+ async function changePassword() {
1378
+ const oldPass = document.getElementById('oldPass').value;
1379
+ const newPass = document.getElementById('newPass').value;
1380
+
1381
+ if(!oldPass || !newPass) {
1382
+ alert(currentLang === 'en' ? 'Fill all fields!' : 'Completează toate câmpurile!');
1383
+ return;
1384
  }
1385
 
1386
+ const r = await fetch('/api/change_password', {
1387
+ method: 'POST',
1388
+ headers: {'Content-Type': 'application/json'},
1389
+ body: JSON.stringify({old_password: oldPass, new_password: newPass})
1390
+ });
1391
+
1392
+ const d = await r.json();
1393
+
1394
+ if(d.success) {
1395
+ showNotify(currentLang === 'en' ? 'PASSWORD CHANGED!' : 'PAROLĂ SCHIMBATĂ!');
1396
+ document.getElementById('oldPass').value = "";
1397
+ document.getElementById('newPass').value = "";
1398
+ addLog("SECURITY: Password changed");
1399
+ } else {
1400
+ alert(currentLang === 'en' ? 'Wrong password!' : 'Parolă greșită!');
1401
+ }
1402
+ }
1403
+
1404
+ async function checkPing() {
1405
+ try {
1406
+ const start = Date.now();
1407
+ await fetch('/api/server_ping');
1408
+ const ping = Date.now() - start;
1409
+
1410
+ document.getElementById('serverPing').innerText = `${currentLang === 'en' ? 'Online' : 'Online'} - ${ping}ms`;
1411
+ addLog(`PING: ${ping}ms`);
1412
+ } catch(e) {
1413
+ document.getElementById('serverPing').innerText = currentLang === 'en' ? 'Offline' : 'Offline';
1414
+ addLog('PING: Server offline');
1415
  }
1416
+ }
1417
 
1418
+ // ==================== ADMIN ====================
1419
 
1420
+ async function loadAdminData() {
1421
+ if(!currentUserData?.is_admin) return;
1422
 
1423
+ const rUsers = await fetch('/api/admin/get_users');
1424
+ const dUsers = await rUsers.json();
1425
+
1426
+ if(dUsers.success) {
1427
+ const container = document.getElementById('adminUsersList');
1428
+ container.innerHTML = '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1429
 
1430
+ dUsers.users.forEach(u => {
1431
+ const div = document.createElement('div');
1432
+ div.className = 'item-list';
1433
+ div.innerHTML = `
1434
+ <div class="item-info">
1435
+ <strong>${u.email}</strong><br>
1436
+ <small>ID: ${u.id} | IP: ${u.ip}</small>
1437
+ </div>
1438
+ `;
1439
+ container.appendChild(div);
1440
  });
1441
 
1442
+ addLog(`ADMIN: ${dUsers.users.length} users`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1443
  }
 
 
1444
 
1445
+ const rInvites = await fetch('/api/admin/get_invitations');
1446
+ const dInvites = await rInvites.json();
1447
+
1448
+ if(dInvites.success) {
1449
+ const container = document.getElementById('adminInvitesList');
1450
+ container.innerHTML = '';
1451
 
1452
+ if(dInvites.invitations.length === 0) {
1453
+ container.innerHTML = '<p style="color:var(--gray-light); font-size:13px; font-weight:500;">No pending requests</p>';
1454
+ } else {
1455
+ dInvites.invitations.forEach(inv => {
 
1456
  const div = document.createElement('div');
1457
  div.className = 'item-list';
1458
  div.innerHTML = `
1459
  <div class="item-info">
1460
+ <strong>${inv.user_email}</strong><br>
1461
+ <small>${inv.timestamp}</small>
1462
  </div>
1463
+ <button class="btn-small" onclick="approveInvite(${inv.id})">✓</button>
1464
  `;
1465
  container.appendChild(div);
1466
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1467
  }
1468
  }
1469
+ }
1470
 
1471
+ async function approveInvite(inviteId) {
1472
+ const r = await fetch('/api/admin/approve_invitation', {
1473
+ method: 'POST',
1474
+ headers: {'Content-Type': 'application/json'},
1475
+ body: JSON.stringify({invitation_id: inviteId})
1476
+ });
1477
+
1478
+ const d = await r.json();
1479
+
1480
+ if(d.success) {
1481
+ showNotify(currentLang === 'en' ? 'APPROVED!' : 'APROBAT!');
1482
+ loadAdminData();
1483
+ addLog("ADMIN: Invitation approved");
 
1484
  }
1485
+ }
1486
 
1487
+ async function triggerMaintenance() {
1488
+ if(!confirm(currentLang === 'en' ? 'Server will shut down in 60s!' : 'Serverul se închide în 60s!')) {
1489
+ return;
1490
+ }
1491
+
1492
+ const r = await fetch('/api/admin/broadcast_warning', { method: 'POST' });
1493
+ const d = await r.json();
1494
+
1495
+ if(d.success) {
1496
+ showNotify('⚠ MAINTENANCE!');
1497
+ addLog("ADMIN: Maintenance activated");
1498
 
1499
+ document.getElementById('warningBanner').style.display = 'block';
 
1500
 
1501
+ let seconds = 60;
1502
+ const countdown = setInterval(() => {
1503
+ seconds--;
1504
+ document.getElementById('warningBanner').innerText =
1505
+ `⚠ ${currentLang === 'en' ? 'SHUTTING DOWN IN' : 'ÎNCHIDERE ÎN'} ${seconds}s`;
 
1506
 
1507
+ if(seconds <= 0) {
1508
+ clearInterval(countdown);
 
 
1509
  document.getElementById('warningBanner').innerText =
1510
+ currentLang === 'en' ? '🔴 SERVER OFFLINE' : '🔴 SERVER OFFLINE';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1511
  }
1512
+ }, 1000);
 
 
1513
  }
1514
+ }
1515
 
1516
+ async function checkShutdown() {
1517
+ try {
1518
+ const r = await fetch('/api/check_shutdown');
1519
+ const d = await r.json();
1520
+
1521
+ if(d.shutdown_scheduled && !document.getElementById('warningBanner').style.display) {
1522
+ document.getElementById('warningBanner').style.display = 'block';
1523
+ addLog("WARNING: Shutdown scheduled!");
1524
+ }
1525
+ } catch(e) {}
1526
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1527
 
1528
  </script>
1529
  </body>