testdeep123 commited on
Commit
15f5037
·
verified ·
1 Parent(s): b13b85a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +641 -40
app.py CHANGED
@@ -16,49 +16,651 @@ TEMPLATE = """
16
  <head>
17
  <meta charset="UTF-8">
18
  <title>HuggingFace Drive - {{ path or 'Root' }}</title>
 
19
  <style>
20
- body { background:#121212; color:#e0e0e0; font-family:Arial,sans-serif; padding:20px }
21
- h2 { margin-top:0 }
22
- .item { padding:6px 12px; margin:6px 0; background:#1e1e1e; border-radius:4px }
23
- .item:hover { background:#2a2a2a }
24
- .actions { margin-bottom:16px }
25
- button,input[type="file"] { background:#333; color:#fff; border:none; padding:6px 10px; margin-right:6px; border-radius:4px; cursor:pointer }
26
- button:hover { background:#444 }
27
- a { color:#8ab4f8; text-decoration:none }
28
- form { display:inline }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  </style>
30
- <script>
31
- function nav(p){ location.href='/?path='+encodeURIComponent(p) }
32
- async function del(p){ if(!confirm('Delete '+p+'?'))return; await fetch('/delete',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p})}); location.reload() }
33
- async function mkfld(cp){ let n=prompt('Folder name:'); if(!n)return; await fetch('/create_folder',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({path:cp+'/'+n})}); location.reload() }
34
- async function ren(o){ let n=prompt('Rename to:',o); if(!n||n===o)return; await fetch('/rename',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({old_path:o,new_path:n})}); location.reload() }
35
- </script>
36
  </head>
37
  <body>
38
- <h2>📁 Drive — {{ path or 'Root' }}</h2>
39
- {% if parent %}
40
- <div><a href="#" onclick="nav('{{ parent }}')">⬅️ Back to {{ parent or 'Root' }}</a></div>
41
- {% endif %}
42
- <div class="actions">
43
- <form action="/upload" method="post" enctype="multipart/form-data">
44
- <input type="file" name="file" required>
45
- <input type="hidden" name="path" value="{{ path }}">
46
- <button type="submit">Upload</button>
47
- </form>
48
- <button onclick="mkfld('{{ path }}')">New Folder</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  </div>
50
- <hr>
51
- {% for item in items %}
52
- <div class="item">
53
- {% if item.type=='dir' %}
54
- 📂 <a href="#" onclick="nav('{{ item.path }}')">{{ item.name }}</a>
55
- {% else %}
56
- 📄 {{ item.name }} <a href="/download?path={{ item.path }}"><button>Download</button></a>
57
- {% endif %}
58
- <button onclick="ren('{{ item.path }}')">Rename</button>
59
- <button onclick="del('{{ item.path }}')">Delete</button>
60
  </div>
61
- {% endfor %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  </body>
63
  </html>
64
  """
@@ -86,8 +688,7 @@ def list_folder(path=""):
86
  @app.route("/", methods=["GET"])
87
  def index():
88
  path = request.args.get("path","").strip("/")
89
- parent = "/".join(path.split("/")[:-1]) if path else None
90
- return render_template_string(TEMPLATE, items=list_folder(path), path=path, parent=parent)
91
 
92
  @app.route("/download", methods=["GET"])
93
  def download():
@@ -141,4 +742,4 @@ def rename():
141
  return jsonify(status="ok")
142
 
143
  if __name__ == "__main__":
144
- app.run(debug=True, host="0.0.0.0", port=7860)
 
16
  <head>
17
  <meta charset="UTF-8">
18
  <title>HuggingFace Drive - {{ path or 'Root' }}</title>
19
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
20
  <style>
21
+ * {
22
+ margin: 0;
23
+ padding: 0;
24
+ box-sizing: border-box;
25
+ }
26
+
27
+ body {
28
+ background: linear-gradient(135deg, #0f0f23 0%, #1a1a2e 100%);
29
+ color: #e2e8f0;
30
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
31
+ min-height: 100vh;
32
+ overflow-x: hidden;
33
+ }
34
+
35
+ .container {
36
+ max-width: 1200px;
37
+ margin: 0 auto;
38
+ padding: 20px;
39
+ }
40
+
41
+ .header {
42
+ background: rgba(255, 255, 255, 0.05);
43
+ backdrop-filter: blur(10px);
44
+ border-radius: 16px;
45
+ padding: 24px;
46
+ margin-bottom: 24px;
47
+ border: 1px solid rgba(255, 255, 255, 0.1);
48
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
49
+ }
50
+
51
+ .title {
52
+ font-size: 2rem;
53
+ font-weight: 700;
54
+ margin-bottom: 16px;
55
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
56
+ -webkit-background-clip: text;
57
+ -webkit-text-fill-color: transparent;
58
+ background-clip: text;
59
+ }
60
+
61
+ .breadcrumb {
62
+ display: flex;
63
+ align-items: center;
64
+ gap: 8px;
65
+ margin-bottom: 20px;
66
+ flex-wrap: wrap;
67
+ }
68
+
69
+ .breadcrumb-item {
70
+ background: rgba(255, 255, 255, 0.1);
71
+ color: #94a3b8;
72
+ padding: 6px 12px;
73
+ border-radius: 20px;
74
+ font-size: 0.875rem;
75
+ cursor: pointer;
76
+ transition: all 0.3s ease;
77
+ border: 1px solid rgba(255, 255, 255, 0.1);
78
+ }
79
+
80
+ .breadcrumb-item:hover {
81
+ background: rgba(102, 126, 234, 0.2);
82
+ color: #e2e8f0;
83
+ transform: translateY(-1px);
84
+ }
85
+
86
+ .breadcrumb-item.active {
87
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
88
+ color: white;
89
+ }
90
+
91
+ .breadcrumb-separator {
92
+ color: #64748b;
93
+ font-size: 0.875rem;
94
+ }
95
+
96
+ .actions {
97
+ display: flex;
98
+ gap: 12px;
99
+ margin-bottom: 24px;
100
+ flex-wrap: wrap;
101
+ }
102
+
103
+ .upload-container {
104
+ position: relative;
105
+ overflow: hidden;
106
+ display: inline-block;
107
+ }
108
+
109
+ .file-input {
110
+ position: absolute;
111
+ left: -9999px;
112
+ opacity: 0;
113
+ }
114
+
115
+ .btn {
116
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
117
+ color: white;
118
+ border: none;
119
+ padding: 12px 24px;
120
+ border-radius: 12px;
121
+ cursor: pointer;
122
+ font-weight: 600;
123
+ font-size: 0.875rem;
124
+ transition: all 0.3s ease;
125
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
126
+ border: 1px solid rgba(255, 255, 255, 0.1);
127
+ }
128
+
129
+ .btn:hover {
130
+ transform: translateY(-2px);
131
+ box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
132
+ }
133
+
134
+ .btn-secondary {
135
+ background: rgba(255, 255, 255, 0.1);
136
+ color: #e2e8f0;
137
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
138
+ }
139
+
140
+ .btn-secondary:hover {
141
+ background: rgba(255, 255, 255, 0.15);
142
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
143
+ }
144
+
145
+ .file-grid {
146
+ display: grid;
147
+ gap: 16px;
148
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
149
+ }
150
+
151
+ .file-item {
152
+ background: rgba(255, 255, 255, 0.05);
153
+ backdrop-filter: blur(10px);
154
+ border: 1px solid rgba(255, 255, 255, 0.1);
155
+ border-radius: 16px;
156
+ padding: 20px;
157
+ transition: all 0.3s ease;
158
+ cursor: pointer;
159
+ position: relative;
160
+ overflow: hidden;
161
+ }
162
+
163
+ .file-item::before {
164
+ content: '';
165
+ position: absolute;
166
+ top: 0;
167
+ left: -100%;
168
+ width: 100%;
169
+ height: 100%;
170
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
171
+ transition: left 0.5s ease;
172
+ }
173
+
174
+ .file-item:hover::before {
175
+ left: 100%;
176
+ }
177
+
178
+ .file-item:hover {
179
+ transform: translateY(-4px);
180
+ box-shadow: 0 12px 30px rgba(0, 0, 0, 0.3);
181
+ border-color: rgba(102, 126, 234, 0.3);
182
+ }
183
+
184
+ .file-content {
185
+ display: flex;
186
+ align-items: center;
187
+ justify-content: space-between;
188
+ position: relative;
189
+ z-index: 2;
190
+ }
191
+
192
+ .file-info {
193
+ display: flex;
194
+ align-items: center;
195
+ gap: 12px;
196
+ flex: 1;
197
+ }
198
+
199
+ .file-icon {
200
+ font-size: 1.5rem;
201
+ width: 40px;
202
+ height: 40px;
203
+ display: flex;
204
+ align-items: center;
205
+ justify-content: center;
206
+ border-radius: 10px;
207
+ background: rgba(255, 255, 255, 0.1);
208
+ }
209
+
210
+ .folder-icon {
211
+ background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%);
212
+ }
213
+
214
+ .file-name {
215
+ font-weight: 500;
216
+ font-size: 1rem;
217
+ color: #f1f5f9;
218
+ }
219
+
220
+ .dropdown {
221
+ position: relative;
222
+ }
223
+
224
+ .dropdown-toggle {
225
+ background: rgba(255, 255, 255, 0.1);
226
+ border: none;
227
+ color: #94a3b8;
228
+ padding: 8px;
229
+ border-radius: 8px;
230
+ cursor: pointer;
231
+ transition: all 0.3s ease;
232
+ font-size: 1.25rem;
233
+ width: 36px;
234
+ height: 36px;
235
+ display: flex;
236
+ align-items: center;
237
+ justify-content: center;
238
+ }
239
+
240
+ .dropdown-toggle:hover {
241
+ background: rgba(255, 255, 255, 0.2);
242
+ color: #e2e8f0;
243
+ }
244
+
245
+ .dropdown-menu {
246
+ position: absolute;
247
+ top: 100%;
248
+ right: 0;
249
+ background: rgba(15, 15, 35, 0.95);
250
+ backdrop-filter: blur(20px);
251
+ border: 1px solid rgba(255, 255, 255, 0.1);
252
+ border-radius: 12px;
253
+ padding: 8px;
254
+ min-width: 150px;
255
+ opacity: 0;
256
+ visibility: hidden;
257
+ transform: translateY(-10px);
258
+ transition: all 0.3s ease;
259
+ z-index: 1000;
260
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
261
+ }
262
+
263
+ .dropdown.active .dropdown-menu {
264
+ opacity: 1;
265
+ visibility: visible;
266
+ transform: translateY(0);
267
+ }
268
+
269
+ .dropdown-item {
270
+ display: flex;
271
+ align-items: center;
272
+ gap: 8px;
273
+ padding: 10px 12px;
274
+ border-radius: 8px;
275
+ cursor: pointer;
276
+ transition: all 0.2s ease;
277
+ font-size: 0.875rem;
278
+ color: #cbd5e1;
279
+ }
280
+
281
+ .dropdown-item:hover {
282
+ background: rgba(255, 255, 255, 0.1);
283
+ color: #f1f5f9;
284
+ }
285
+
286
+ .dropdown-item.danger:hover {
287
+ background: rgba(239, 68, 68, 0.2);
288
+ color: #fca5a5;
289
+ }
290
+
291
+ /* Modal Styles */
292
+ .modal-overlay {
293
+ position: fixed;
294
+ top: 0;
295
+ left: 0;
296
+ width: 100%;
297
+ height: 100%;
298
+ background: rgba(0, 0, 0, 0.7);
299
+ backdrop-filter: blur(5px);
300
+ display: flex;
301
+ align-items: center;
302
+ justify-content: center;
303
+ z-index: 2000;
304
+ opacity: 0;
305
+ visibility: hidden;
306
+ transition: all 0.3s ease;
307
+ }
308
+
309
+ .modal-overlay.active {
310
+ opacity: 1;
311
+ visibility: visible;
312
+ }
313
+
314
+ .modal {
315
+ background: rgba(15, 15, 35, 0.95);
316
+ backdrop-filter: blur(20px);
317
+ border: 1px solid rgba(255, 255, 255, 0.1);
318
+ border-radius: 20px;
319
+ padding: 32px;
320
+ max-width: 400px;
321
+ width: 90%;
322
+ transform: scale(0.8);
323
+ transition: all 0.3s ease;
324
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
325
+ }
326
+
327
+ .modal-overlay.active .modal {
328
+ transform: scale(1);
329
+ }
330
+
331
+ .modal-title {
332
+ font-size: 1.25rem;
333
+ font-weight: 600;
334
+ margin-bottom: 16px;
335
+ color: #f1f5f9;
336
+ }
337
+
338
+ .modal-input {
339
+ width: 100%;
340
+ background: rgba(255, 255, 255, 0.1);
341
+ border: 1px solid rgba(255, 255, 255, 0.2);
342
+ border-radius: 12px;
343
+ padding: 12px 16px;
344
+ color: #f1f5f9;
345
+ font-size: 1rem;
346
+ margin-bottom: 20px;
347
+ }
348
+
349
+ .modal-input:focus {
350
+ outline: none;
351
+ border-color: #667eea;
352
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
353
+ }
354
+
355
+ .modal-actions {
356
+ display: flex;
357
+ gap: 12px;
358
+ justify-content: flex-end;
359
+ }
360
+
361
+ .btn-danger {
362
+ background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
363
+ box-shadow: 0 4px 15px rgba(239, 68, 68, 0.3);
364
+ }
365
+
366
+ .btn-danger:hover {
367
+ box-shadow: 0 8px 25px rgba(239, 68, 68, 0.4);
368
+ }
369
+
370
+ /* Loading Animation */
371
+ .loading {
372
+ display: inline-block;
373
+ width: 20px;
374
+ height: 20px;
375
+ border: 2px solid rgba(255, 255, 255, 0.3);
376
+ border-radius: 50%;
377
+ border-top-color: #667eea;
378
+ animation: spin 1s ease-in-out infinite;
379
+ }
380
+
381
+ @keyframes spin {
382
+ to { transform: rotate(360deg); }
383
+ }
384
+
385
+ /* Responsive Design */
386
+ @media (max-width: 768px) {
387
+ .container {
388
+ padding: 16px;
389
+ }
390
+
391
+ .file-grid {
392
+ grid-template-columns: 1fr;
393
+ }
394
+
395
+ .actions {
396
+ flex-direction: column;
397
+ }
398
+
399
+ .breadcrumb {
400
+ flex-direction: column;
401
+ align-items: flex-start;
402
+ }
403
+ }
404
+
405
+ /* Custom Scrollbar */
406
+ ::-webkit-scrollbar {
407
+ width: 8px;
408
+ }
409
+
410
+ ::-webkit-scrollbar-track {
411
+ background: rgba(255, 255, 255, 0.1);
412
+ }
413
+
414
+ ::-webkit-scrollbar-thumb {
415
+ background: rgba(102, 126, 234, 0.5);
416
+ border-radius: 4px;
417
+ }
418
+
419
+ ::-webkit-scrollbar-thumb:hover {
420
+ background: rgba(102, 126, 234, 0.7);
421
+ }
422
  </style>
 
 
 
 
 
 
423
  </head>
424
  <body>
425
+ <div class="container">
426
+ <div class="header">
427
+ <h1 class="title">🚀 HuggingFace Drive</h1>
428
+
429
+ <!-- Breadcrumb Navigation -->
430
+ <div class="breadcrumb">
431
+ <span class="breadcrumb-item {{ 'active' if not path else '' }}" onclick="nav('')">
432
+ 🏠 Home
433
+ </span>
434
+ {% if path %}
435
+ {% set parts = path.split('/') %}
436
+ {% for i in range(parts|length) %}
437
+ <span class="breadcrumb-separator">›</span>
438
+ {% set current_path = parts[:i+1]|join('/') %}
439
+ <span class="breadcrumb-item {{ 'active' if current_path == path else '' }}"
440
+ onclick="nav('{{ current_path }}')">
441
+ 📁 {{ parts[i] }}
442
+ </span>
443
+ {% endfor %}
444
+ {% endif %}
445
+ </div>
446
+
447
+ <!-- Actions -->
448
+ <div class="actions">
449
+ <form action="/upload" method="post" enctype="multipart/form-data" class="upload-container">
450
+ <input type="file" name="file" required class="file-input" id="fileInput">
451
+ <input type="hidden" name="path" value="{{ path }}">
452
+ <label for="fileInput" class="btn">
453
+ 📤 Upload File
454
+ </label>
455
+ </form>
456
+ <button class="btn btn-secondary" onclick="createFolder('{{ path }}')">
457
+ 📁 New Folder
458
+ </button>
459
+ </div>
460
+ </div>
461
+
462
+ <!-- File Grid -->
463
+ <div class="file-grid">
464
+ {% for item in items %}
465
+ <div class="file-item">
466
+ <div class="file-content">
467
+ <div class="file-info" onclick="{% if item.type=='dir' %}nav('{{ item.path }}'){% endif %}">
468
+ <div class="file-icon {{ 'folder-icon' if item.type=='dir' else '' }}">
469
+ {{ '📁' if item.type=='dir' else '📄' }}
470
+ </div>
471
+ <div class="file-name">{{ item.name }}</div>
472
+ </div>
473
+
474
+ <div class="dropdown">
475
+ <button class="dropdown-toggle" onclick="toggleDropdown(this)">⋮</button>
476
+ <div class="dropdown-menu">
477
+ {% if item.type == 'file' %}
478
+ <div class="dropdown-item" onclick="download('{{ item.path }}')">
479
+ 📥 Download
480
+ </div>
481
+ {% endif %}
482
+ <div class="dropdown-item" onclick="showRenameModal('{{ item.path }}', '{{ item.name }}')">
483
+ ✏️ Rename
484
+ </div>
485
+ <div class="dropdown-item danger" onclick="showDeleteModal('{{ item.path }}')">
486
+ 🗑️ Delete
487
+ </div>
488
+ </div>
489
+ </div>
490
+ </div>
491
+ </div>
492
+ {% endfor %}
493
+ </div>
494
  </div>
495
+
496
+ <!-- Rename Modal -->
497
+ <div class="modal-overlay" id="renameModal">
498
+ <div class="modal">
499
+ <h3 class="modal-title">Rename Item</h3>
500
+ <input type="text" class="modal-input" id="renameInput" placeholder="Enter new name">
501
+ <div class="modal-actions">
502
+ <button class="btn btn-secondary" onclick="closeModal('renameModal')">Cancel</button>
503
+ <button class="btn" onclick="confirmRename()">Rename</button>
504
+ </div>
505
  </div>
506
+ </div>
507
+
508
+ <!-- Delete Confirmation Modal -->
509
+ <div class="modal-overlay" id="deleteModal">
510
+ <div class="modal">
511
+ <h3 class="modal-title">Confirm Deletion</h3>
512
+ <p style="margin-bottom: 20px; color: #cbd5e1;">Are you sure you want to delete this item? This action cannot be undone.</p>
513
+ <div class="modal-actions">
514
+ <button class="btn btn-secondary" onclick="closeModal('deleteModal')">Cancel</button>
515
+ <button class="btn btn-danger" onclick="confirmDelete()">Delete</button>
516
+ </div>
517
+ </div>
518
+ </div>
519
+
520
+ <script>
521
+ let currentRenamePath = '';
522
+ let currentDeletePath = '';
523
+ let activeDropdown = null;
524
+
525
+ function nav(p) {
526
+ location.href = '/?path=' + encodeURIComponent(p);
527
+ }
528
+
529
+ function download(path) {
530
+ window.open('/download?path=' + encodeURIComponent(path), '_blank');
531
+ }
532
+
533
+ function toggleDropdown(button) {
534
+ const dropdown = button.closest('.dropdown');
535
+
536
+ // Close other dropdowns
537
+ if (activeDropdown && activeDropdown !== dropdown) {
538
+ activeDropdown.classList.remove('active');
539
+ }
540
+
541
+ dropdown.classList.toggle('active');
542
+ activeDropdown = dropdown.classList.contains('active') ? dropdown : null;
543
+ }
544
+
545
+ function showRenameModal(path, currentName) {
546
+ currentRenamePath = path;
547
+ document.getElementById('renameInput').value = currentName;
548
+ showModal('renameModal');
549
+ setTimeout(() => document.getElementById('renameInput').focus(), 100);
550
+ closeAllDropdowns();
551
+ }
552
+
553
+ function showDeleteModal(path) {
554
+ currentDeletePath = path;
555
+ showModal('deleteModal');
556
+ closeAllDropdowns();
557
+ }
558
+
559
+ function showModal(modalId) {
560
+ document.getElementById(modalId).classList.add('active');
561
+ }
562
+
563
+ function closeModal(modalId) {
564
+ document.getElementById(modalId).classList.remove('active');
565
+ }
566
+
567
+ function closeAllDropdowns() {
568
+ document.querySelectorAll('.dropdown.active').forEach(dropdown => {
569
+ dropdown.classList.remove('active');
570
+ });
571
+ activeDropdown = null;
572
+ }
573
+
574
+ async function confirmRename() {
575
+ const newName = document.getElementById('renameInput').value.trim();
576
+ if (!newName) return;
577
+
578
+ try {
579
+ const response = await fetch('/rename', {
580
+ method: 'POST',
581
+ headers: { 'Content-Type': 'application/json' },
582
+ body: JSON.stringify({
583
+ old_path: currentRenamePath,
584
+ new_path: newName
585
+ })
586
+ });
587
+
588
+ if (response.ok) {
589
+ closeModal('renameModal');
590
+ location.reload();
591
+ }
592
+ } catch (error) {
593
+ console.error('Rename failed:', error);
594
+ }
595
+ }
596
+
597
+ async function confirmDelete() {
598
+ try {
599
+ const response = await fetch('/delete', {
600
+ method: 'POST',
601
+ headers: { 'Content-Type': 'application/json' },
602
+ body: JSON.stringify({ path: currentDeletePath })
603
+ });
604
+
605
+ if (response.ok) {
606
+ closeModal('deleteModal');
607
+ location.reload();
608
+ }
609
+ } catch (error) {
610
+ console.error('Delete failed:', error);
611
+ }
612
+ }
613
+
614
+ async function createFolder(currentPath) {
615
+ const name = prompt('Enter folder name:');
616
+ if (!name) return;
617
+
618
+ try {
619
+ const folderPath = currentPath ? `${currentPath}/${name}` : name;
620
+ const response = await fetch('/create_folder', {
621
+ method: 'POST',
622
+ headers: { 'Content-Type': 'application/json' },
623
+ body: JSON.stringify({ path: folderPath })
624
+ });
625
+
626
+ if (response.ok) {
627
+ location.reload();
628
+ }
629
+ } catch (error) {
630
+ console.error('Create folder failed:', error);
631
+ }
632
+ }
633
+
634
+ // Close dropdowns when clicking outside
635
+ document.addEventListener('click', function(e) {
636
+ if (!e.target.closest('.dropdown')) {
637
+ closeAllDropdowns();
638
+ }
639
+ });
640
+
641
+ // Handle file input change for better UX
642
+ document.getElementById('fileInput').addEventListener('change', function(e) {
643
+ if (e.target.files.length > 0) {
644
+ e.target.closest('form').submit();
645
+ }
646
+ });
647
+
648
+ // Handle modal close on overlay click
649
+ document.querySelectorAll('.modal-overlay').forEach(overlay => {
650
+ overlay.addEventListener('click', function(e) {
651
+ if (e.target === overlay) {
652
+ overlay.classList.remove('active');
653
+ }
654
+ });
655
+ });
656
+
657
+ // Handle Enter key in rename modal
658
+ document.getElementById('renameInput').addEventListener('keypress', function(e) {
659
+ if (e.key === 'Enter') {
660
+ confirmRename();
661
+ }
662
+ });
663
+ </script>
664
  </body>
665
  </html>
666
  """
 
688
  @app.route("/", methods=["GET"])
689
  def index():
690
  path = request.args.get("path","").strip("/")
691
+ return render_template_string(TEMPLATE, items=list_folder(path), path=path)
 
692
 
693
  @app.route("/download", methods=["GET"])
694
  def download():
 
742
  return jsonify(status="ok")
743
 
744
  if __name__ == "__main__":
745
+ app.run(debug=True, host="0.0.0.0", port=7860)