phirepass-widgets 0.0.53 → 0.0.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/cjs/{index-DqslB2R4.js → index-YW0ts9D0.js} +0 -37
  2. package/dist/cjs/loader.cjs.js +2 -2
  3. package/dist/cjs/phirepass-sftp-client.cjs.entry.js +462 -83
  4. package/dist/cjs/phirepass-terminal.cjs.entry.js +3 -3
  5. package/dist/cjs/phirepass-widgets.cjs.js +2 -2
  6. package/dist/cjs/{protocol-D_0VKXs_.js → protocol-DPi8rcp9.js} +5 -1
  7. package/dist/collection/common/protocol.js +4 -0
  8. package/dist/collection/components/phirepass-sftp-client/phirepass-sftp-client.css +53 -40
  9. package/dist/collection/components/phirepass-sftp-client/phirepass-sftp-client.js +462 -81
  10. package/dist/collection/components/phirepass-terminal/phirepass-terminal.js +1 -1
  11. package/dist/components/index.js +1 -1
  12. package/dist/components/p-D7u6YzKp.js +1 -0
  13. package/dist/components/phirepass-sftp-client.js +1 -1
  14. package/dist/components/phirepass-terminal.js +2 -2
  15. package/dist/esm/{index-jdexunMF.js → index-PKnTMZa7.js} +0 -37
  16. package/dist/esm/loader.js +3 -3
  17. package/dist/esm/phirepass-sftp-client.entry.js +462 -83
  18. package/dist/esm/phirepass-terminal.entry.js +3 -3
  19. package/dist/esm/phirepass-widgets.js +3 -3
  20. package/dist/esm/{protocol-BYaVbj9C.js → protocol-D7u6YzKp.js} +4 -0
  21. package/dist/phirepass-widgets/p-21fb3b58.entry.js +1 -0
  22. package/dist/phirepass-widgets/p-D7u6YzKp.js +1 -0
  23. package/dist/phirepass-widgets/p-PKnTMZa7.js +2 -0
  24. package/dist/phirepass-widgets/{p-a6553a86.entry.js → p-fec100bd.entry.js} +2 -2
  25. package/dist/phirepass-widgets/phirepass-widgets.esm.js +1 -1
  26. package/dist/types/common/protocol.d.ts +37 -2
  27. package/dist/types/components/phirepass-sftp-client/phirepass-sftp-client.d.ts +28 -5
  28. package/package.json +1 -1
  29. package/dist/components/p-BYaVbj9C.js +0 -1
  30. package/dist/phirepass-widgets/p-BYaVbj9C.js +0 -1
  31. package/dist/phirepass-widgets/p-ba7d75c5.entry.js +0 -1
  32. package/dist/phirepass-widgets/p-jdexunMF.js +0 -2
@@ -8,7 +8,7 @@ import file from "./phirepass-sftp-client.file.svg";
8
8
  import go_up from "./phirepass-sftp-client.go_up.svg";
9
9
  import refresh from "./phirepass-sftp-client.refresh.svg";
10
10
  import upload from "./phirepass-sftp-client.upload.svg";
11
- import { ConnectionState, ProtocolMessageError, ProtocolMessageType } from "../../common/protocol";
11
+ import { ConnectionState, ProtocolMessageError, ProtocolMessageType, } from "../../common/protocol";
12
12
  // https://sweet-sftp-view.lovable.app/
13
13
  export class PhirepassSftpClient {
14
14
  channel;
@@ -16,9 +16,15 @@ export class PhirepassSftpClient {
16
16
  runtimeReady = false;
17
17
  connected = false;
18
18
  uploadInputEl;
19
- uploadProgressHandle;
20
- downloadProgressHandle;
21
19
  deleteLoadingTimeout;
20
+ msgId = 1;
21
+ activeUploadToken = 0;
22
+ pendingUploadStarts = new Map();
23
+ pendingUploadAcks = new Map();
24
+ pendingDownloadStarts = new Map();
25
+ activeDownloads = new Map();
26
+ activeDownloadMsgId;
27
+ pendingDelete;
22
28
  // private inputMode: InputMode = InputMode.Default;
23
29
  session_id;
24
30
  // private usernameBuffer = "";
@@ -79,10 +85,12 @@ export class PhirepassSftpClient {
79
85
  show_upload_modal = false;
80
86
  upload_progress = 0;
81
87
  upload_file_name = '';
88
+ upload_speed = '--';
82
89
  upload_finished = false;
83
90
  show_download_modal = false;
84
91
  download_progress = 0;
85
92
  download_file_name = '';
93
+ download_speed = '--';
86
94
  download_finished = false;
87
95
  show_delete_modal = false;
88
96
  delete_file_name = '';
@@ -117,30 +125,120 @@ export class PhirepassSftpClient {
117
125
  this.connected = false;
118
126
  this.domReady = false;
119
127
  this.runtimeReady = false;
120
- this.clear_upload_progress();
121
- this.clear_download_progress();
128
+ this.cancel_active_upload();
129
+ this.cancel_active_download();
130
+ this.clear_pending_operations();
122
131
  this.clear_delete_loading_timeout();
123
132
  this.close_comms();
124
133
  // this.destroy_terminal();
125
134
  }
126
- clear_upload_progress() {
127
- if (this.uploadProgressHandle !== undefined) {
128
- window.clearInterval(this.uploadProgressHandle);
129
- this.uploadProgressHandle = undefined;
130
- }
131
- }
132
- clear_download_progress() {
133
- if (this.downloadProgressHandle !== undefined) {
134
- window.clearInterval(this.downloadProgressHandle);
135
- this.downloadProgressHandle = undefined;
136
- }
137
- }
138
135
  clear_delete_loading_timeout() {
139
136
  if (this.deleteLoadingTimeout !== undefined) {
140
137
  window.clearTimeout(this.deleteLoadingTimeout);
141
138
  this.deleteLoadingTimeout = undefined;
142
139
  }
143
140
  }
141
+ next_msg_id() {
142
+ const id = this.msgId;
143
+ this.msgId += 1;
144
+ return id;
145
+ }
146
+ clear_pending_operations() {
147
+ this.pendingUploadStarts.forEach((pending) => {
148
+ window.clearTimeout(pending.timeout);
149
+ pending.reject(new Error('Upload start aborted'));
150
+ });
151
+ this.pendingUploadStarts.clear();
152
+ this.pendingUploadAcks.forEach((pending) => {
153
+ window.clearTimeout(pending.timeout);
154
+ pending.reject(new Error('Upload chunk aborted'));
155
+ });
156
+ this.pendingUploadAcks.clear();
157
+ this.pendingDownloadStarts.forEach((pending) => {
158
+ window.clearTimeout(pending.timeout);
159
+ pending.reject(new Error('Download start aborted'));
160
+ });
161
+ this.pendingDownloadStarts.clear();
162
+ this.stop_delete_polling();
163
+ this.activeDownloads.clear();
164
+ }
165
+ stop_delete_polling() {
166
+ if (this.pendingDelete?.interval !== undefined) {
167
+ window.clearInterval(this.pendingDelete.interval);
168
+ }
169
+ this.pendingDelete = undefined;
170
+ }
171
+ cancel_active_upload() {
172
+ this.activeUploadToken += 1;
173
+ this.upload_progress = 0;
174
+ this.upload_finished = false;
175
+ this.upload_speed = '--';
176
+ }
177
+ cancel_active_download() {
178
+ if (this.activeDownloadMsgId !== undefined) {
179
+ this.activeDownloads.delete(this.activeDownloadMsgId);
180
+ }
181
+ this.activeDownloadMsgId = undefined;
182
+ this.download_progress = 0;
183
+ this.download_finished = false;
184
+ this.download_speed = '--';
185
+ }
186
+ format_duration(seconds) {
187
+ if (!Number.isFinite(seconds) || seconds < 0) {
188
+ return '--';
189
+ }
190
+ if (seconds < 60) {
191
+ return `${seconds.toFixed(0)}s`;
192
+ }
193
+ const minutes = Math.floor(seconds / 60);
194
+ const remainderSeconds = Math.floor(seconds % 60);
195
+ if (minutes < 60) {
196
+ return `${minutes}m ${remainderSeconds}s`;
197
+ }
198
+ const hours = Math.floor(minutes / 60);
199
+ const remainderMinutes = minutes % 60;
200
+ return `${hours}h ${remainderMinutes}m`;
201
+ }
202
+ format_percent(value) {
203
+ const safe = Number.isFinite(value) ? value : 0;
204
+ return `${safe.toFixed(2)}%`;
205
+ }
206
+ format_transfer_rate(bytesPerSecond) {
207
+ if (!Number.isFinite(bytesPerSecond) || bytesPerSecond <= 0) {
208
+ return '--';
209
+ }
210
+ return `${this.format_size(bytesPerSecond)}/s`;
211
+ }
212
+ update_upload_progress(uploadedBytes, totalBytes, startTime) {
213
+ const progress = totalBytes > 0 ? (uploadedBytes / totalBytes) * 100 : 0;
214
+ this.upload_progress = Math.max(0, Math.min(100, progress));
215
+ if (uploadedBytes >= totalBytes) {
216
+ this.upload_finished = true;
217
+ this.upload_speed = '--';
218
+ return;
219
+ }
220
+ const elapsedSeconds = (performance.now() - startTime) / 1000;
221
+ const speed = elapsedSeconds > 0 ? uploadedBytes / elapsedSeconds : 0;
222
+ this.upload_speed = this.format_transfer_rate(speed);
223
+ const remaining = totalBytes - uploadedBytes;
224
+ const eta = speed > 0 ? remaining / speed : NaN;
225
+ this.status = `Uploading ${this.format_size(uploadedBytes)} / ${this.format_size(totalBytes)} (ETA ${this.format_duration(eta)})`;
226
+ }
227
+ update_download_progress(receivedBytes, totalBytes, startTime) {
228
+ const progress = totalBytes > 0 ? (receivedBytes / totalBytes) * 100 : 0;
229
+ this.download_progress = Math.max(0, Math.min(100, progress));
230
+ if (receivedBytes >= totalBytes) {
231
+ this.download_finished = true;
232
+ this.download_speed = '--';
233
+ return;
234
+ }
235
+ const elapsedSeconds = (Date.now() - startTime) / 1000;
236
+ const speed = elapsedSeconds > 0 ? receivedBytes / elapsedSeconds : 0;
237
+ this.download_speed = this.format_transfer_rate(speed);
238
+ const remaining = totalBytes - receivedBytes;
239
+ const eta = speed > 0 ? remaining / speed : NaN;
240
+ this.status = `Downloading ${this.format_size(receivedBytes)} / ${this.format_size(totalBytes)} (ETA ${this.format_duration(eta)})`;
241
+ }
144
242
  connect() {
145
243
  this.connected = true;
146
244
  this.channel.connect();
@@ -179,10 +277,38 @@ export class PhirepassSftpClient {
179
277
  return `${protocol}://${this.serverHost}:${this.serverPort}`;
180
278
  }
181
279
  handle_error(error) {
280
+ if (error.msg_id !== undefined) {
281
+ const pendingUploadStart = this.pendingUploadStarts.get(error.msg_id);
282
+ if (pendingUploadStart) {
283
+ window.clearTimeout(pendingUploadStart.timeout);
284
+ pendingUploadStart.reject(new Error(error.message || 'Upload start failed'));
285
+ this.pendingUploadStarts.delete(error.msg_id);
286
+ }
287
+ const pendingDownloadStart = this.pendingDownloadStarts.get(error.msg_id);
288
+ if (pendingDownloadStart) {
289
+ window.clearTimeout(pendingDownloadStart.timeout);
290
+ pendingDownloadStart.reject(new Error(error.message || 'Download start failed'));
291
+ this.pendingDownloadStarts.delete(error.msg_id);
292
+ }
293
+ if (this.activeDownloads.has(error.msg_id)) {
294
+ this.activeDownloads.delete(error.msg_id);
295
+ if (this.activeDownloadMsgId === error.msg_id) {
296
+ this.activeDownloadMsgId = undefined;
297
+ }
298
+ this.download_finished = false;
299
+ }
300
+ if (this.pendingDelete?.msgId === error.msg_id) {
301
+ this.stop_delete_polling();
302
+ this.delete_loading = false;
303
+ this.show_delete_modal = false;
304
+ this.status = 'Connected';
305
+ }
306
+ }
182
307
  switch (error.kind) {
183
308
  case ProtocolMessageError.Generic:
184
309
  case ProtocolMessageError.Authentication:
185
310
  this.error_message = error.message || 'An unknown error occurred.';
311
+ this.show_error = true;
186
312
  break;
187
313
  case ProtocolMessageError.RequiresUsername:
188
314
  this.show_login_screen_username = true;
@@ -232,8 +358,233 @@ export class PhirepassSftpClient {
232
358
  });
233
359
  this.show_content = true;
234
360
  this.show_navigation = true;
361
+ if (this.pendingDelete && web.path === this.current_dir) {
362
+ const fileStillExists = web.dir.items.some((item) => item.kind === 'File' && item.name === this.pendingDelete?.filename);
363
+ if (!fileStillExists) {
364
+ this.stop_delete_polling();
365
+ this.delete_loading = false;
366
+ this.show_delete_modal = false;
367
+ this.delete_file_name = '';
368
+ this.status = 'Connected';
369
+ }
370
+ }
235
371
  console.log('Received SFTP list items:', web);
236
372
  }
373
+ handle_upload_start_response(web) {
374
+ if (web.msg_id === undefined) {
375
+ return;
376
+ }
377
+ const pending = this.pendingUploadStarts.get(web.msg_id);
378
+ if (!pending) {
379
+ return;
380
+ }
381
+ window.clearTimeout(pending.timeout);
382
+ pending.resolve(web.response.upload_id);
383
+ this.pendingUploadStarts.delete(web.msg_id);
384
+ }
385
+ handle_upload_chunk_ack(web) {
386
+ const key = `${web.upload_id}_${web.chunk_index}`;
387
+ const pending = this.pendingUploadAcks.get(key);
388
+ if (!pending) {
389
+ return;
390
+ }
391
+ window.clearTimeout(pending.timeout);
392
+ pending.resolve();
393
+ this.pendingUploadAcks.delete(key);
394
+ }
395
+ handle_download_start_response(web) {
396
+ if (web.msg_id === undefined) {
397
+ return;
398
+ }
399
+ const pending = this.pendingDownloadStarts.get(web.msg_id);
400
+ if (!pending) {
401
+ return;
402
+ }
403
+ window.clearTimeout(pending.timeout);
404
+ pending.resolve({
405
+ download_id: web.response.download_id,
406
+ total_size: web.response.total_size,
407
+ total_chunks: web.response.total_chunks,
408
+ });
409
+ this.pendingDownloadStarts.delete(web.msg_id);
410
+ }
411
+ request_next_download_chunk(msgId) {
412
+ const download = this.activeDownloads.get(msgId);
413
+ if (!download || !this.session_id) {
414
+ return;
415
+ }
416
+ this.channel.send_sftp_download_chunk(this.nodeId, this.session_id, download.download_id, download.nextChunkToRequest, msgId);
417
+ download.nextChunkToRequest += 1;
418
+ }
419
+ finalize_download(msgId) {
420
+ const download = this.activeDownloads.get(msgId);
421
+ if (!download) {
422
+ return;
423
+ }
424
+ const sortedChunks = Array.from(download.chunks.entries())
425
+ .sort((a, b) => a[0] - b[0])
426
+ .map(([, data]) => data);
427
+ // Normalize to fresh ArrayBuffer-backed views for BlobPart compatibility.
428
+ const blobParts = sortedChunks.map((chunk) => new Uint8Array(chunk));
429
+ const blob = new Blob(blobParts, { type: 'application/octet-stream' });
430
+ const objectUrl = URL.createObjectURL(blob);
431
+ const anchor = document.createElement('a');
432
+ anchor.href = objectUrl;
433
+ anchor.download = download.filename;
434
+ document.body.appendChild(anchor);
435
+ anchor.click();
436
+ document.body.removeChild(anchor);
437
+ URL.revokeObjectURL(objectUrl);
438
+ this.activeDownloads.delete(msgId);
439
+ if (this.activeDownloadMsgId === msgId) {
440
+ this.activeDownloadMsgId = undefined;
441
+ }
442
+ this.download_finished = true;
443
+ this.status = 'Connected';
444
+ }
445
+ handle_download_chunk(web) {
446
+ if (web.msg_id === undefined) {
447
+ return;
448
+ }
449
+ const download = this.activeDownloads.get(web.msg_id);
450
+ if (!download) {
451
+ return;
452
+ }
453
+ const chunkData = new Uint8Array(web.chunk.data);
454
+ download.chunks.set(web.chunk.chunk_index, chunkData);
455
+ const receivedBytes = Array.from(download.chunks.values()).reduce((sum, data) => sum + data.length, 0);
456
+ this.update_download_progress(receivedBytes, download.total_size, download.startTime);
457
+ if (download.chunks.size >= download.total_chunks) {
458
+ this.finalize_download(web.msg_id);
459
+ return;
460
+ }
461
+ this.request_next_download_chunk(web.msg_id);
462
+ }
463
+ async start_download(item) {
464
+ if (!this.session_id) {
465
+ return;
466
+ }
467
+ this.selected_item = item;
468
+ this.download_file_name = item.name;
469
+ this.download_progress = 0;
470
+ this.download_finished = false;
471
+ this.download_speed = '--';
472
+ this.show_download_modal = true;
473
+ this.show_error = false;
474
+ const msgId = this.next_msg_id();
475
+ this.activeDownloadMsgId = msgId;
476
+ this.activeDownloads.set(msgId, {
477
+ filename: item.name,
478
+ chunks: new Map(),
479
+ total_chunks: 0,
480
+ total_size: 0,
481
+ download_id: 0,
482
+ nextChunkToRequest: 0,
483
+ startTime: Date.now(),
484
+ });
485
+ const downloadStart = new Promise((resolve, reject) => {
486
+ const timeout = window.setTimeout(() => {
487
+ this.pendingDownloadStarts.delete(msgId);
488
+ reject(new Error('Download start timeout'));
489
+ }, 10_000);
490
+ this.pendingDownloadStarts.set(msgId, {
491
+ timeout,
492
+ resolve,
493
+ reject,
494
+ });
495
+ });
496
+ try {
497
+ this.channel.send_sftp_download_start(this.nodeId, this.session_id, item.path, item.name, msgId);
498
+ const { download_id, total_chunks, total_size } = await downloadStart;
499
+ const download = this.activeDownloads.get(msgId);
500
+ if (!download) {
501
+ return;
502
+ }
503
+ download.download_id = download_id;
504
+ download.total_chunks = total_chunks;
505
+ download.total_size = total_size;
506
+ download.nextChunkToRequest = 0;
507
+ this.request_next_download_chunk(msgId);
508
+ }
509
+ catch (err) {
510
+ this.activeDownloads.delete(msgId);
511
+ if (this.activeDownloadMsgId === msgId) {
512
+ this.activeDownloadMsgId = undefined;
513
+ }
514
+ this.show_error = true;
515
+ this.error_message = err instanceof Error ? err.message : 'Failed to start download';
516
+ this.cancel_download();
517
+ }
518
+ }
519
+ async upload_file(fileToUpload) {
520
+ if (!this.session_id) {
521
+ return;
522
+ }
523
+ const uploadToken = this.activeUploadToken + 1;
524
+ this.activeUploadToken = uploadToken;
525
+ this.upload_file_name = fileToUpload.name;
526
+ this.upload_progress = 0;
527
+ this.upload_finished = false;
528
+ this.upload_speed = '--';
529
+ this.show_upload_modal = true;
530
+ this.show_error = false;
531
+ this.status = 'Uploading...';
532
+ const chunkSize = 64 * 1024;
533
+ const totalChunks = Math.max(1, Math.ceil(fileToUpload.size / chunkSize));
534
+ const msgId = this.next_msg_id();
535
+ const uploadStart = new Promise((resolve, reject) => {
536
+ const timeout = window.setTimeout(() => {
537
+ this.pendingUploadStarts.delete(msgId);
538
+ reject(new Error('Upload start timeout'));
539
+ }, 10_000);
540
+ this.pendingUploadStarts.set(msgId, {
541
+ timeout,
542
+ resolve,
543
+ reject,
544
+ });
545
+ });
546
+ try {
547
+ this.channel.send_sftp_upload_start(this.nodeId, this.session_id, fileToUpload.name, this.current_dir, totalChunks, BigInt(fileToUpload.size), msgId);
548
+ const uploadId = await uploadStart;
549
+ let uploadedBytes = 0;
550
+ const startedAt = performance.now();
551
+ for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex += 1) {
552
+ if (uploadToken !== this.activeUploadToken) {
553
+ throw new Error('Upload cancelled');
554
+ }
555
+ const start = chunkIndex * chunkSize;
556
+ const end = Math.min(start + chunkSize, fileToUpload.size);
557
+ const chunkBuffer = new Uint8Array(await fileToUpload.slice(start, end).arrayBuffer());
558
+ await new Promise((resolve, reject) => {
559
+ const key = `${uploadId}_${chunkIndex}`;
560
+ const timeout = window.setTimeout(() => {
561
+ this.pendingUploadAcks.delete(key);
562
+ reject(new Error(`Upload chunk ${chunkIndex + 1} timed out`));
563
+ }, 30_000);
564
+ this.pendingUploadAcks.set(key, {
565
+ timeout,
566
+ resolve,
567
+ reject,
568
+ });
569
+ this.channel.send_sftp_upload_chunk(this.nodeId, this.session_id, uploadId, chunkIndex, chunkBuffer.length, chunkBuffer, null);
570
+ });
571
+ uploadedBytes += chunkBuffer.length;
572
+ this.update_upload_progress(uploadedBytes, fileToUpload.size, startedAt);
573
+ }
574
+ this.upload_progress = 100;
575
+ this.upload_finished = true;
576
+ this.status = 'Connected';
577
+ this.refresh_directory();
578
+ }
579
+ catch (err) {
580
+ if (err.message !== 'Upload cancelled') {
581
+ this.show_error = true;
582
+ this.error_message = err instanceof Error ? err.message : 'Upload failed';
583
+ }
584
+ this.upload_finished = false;
585
+ this.status = 'Connected';
586
+ }
587
+ }
237
588
  handle_tunnel_data(web) {
238
589
  console.log('received tunnel data:', web);
239
590
  }
@@ -289,12 +640,30 @@ export class PhirepassSftpClient {
289
640
  case ProtocolMessageType.SFTPListItems:
290
641
  this.handle_sftp_list_items(web);
291
642
  break;
643
+ case ProtocolMessageType.SFTPUploadStartResponse:
644
+ this.handle_upload_start_response(web);
645
+ break;
646
+ case ProtocolMessageType.SFTPUploadChunkAck:
647
+ this.handle_upload_chunk_ack(web);
648
+ break;
649
+ case ProtocolMessageType.SFTPDownloadStartResponse:
650
+ this.handle_download_start_response(web);
651
+ break;
652
+ case ProtocolMessageType.SFTPDownloadChunk:
653
+ this.handle_download_chunk(web);
654
+ break;
292
655
  default:
293
656
  console.warn('Unhandled protocol message type:', web);
294
657
  }
295
658
  });
296
659
  }
297
660
  close_comms() {
661
+ this.cancel_active_upload();
662
+ this.cancel_active_download();
663
+ this.clear_pending_operations();
664
+ if (!this.channel) {
665
+ return;
666
+ }
298
667
  this.channel.stop_heartbeat();
299
668
  this.channel.disconnect();
300
669
  }
@@ -340,26 +709,25 @@ export class PhirepassSftpClient {
340
709
  this.show_login_screen_username = false;
341
710
  this.show_login_screen_password = false;
342
711
  this.show_login_screen = false;
712
+ this.show_upload_modal = false;
713
+ this.show_download_modal = false;
714
+ this.show_delete_modal = false;
715
+ this.upload_progress = 0;
716
+ this.download_progress = 0;
717
+ this.upload_finished = false;
718
+ this.download_finished = false;
719
+ this.delete_loading = false;
720
+ this.delete_file_name = '';
343
721
  this.version = '';
344
722
  this.status = 'Disconnected';
345
723
  }
346
724
  on_file_row_action(item, event) {
347
725
  event.preventDefault();
348
726
  event.stopPropagation();
349
- this.selected_item = item;
350
- this.download_file_name = item.name;
351
- this.download_progress = 0;
352
- this.download_finished = false;
353
- this.show_download_modal = true;
354
- this.clear_download_progress();
355
- this.downloadProgressHandle = window.setInterval(() => {
356
- if (this.download_progress >= 100) {
357
- this.clear_download_progress();
358
- this.download_finished = true;
359
- return;
360
- }
361
- this.download_progress = Math.min(100, this.download_progress + 5);
362
- }, 180);
727
+ if (item.kind !== 'File') {
728
+ return;
729
+ }
730
+ void this.start_download(item);
363
731
  }
364
732
  on_file_delete_action(item, event) {
365
733
  event.preventDefault();
@@ -376,25 +744,48 @@ export class PhirepassSftpClient {
376
744
  this.show_delete_modal = false;
377
745
  this.delete_file_name = '';
378
746
  this.delete_loading = false;
747
+ this.refresh_directory();
379
748
  }
380
749
  confirm_delete() {
381
750
  if (this.delete_loading) {
382
751
  return;
383
752
  }
384
- this.delete_loading = true;
385
- const deletingFileName = this.delete_file_name;
386
- this.clear_delete_loading_timeout();
387
- this.deleteLoadingTimeout = window.setTimeout(() => {
753
+ if (!this.session_id || !this.selected_item) {
388
754
  this.show_delete_modal = false;
389
- this.delete_loading = false;
390
- this.show_error = true;
391
- this.error_message = `Delete for "${deletingFileName}" is not available yet.`;
392
- window.setTimeout(() => {
393
- this.show_error = false;
394
- }, 2_000);
395
- this.delete_file_name = '';
396
- this.deleteLoadingTimeout = undefined;
397
- }, 1_100);
755
+ return;
756
+ }
757
+ const fileToDelete = this.selected_item.name;
758
+ this.delete_loading = true;
759
+ this.status = 'Deleting...';
760
+ this.show_error = false;
761
+ this.stop_delete_polling();
762
+ const msgId = this.next_msg_id();
763
+ this.pendingDelete = {
764
+ filename: fileToDelete,
765
+ msgId,
766
+ startedAt: Date.now(),
767
+ };
768
+ const pollOnce = () => {
769
+ if (!this.pendingDelete) {
770
+ return;
771
+ }
772
+ const elapsed = Date.now() - this.pendingDelete.startedAt;
773
+ if (elapsed >= 30_000) {
774
+ this.stop_delete_polling();
775
+ this.delete_loading = false;
776
+ this.show_delete_modal = false;
777
+ this.show_error = true;
778
+ this.error_message = `Delete timed out for "${fileToDelete}".`;
779
+ this.status = 'Connected';
780
+ return;
781
+ }
782
+ if (this.session_id) {
783
+ this.channel.send_sftp_list_data(this.nodeId, this.session_id, this.current_dir);
784
+ }
785
+ };
786
+ pollOnce();
787
+ this.pendingDelete.interval = window.setInterval(pollOnce, 2_500);
788
+ this.channel.send_sftp_delete(this.nodeId, this.session_id, this.current_dir, fileToDelete, msgId);
398
789
  }
399
790
  open_upload_picker() {
400
791
  this.uploadInputEl?.click();
@@ -405,34 +796,24 @@ export class PhirepassSftpClient {
405
796
  if (!selectedFile) {
406
797
  return;
407
798
  }
408
- this.upload_file_name = selectedFile.name;
409
- this.upload_progress = 0;
410
- this.upload_finished = false;
411
- this.show_upload_modal = true;
412
- this.clear_upload_progress();
413
- this.uploadProgressHandle = window.setInterval(() => {
414
- if (this.upload_progress >= 100) {
415
- this.clear_upload_progress();
416
- this.upload_finished = true;
417
- return;
418
- }
419
- this.upload_progress = Math.min(100, this.upload_progress + 5);
420
- }, 180);
799
+ void this.upload_file(selectedFile);
421
800
  input.value = '';
422
801
  }
423
802
  cancel_upload() {
424
- this.clear_upload_progress();
803
+ this.cancel_active_upload();
425
804
  this.show_upload_modal = false;
426
- this.upload_progress = 0;
427
805
  this.upload_file_name = '';
428
- this.upload_finished = false;
806
+ this.upload_speed = '--';
807
+ this.status = 'Connected';
808
+ this.refresh_directory();
429
809
  }
430
810
  cancel_download() {
431
- this.clear_download_progress();
811
+ this.cancel_active_download();
432
812
  this.show_download_modal = false;
433
- this.download_progress = 0;
434
813
  this.download_file_name = '';
435
- this.download_finished = false;
814
+ this.download_speed = '--';
815
+ this.status = 'Connected';
816
+ this.refresh_directory();
436
817
  }
437
818
  is_selected(item) {
438
819
  if (!this.selected_item) {
@@ -460,9 +841,6 @@ export class PhirepassSftpClient {
460
841
  this.selected_item = null;
461
842
  this.channel.send_sftp_list_data(this.nodeId, this.session_id, path);
462
843
  }
463
- get_full_path(item) {
464
- return [item.path, item.name].join('/');
465
- }
466
844
  format_size(size) {
467
845
  if (size === undefined || Number.isNaN(size)) {
468
846
  return '-';
@@ -539,18 +917,18 @@ export class PhirepassSftpClient {
539
917
  return `${typeChar}${chars.join('')}`;
540
918
  }
541
919
  render() {
542
- return (h(Host, { key: 'd6835d8f10772d163d135f7c03a8bfd6ea9dc9e5', class: {
920
+ return (h(Host, { key: '4a0c122426a92a92e3a7121d8adecbefe5eaf852', class: {
543
921
  'default': !this.max,
544
922
  'max': this.max,
545
- } }, h("section", { key: '1b8c7c297fe0c13fb6374179960d220bfa264ce1', class: "listing" }, !this.hideHeader &&
546
- h("header", { key: '2aca7b108ed314344a9adab45c41b155ad243006' }, h("section", { key: 'e74cf72a21a03f706b40badcc453a6f00f00b54f', class: "title" }, h("img", { key: 'f3f9799352fd0f24a30c32fbf776fac55297d115', src: svg, alt: "SFTP Client" }), h("div", { key: '6094df7cf6cd9e00d02f88aec504f1f1f8aa0117', class: "text" }, h("div", { key: '661f83eedde6b10eb779cf5ba51eda8de86a6235', class: "name" }, this.name), h("div", { key: '43f1e5268dcef0ce029e0037865444337da720db', class: "description" }, this.description))), h("section", { key: '1f39c200d7e429c1fe2ba00f271524cbf4694c1b', class: "actions" }, h("div", { key: 'c09d3bafff294a24c84cb98041ccc0914024aebf', class: "action", onClick: () => this.toggle_max() }, h("img", { key: '15700c04d1aac6c5552f2cbbd13909bdac28ebc4', src: max, alt: "Maximize" })))), h("main", { key: '43d58b425080731024cb0987f99750b4d106d929' }, this.show_navigation && h("nav", { key: 'fe3020afb24408ae00d8feb03f71911736a4e8fd', class: "navigation" }, h("div", { key: '92f394c1b1f867e365c713e587ee3bf03677ebdd', class: "breadcrumbs" }, this.breadcrumbs.map((crumb, index, breadcrumbs) => (h(h.Fragment, null, h("span", { key: index, onClick: () => this.list_breadcrumb(crumb.path), class: "breadcrumb" }, crumb.label), index < breadcrumbs.length - 1 && h("img", { class: "arrow", src: chevron }))))), h("section", { key: '0a33173ce03dbef85a09c0e6bddea20e8defd4cc', class: "actions", "aria-label": "SFTP actions" }, h("button", { key: '08f27eacad2be8c3b26ab911ab7c8a467d050783', type: "button", class: "action", onClick: () => this.go_to_parent_directory(), title: "Go to parent directory", "aria-label": "Go to parent directory" }, h("img", { key: '20f41d4e2d4ed6bfa0c7cd45140eeebc7d151b29', src: go_up, alt: "Go up" })), h("button", { key: 'cdc64bf9c863337f1d88923a791aa734e2c6ecfb', type: "button", class: "action", onClick: () => this.refresh_directory(), title: "Refresh", "aria-label": "Refresh" }, h("img", { key: 'c7b8503b466a3f319bc63ddbfc1893addb52d731', src: refresh, alt: "Refresh" })), h("button", { key: 'a27273c0860ebea231217f49639345185414cd6a', type: "button", class: "action", onClick: () => this.open_upload_picker(), title: "Upload", "aria-label": "Upload" }, h("img", { key: '56c4feb6be73b663df107c342c974689a1922427', src: upload, alt: "Upload" })), h("button", { key: 'e052aea68588e6711b6fe9ac39b057e8a1ca8f59', type: "button", class: "action disconnect", onClick: () => this.disconnect_session(), title: "Disconnect", "aria-label": "Disconnect" }, "DISCONNECT"))), h("input", { key: 'bac3a9f443a3175dcf9e436ea2754aa46c237e38', type: "file", ref: (el) => this.uploadInputEl = el, onChange: (event) => this.on_upload_selected(event), style: { display: 'none' } }), this.show_content && h("div", { key: 'f8b5a1b307b290bc4ac1c5dbfafea13d942981ea', class: "content" }, h("table", { key: 'acd4c91ff1fad061b847c6c10a6bd5228540bbdd' }, h("thead", { key: 'a416b35d7ac3e1809514f90b20cd52180a98fd78' }, h("tr", { key: '1e42ebd055d9821455205981cd6b86c176d818c6' }, h("th", { key: '785f06a6ff02eca8de9fef36cbb085c2a03b16e9' }, "Name"), h("th", { key: 'f860e66146bd0a5a85516a7c98d901ae4ccf672e' }, "Size"), h("th", { key: '21e2d9c1b923af76afc9aff707219d8fb570c661' }, "Permissions"), h("th", { key: '4d8f3d7909fa2ad3cb52e0c62426d86c399015d8' }, "Owner"), h("th", { key: '5a628872d532a62c49923139d762bb1500163d2f' }, "Modified"), h("th", { key: 'bd635bbd123d003a87460b8958dafdeed887d6f2', class: "action-col", "aria-label": "Actions" }))), h("tbody", { key: '762ab21361490e0455942ea42e5b9feccb9ea6d7' }, this.listing.map((item, index) => (h("tr", { key: index, class: {
923
+ } }, h("section", { key: 'f408645cf18f728c09e7af174f5eb30b5d37ca3b', class: "listing" }, !this.hideHeader &&
924
+ h("header", { key: '42fbffd05631ab758d4a9647b4bda41cb5b77b0a' }, h("section", { key: '9b67a0e72219f287d8458bb8ce271789ec653dc7', class: "title" }, h("img", { key: '0eb4b3588b0631d4e93827f74111650d3bed26c4', src: svg, alt: "SFTP Client" }), h("div", { key: '30945973ee86494b0b2c90ead1c178a1f48086fa', class: "text" }, h("div", { key: '716418d9af825dfd974ef96f7c9687f8e08cc4e7', class: "name" }, this.name), h("div", { key: 'eec1114d46f86c0f777b75a98dc21af1fe473f96', class: "description" }, this.description))), h("section", { key: '05aa545ba36f40adabe6d920514a00b50219744e', class: "actions" }, h("div", { key: '036d227279849911bd353ba879600d46d4cd1ab2', class: "action", onClick: () => this.toggle_max() }, h("img", { key: '3087b9252229f3dc401ea984c7e4576dc0bb74cb', src: max, alt: "Maximize" })))), h("main", { key: '5f2a69fb252b3df05742f8b74a15a0c9028f372f' }, this.show_navigation && h("nav", { key: '10612928faf505db3a3a2a00df51425540ea97ec', class: "navigation" }, h("div", { key: 'fdaa4662f45fad1b2fa31059f67df9344f7e7428', class: "breadcrumbs" }, this.breadcrumbs.map((crumb, index, breadcrumbs) => (h("span", { key: index, class: "breadcrumb-container" }, h("span", { key: index, onClick: () => this.list_breadcrumb(crumb.path), class: "breadcrumb" }, crumb.label), index < breadcrumbs.length - 1 && h("img", { class: "arrow", src: chevron }))))), h("section", { key: '71356c63017f5c74eadeabec428a3b7b3809ade2', class: "actions", "aria-label": "SFTP actions" }, h("button", { key: 'eaf88ec4362509efd594f29a38531a8b158ece9f', type: "button", class: "action", onClick: () => this.go_to_parent_directory(), title: "Go to parent directory", "aria-label": "Go to parent directory" }, h("img", { key: 'b1d5c6a632e22d64f829dbbd7139b859006dbd7a', src: go_up, alt: "Go up" })), h("button", { key: '94abac9e1a6984872c4c85e66aa448d11621e980', type: "button", class: "action", onClick: () => this.refresh_directory(), title: "Refresh", "aria-label": "Refresh" }, h("img", { key: 'd953e97de3411cd1a526c6e1fe042db0ddc673e5', src: refresh, alt: "Refresh" })), h("button", { key: '03598d60ae17c54493b1572f53c267b765008766', type: "button", class: "action", onClick: () => this.open_upload_picker(), title: "Upload", "aria-label": "Upload" }, h("img", { key: 'c39ee38d8e584c35155035aeb37403d4a89372ba', src: upload, alt: "Upload" })), h("button", { key: '7e017137f2588bb7a231ef76e874b85e3c1c112d', type: "button", class: "action disconnect", onClick: () => this.disconnect_session(), title: "Disconnect", "aria-label": "Disconnect" }, "DISCONNECT"))), h("input", { key: '2956f9ad2124f813d848ace2206312ee81a4e145', type: "file", ref: (el) => this.uploadInputEl = el, onChange: (event) => this.on_upload_selected(event), style: { display: 'none' } }), this.show_content && h("div", { key: '451d0758c8b71ac9c720f4ad07c5fa3a7011b43e', class: "content" }, h("table", { key: '27a87bfdc1e357a15ff578cae12f41fb768be576' }, h("thead", { key: 'c8161cd9ff7e2bdf0579b3aa9797bd2366b106e8' }, h("tr", { key: '23dc88a5b43a77bb1ab031f72e01996c0e16d4de' }, h("th", { key: '460827f1703d2c7165df2e07b2d873d71a842c6f' }, "Name"), h("th", { key: '6960f731ba9b30012d1772911899714ad6ea5e10' }, "Size"), h("th", { key: '71ae2e8bd6c7ba2110470b651aed9cda0ef2b88f' }, "Permissions"), h("th", { key: '6e8e87a11812570c5bbfebadf4793eafe15e6237' }, "Owner"), h("th", { key: 'b0001ae77eb5c4958434558c715aa5b23e336da2' }, "Modified"), h("th", { key: '3eb862c25e9734c1a2c99fe07ea804761d9578e9', class: "action-col", "aria-label": "Actions" }))), h("tbody", { key: 'b80916d2917e97c9e4058195c5d5d20e98702d60' }, this.listing.map((item, index) => (h("tr", { key: index, class: {
547
925
  'selected': this.is_selected(item),
548
926
  }, onClick: () => this.list_directory(item) }, h("td", null, item.kind === 'Folder' ? h("img", { class: "kind", src: folder, alt: "Folder" }) : h("img", { class: "kind", src: file, alt: "File" }), h("span", { class: `name ${item.kind.toLowerCase()}` }, item.name)), h("td", null, this.format_size(item.attributes.size)), h("td", null, this.format_permissions(item.attributes.permissions, item.kind)), h("td", null, item.attributes.user ?? '-'), h("td", null, new Date(item.attributes.modified * 1000).toLocaleString()), h("td", { class: "action-col" }, item.kind === 'File' &&
549
- h("div", { class: "file-actions" }, h("button", { type: "button", class: "file-action", onClick: (event) => this.on_file_row_action(item, event), title: "Download", "aria-label": "Download" }, h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true" }, h("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }), h("polyline", { points: "7 10 12 15 17 10" }), h("line", { x1: "12", x2: "12", y1: "15", y2: "3" }))), h("button", { type: "button", class: "file-action delete", onClick: (event) => this.on_file_delete_action(item, event), title: "Delete", "aria-label": "Delete" }, h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true" }, h("polyline", { points: "3 6 5 6 21 6" }), h("path", { d: "M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" }), h("path", { d: "M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" }), h("line", { x1: "10", x2: "10", y1: "11", y2: "17" }), h("line", { x1: "14", x2: "14", y1: "11", y2: "17" }))))))))))), this.show_loader && h("div", { key: '8889681c30251969a4b0c7bdf9dee9716bae3a7e', class: "loader" }, "Loading..."), this.show_error && h("div", { key: 'e64a52b759c799db6e3b1fe50a6bf37a5c98054c', class: "error" }, this.error_message)), h("footer", { key: '5a839546c86673fefb234bdaef9bd365779411df' }, h("section", { key: 'cbff2897ce0c934cd65afee0ca26214792b8e2dd', class: "status" }, h("span", { key: 'd948c7ffd13a54551f3ef2b4994b6d4c332d7621' }, this.status), this.selected_item && h("span", { key: '9f42bdd345c3fbf7e81fb74e8ddf1950d5d29a46', class: "selected-item" }, this.get_full_path(this.selected_item))), h("section", { key: 'c4dc849a8fd5c3a81977e4bdca9bf2b4f525f8d9', class: "version" }, this.version ? `Version: ${this.version}` : ''))), this.show_login_screen &&
550
- h("section", { key: '98180dd1473469824fcc7b2e88cbf62012c026d3', class: {
927
+ h("div", { class: "file-actions" }, h("button", { type: "button", class: "file-action", onClick: (event) => this.on_file_row_action(item, event), title: "Download", "aria-label": "Download" }, h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true" }, h("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }), h("polyline", { points: "7 10 12 15 17 10" }), h("line", { x1: "12", x2: "12", y1: "15", y2: "3" }))), h("button", { type: "button", class: "file-action delete", onClick: (event) => this.on_file_delete_action(item, event), title: "Delete", "aria-label": "Delete" }, h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true" }, h("polyline", { points: "3 6 5 6 21 6" }), h("path", { d: "M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" }), h("path", { d: "M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" }), h("line", { x1: "10", x2: "10", y1: "11", y2: "17" }), h("line", { x1: "14", x2: "14", y1: "11", y2: "17" }))))))))))), this.show_loader && h("div", { key: '6698d4f3a2f60efa264ac5d4a3e35da14c1b47d5', class: "loader" }, "Loading..."), this.show_error && h("div", { key: '411d74aa5574e444eb4f08d7ecec37fbe9c54a37', class: "error" }, this.error_message)), h("footer", { key: 'a58fb0f412a403f930cbdca15269678cf92037f9' }, h("section", { key: '8a989e00a03d5d3290599131989ddef872444e9b', class: "version" }, this.version ? `Version: ${this.version}` : ''))), this.show_login_screen &&
928
+ h("section", { key: '8f070e03986f073de9ca90ea2f46cd0d485a0a8c', class: {
551
929
  'creds': true,
552
930
  'blurred': this.show_login_screen,
553
- } }, h("form", { key: '8ee6af5523225fd1f1ab93bc3fb268e73ec8d7e6', class: "auth", onSubmit: (event) => {
931
+ } }, h("form", { key: 'f8d6b2455ecf15e6ef19f1058c86100d7fe36357', class: "auth", onSubmit: (event) => {
554
932
  const formData = new FormData(event.target);
555
933
  let username = undefined;
556
934
  if (this.show_login_screen_username) {
@@ -567,27 +945,28 @@ export class PhirepassSftpClient {
567
945
  this.show_loader = true;
568
946
  event.stopPropagation();
569
947
  event.preventDefault();
570
- } }, h("div", { key: '50fd8c259b5df82b304f2ed08b16050d3cb9bba0', class: "title" }, "SFTP Connection"), this.show_login_screen_username &&
571
- h("div", { key: '9bcbc10707682d879c14419a0bd02f982af985e8' }, h("label", { key: 'c60920335472e50331db739e63cd3615c5cb24b4', htmlFor: "username" }, "Username"), h("input", { key: '9a56e8f648049f2f9216f5886da962cdda273781', autocorrect: "off", autocapitalize: "none", autoComplete: "off", id: "username", name: "username", type: "text", placeholder: "" })), this.show_login_screen_password &&
572
- h("div", { key: '1118e66371c80e7da7100469bc7820e315ffdc7e' }, h("label", { key: 'ff1a7e258f720f380ebe90f8d878713552be1eb8', htmlFor: "password" }, "Password"), h("input", { key: '14265511ce70214639d6c86d2dbec827cf35e3d0', autocorrect: "off", autocapitalize: "none", autoComplete: "off", id: "password", name: "password", type: "password", placeholder: "" })), h("div", { key: 'b850f2be07d80f4d151878c73545e644f05ecd34' }, h("button", { key: '9291d47362ed567b910276636176aa52d106c388', type: "submit" }, "Connect")))), this.show_upload_modal &&
573
- h("section", { key: 'b7f9bd6374bfb8bcf04d8563e4ef48f8a077d582', class: {
948
+ } }, h("div", { key: '870343a9f67b01ce620c88ab3af2d823e9c109da', class: "title" }, "SFTP Connection"), this.show_login_screen_username &&
949
+ h("div", { key: 'd32b19a3d0e89196247358ca846fd6108fe15128' }, h("label", { key: 'c60053d805db2357780bb986c71f6b839390ca20', htmlFor: "username" }, "Username"), h("input", { key: '73c368508cc63db7ff5d7c7ca1d3f12a6e409926', autocorrect: "off", autocapitalize: "none", autoComplete: "off", id: "username", name: "username", type: "text", placeholder: "" })), this.show_login_screen_password &&
950
+ h("div", { key: 'c5a91b121c0309e577ca63a5392dce7b982e6429' }, h("label", { key: 'b3adfc6963d418f9a7748092a661f87a77949f1d', htmlFor: "password" }, "Password"), h("input", { key: '0e6855eb8e7ef22e6981e5c7df429d4bc81e8d67', autocorrect: "off", autocapitalize: "none", autoComplete: "off", id: "password", name: "password", type: "password", placeholder: "" })), h("div", { key: '05377a2061e222afada603838feaac68b913be1b' }, h("button", { key: '4d28615b22f083ad8b7430a3babe2a5dbc9c4f90', type: "submit" }, "Connect")))), this.show_upload_modal &&
951
+ h("section", { key: '7979af0ec1f92bf78a94d5e1be2a7b7a79b780be', class: {
574
952
  'upload-modal': true,
575
953
  'visible': this.show_upload_modal,
576
- } }, h("div", { key: '44f4f5f989376be19de17ef9ceee57d421f74a87', class: "upload-dialog", role: "dialog", "aria-modal": "true", "aria-label": "Upload progress" }, h("div", { key: '04337b2bdb2248c357d75479c16a9b7dee480150', class: "title" }, "Uploading File"), h("div", { key: '2bada0f7123a8111ace072757857fc5e9d1e45df', class: "file-name", title: this.upload_file_name }, this.upload_file_name), h("div", { key: '7e5c8115e49d339421af4c481bc1381b45b14906', class: "progress-track", role: "progressbar", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": this.upload_progress }, h("div", { key: 'fe2d2659b4e09cfc07604761800f0155f6f4da7c', class: "progress-fill", style: { width: `${this.upload_progress}%` } })), h("div", { key: 'a50903b50a4d57fc7aa9c62f2b08eee94e90c6c6', class: "progress-value" }, this.upload_progress, "%"), h("button", { key: 'a3a0f3b6bee2348d77affcef6a35faba7bdf7b0a', type: "button", class: {
954
+ } }, h("div", { key: '55132c16758312e55fd7bb5bb32dfcd557500838', class: "upload-dialog", role: "dialog", "aria-modal": "true", "aria-label": "Upload progress" }, h("div", { key: '7b808945f2123b5c1e8ed300cba85f3325be643e', class: "title" }, "Uploading File"), h("div", { key: '0c7065ccff128f524eee7da248eee97f78819bc0', class: "file-name", title: this.upload_file_name }, this.upload_file_name), h("div", { key: '848d3526b2cff55c6cc6eab0a4cb536264f14d80', class: "progress-track", role: "progressbar", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": this.upload_progress }, h("div", { key: 'a3f8d7c47971866243a5138ae1e5b84fe50fa008', class: "progress-fill", style: { width: `${this.upload_progress}%` } })), h("div", { key: '6675b28ce14fa0f3d84cbfcc602409f6ba650a13', class: "progress-meta" }, h("div", { key: '5205d72ab81c7bfc906fcfe827b05933440161b8', class: "progress-speed" }, this.upload_speed), h("div", { key: 'c3f59de705696ac1355629ac0397e1a9baf56a0a', class: "progress-value" }, this.format_percent(this.upload_progress))), h("button", { key: '06d9bc2f61d7f83361ad97b7bb23f2f45b11ec27', type: "button", class: {
577
955
  'cancel': true,
578
956
  'finished': this.upload_finished,
579
957
  }, onClick: () => this.cancel_upload() }, this.upload_finished ? 'Close' : 'Cancel'))), this.show_download_modal &&
580
- h("section", { key: '268e3fc9a4bd26e0b167ac2f7ec98d85755e06d1', class: {
958
+ h("section", { key: '7217722972f463d2893bd44ac5a4899c8fe545a7', class: {
581
959
  'download-modal': true,
582
960
  'visible': this.show_download_modal,
583
- } }, h("div", { key: '94c7cee66d0d49f1efe1489e93a84b2f66d2566f', class: "download-dialog", role: "dialog", "aria-modal": "true", "aria-label": "Download progress" }, h("div", { key: '392c81df57b514dd146c24aaf47fc7232949e8e1', class: "title" }, "Downloading File"), h("div", { key: 'ebfe8992e27741c18e3d2539fd621db660fb1552', class: "file-name", title: this.download_file_name }, this.download_file_name), h("div", { key: '1424a4bde99d5ee57946210e5089123efbaa5812', class: "progress-track", role: "progressbar", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": this.download_progress }, h("div", { key: '6011f979cf8a865a3e8116f16c8e04b3c12b2ebc', class: "progress-fill", style: { width: `${this.download_progress}%` } })), h("div", { key: '8f38212d4d5b55ec24868e14db0d3fa76e3c0a4d', class: "progress-value" }, this.download_progress, "%"), h("button", { key: '2d6090c87206ca4dbbc0d6f0e88f772153f19260', type: "button", class: {
961
+ } }, h("div", { key: '493dddfad5d25411a3421641ef930eab561347b4', class: "download-dialog", role: "dialog", "aria-modal": "true", "aria-label": "Download progress" }, h("div", { key: '2115492e8360a19edf260c7c603bc44e736cccec', class: "title" }, "Downloading File"), h("div", { key: '5582b3ea56c9a184ff1b6de4ca89b93f07461847', class: "file-name", title: this.download_file_name }, this.download_file_name), h("div", { key: '90df12daa47066e7a212edeec5760cef2abc52c6', class: "progress-track", role: "progressbar", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": this.download_progress }, h("div", { key: '2c3c6ef5b7091ea010c4fc9037d05db5845f4e23', class: "progress-fill", style: { width: `${this.download_progress}%` } })), h("div", { key: '9d963e08afe1e073f19fc9195cd66f67a874204f', class: "progress-meta" }, h("div", { key: '96508c5a2a95ce9889d947bc4786403785b15c75', class: "progress-speed" }, this.download_speed), h("div", { key: '3f0cab67e511efda1cf79111705525b9838eb410', class: "progress-value" }, this.format_percent(this.download_progress))), h("button", { key: 'cac13e01231ba4c1713b85e7b7ac661b1ec6f6e5', type: "button", class: {
584
962
  'cancel': true,
585
963
  'finished': this.download_finished,
586
964
  }, onClick: () => this.cancel_download() }, this.download_finished ? 'Close' : 'Cancel'))), this.show_delete_modal &&
587
- h("section", { key: '8bfc94b6a829c3997aebcec8368d4e99a3321375', class: {
965
+ h("section", { key: 'e66cc06cb588d7842a4557041a5fa617b2672b0b', class: {
588
966
  'delete-modal': true,
589
967
  'visible': this.show_delete_modal,
590
- } }, h("div", { key: '9fb7a902510330c9366370fd66b48de54339b154', class: "delete-dialog", role: "dialog", "aria-modal": "true", "aria-label": "Delete confirmation" }, h("div", { key: '0835d56456b5a5de31f44528d4e4ece436361280', class: "title" }, "Delete File"), h("div", { key: '8fa20e7ec0ece30993cf07415d6288d855fa9afe', class: "message" }, this.delete_loading ? 'Deleting file...' : 'Are you sure you want to delete this file?'), h("div", { key: '47e29802f717c5c1613cbbd86de7c493c3f4267f', class: "file-name", title: this.delete_file_name }, this.delete_file_name), this.delete_loading && h("div", { key: 'd63f8e662d7a44ab2ab3dba7988996ac8b80bb9e', class: "delete-loading-bar", "aria-hidden": "true" }), h("div", { key: '1b498931d6fed515130589a837a9a0a017718c9a', class: "modal-actions" }, h("button", { key: '641f8ce95c205b170da0a498213c263bc7c9a410', type: "button", class: "btn secondary", onClick: () => this.cancel_delete(), disabled: this.delete_loading }, "Cancel"), h("button", { key: '54685d2b101a8af75912edb72109e3c4c23b0789', type: "button", class: "btn destructive", onClick: () => this.confirm_delete(), disabled: this.delete_loading }, this.delete_loading ? 'Deleting...' : 'Delete'))))));
968
+ } }, h("div", { key: 'ac2298da9dde4e4043f29a8b2b137fe7f2e0f5dc', class: "delete-dialog", role: "dialog", "aria-modal": "true", "aria-label": "Delete confirmation" }, h("div", { key: 'ac53826a41c03e3cb94296c30e622f453bca0f40', class: "title" }, "Delete File"), h("div", { key: '9724627c77b3242f25e7d73b18bae820833cdb9f', class: "message" }, this.delete_loading ? 'Deleting file...' : 'Are you sure you want to delete this file?'), h("div", { key: '408ece6999a858d60baf7bd34b3133d07aba8806', class: "file-name", title: this.delete_file_name }, this.delete_file_name), this.delete_loading &&
969
+ h("div", { key: '6f157f39e1097a355c139af9ea0e35ee76a04117', class: "delete-loader", "aria-hidden": "true" }, h("span", { key: 'bd85fe1e6dfc09febebcd1c31fc99629d1f4429d', class: "spinner" })), h("div", { key: 'ad4f19824730b61e82e8ad205d5ea0cc2e715a7f', class: "modal-actions" }, h("button", { key: '9d95213a54aefc8064769796f235b4533fc5aa08', type: "button", class: "btn secondary", onClick: () => this.cancel_delete(), disabled: this.delete_loading }, "Cancel"), h("button", { key: 'd423c0537eb372ed491387c45e6a5c5f5a975259', type: "button", class: "btn destructive", onClick: () => this.confirm_delete(), disabled: this.delete_loading }, this.delete_loading ? 'Deleting...' : 'Delete'))))));
591
970
  }
592
971
  static get is() { return "phirepass-sftp-client"; }
593
972
  static get encapsulation() { return "shadow"; }
@@ -822,10 +1201,12 @@ export class PhirepassSftpClient {
822
1201
  "show_upload_modal": {},
823
1202
  "upload_progress": {},
824
1203
  "upload_file_name": {},
1204
+ "upload_speed": {},
825
1205
  "upload_finished": {},
826
1206
  "show_download_modal": {},
827
1207
  "download_progress": {},
828
1208
  "download_file_name": {},
1209
+ "download_speed": {},
829
1210
  "download_finished": {},
830
1211
  "show_delete_modal": {},
831
1212
  "delete_file_name": {},