@putkoff/abstract-securefiles 0.0.51 → 0.0.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/MyComputers.d.ts +7 -0
- package/dist/api.d.ts +12 -1
- package/dist/index.cjs +74 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +74 -10
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +12 -1
- package/package.json +1 -1
- package/src/MyComputers.tsx +141 -0
- package/src/SecureFiles.tsx +24 -5
- package/src/api.ts +8 -2
- package/src/index.ts +1 -1
- package/src/styles.css +38 -0
- package/src/types.ts +13 -1
package/dist/index.js
CHANGED
|
@@ -35,6 +35,9 @@ function createApi(apiBase = DEFAULT_API_BASE) {
|
|
|
35
35
|
}
|
|
36
36
|
return {
|
|
37
37
|
list: () => json("/userInfo"),
|
|
38
|
+
devices: () => json("/devices"),
|
|
39
|
+
pairDevice: () => json("/devices/pair", { method: "POST", body: "{}" }),
|
|
40
|
+
removeDevice: (id) => json(`/devices/${id}`, { method: "DELETE" }),
|
|
38
41
|
roots: () => json("/admin/roots"),
|
|
39
42
|
requestRoot: (path) => json("/admin/roots", { method: "POST", body: JSON.stringify({ path }) }),
|
|
40
43
|
confirmRoot: (nonce, signature, label) => json("/admin/roots/confirm", {
|
|
@@ -327,7 +330,7 @@ function formatSize(bytes) {
|
|
|
327
330
|
}
|
|
328
331
|
return `${n.toFixed(i ? 1 : 0)} ${units[i]}`;
|
|
329
332
|
}
|
|
330
|
-
const errText = (err) => (err instanceof Error ? err.message : String(err));
|
|
333
|
+
const errText$1 = (err) => (err instanceof Error ? err.message : String(err));
|
|
331
334
|
function AddRoot({ api, onAdded }) {
|
|
332
335
|
const [path, setPath] = useState("");
|
|
333
336
|
const [label, setLabel] = useState("");
|
|
@@ -343,7 +346,7 @@ function AddRoot({ api, onAdded }) {
|
|
|
343
346
|
setSignature("");
|
|
344
347
|
}
|
|
345
348
|
catch (err) {
|
|
346
|
-
setError(errText(err));
|
|
349
|
+
setError(errText$1(err));
|
|
347
350
|
}
|
|
348
351
|
finally {
|
|
349
352
|
setBusy(false);
|
|
@@ -362,7 +365,7 @@ function AddRoot({ api, onAdded }) {
|
|
|
362
365
|
setSignature("");
|
|
363
366
|
}
|
|
364
367
|
catch (err) {
|
|
365
|
-
setError(errText(err));
|
|
368
|
+
setError(errText$1(err));
|
|
366
369
|
}
|
|
367
370
|
finally {
|
|
368
371
|
setBusy(false);
|
|
@@ -385,7 +388,7 @@ function Browser({ api, roots, folders, onLinked, }) {
|
|
|
385
388
|
setPicked(new Set());
|
|
386
389
|
}
|
|
387
390
|
catch (err) {
|
|
388
|
-
setError(errText(err));
|
|
391
|
+
setError(errText$1(err));
|
|
389
392
|
}
|
|
390
393
|
}, [api]);
|
|
391
394
|
useEffect(() => {
|
|
@@ -413,7 +416,7 @@ function Browser({ api, roots, folders, onLinked, }) {
|
|
|
413
416
|
await open(listing.path);
|
|
414
417
|
}
|
|
415
418
|
catch (err) {
|
|
416
|
-
setResult(errText(err));
|
|
419
|
+
setResult(errText$1(err));
|
|
417
420
|
}
|
|
418
421
|
finally {
|
|
419
422
|
setBusy(false);
|
|
@@ -437,7 +440,7 @@ function ServerFiles({ api, folders, onLinked, }) {
|
|
|
437
440
|
setError(null);
|
|
438
441
|
}
|
|
439
442
|
catch (err) {
|
|
440
|
-
setError(errText(err));
|
|
443
|
+
setError(errText$1(err));
|
|
441
444
|
}
|
|
442
445
|
}, [api]);
|
|
443
446
|
return (jsxs("details", { className: "sf-admin", onToggle: (e) => e.currentTarget.open && !roots && load(), children: [jsx("summary", { children: "Server files (admin)" }), jsxs("div", { className: "sf-stack", children: [jsx("p", { className: "sf-muted", children: "Link files or whole folders that already live on the server instead of uploading copies. A linked folder downloads as one uncompressed .tar with an exact size, and interrupted downloads can be resumed. Only items inside allowed directories can be linked; removing a directory immediately disables its links. Deleting a linked entry never touches the original. Available from the local network only." }), error && jsx("p", { className: "sf-error", children: error }), roots && (jsxs(Fragment, { children: [jsxs("div", { className: "sf-card", children: [jsx("h4", { children: "Allowed directories" }), roots.length === 0 ? (jsx("p", { className: "sf-muted", children: "None yet." })) : (jsx("ul", { className: "sf-roots", children: roots.map((r) => (jsxs("li", { children: [jsxs("span", { children: [jsx("code", { children: r.path }), " ", r.label && jsxs("span", { className: "sf-muted", children: ["\u2014 ", r.label] }), !r.exists && jsx("span", { className: "sf-error", children: " (missing)" })] }), jsx(ConfirmButton, { label: "Remove", onConfirm: async () => {
|
|
@@ -446,11 +449,70 @@ function ServerFiles({ api, folders, onLinked, }) {
|
|
|
446
449
|
onLinked();
|
|
447
450
|
}
|
|
448
451
|
catch (err) {
|
|
449
|
-
setError(errText(err));
|
|
452
|
+
setError(errText$1(err));
|
|
450
453
|
}
|
|
451
454
|
} })] }, r.id))) }))] }), jsx(AddRoot, { api: api, onAdded: setRoots }), jsx(Browser, { api: api, roots: roots, folders: folders, onLinked: onLinked })] }))] })] }));
|
|
452
455
|
}
|
|
453
456
|
|
|
457
|
+
const errText = (err) => (err instanceof Error ? err.message : String(err));
|
|
458
|
+
function ago(iso) {
|
|
459
|
+
if (!iso)
|
|
460
|
+
return "never";
|
|
461
|
+
const secs = Math.max(0, (Date.now() - new Date(iso).getTime()) / 1000);
|
|
462
|
+
if (secs < 90)
|
|
463
|
+
return "just now";
|
|
464
|
+
if (secs < 3600)
|
|
465
|
+
return `${Math.round(secs / 60)} min ago`;
|
|
466
|
+
if (secs < 86400)
|
|
467
|
+
return `${Math.round(secs / 3600)} h ago`;
|
|
468
|
+
return `${Math.round(secs / 86400)} d ago`;
|
|
469
|
+
}
|
|
470
|
+
function AddComputer({ api, onPaired }) {
|
|
471
|
+
const [code, setCode] = useState(null);
|
|
472
|
+
const [expiresAt, setExpiresAt] = useState(0);
|
|
473
|
+
const [, setTick] = useState(0);
|
|
474
|
+
const [error, setError] = useState(null);
|
|
475
|
+
useEffect(() => {
|
|
476
|
+
if (!code)
|
|
477
|
+
return;
|
|
478
|
+
const timer = setInterval(() => {
|
|
479
|
+
if (Date.now() > expiresAt)
|
|
480
|
+
setCode(null);
|
|
481
|
+
setTick((t) => t + 1);
|
|
482
|
+
onPaired();
|
|
483
|
+
}, 5000);
|
|
484
|
+
return () => clearInterval(timer);
|
|
485
|
+
}, [code, expiresAt, onPaired]);
|
|
486
|
+
const create = async () => {
|
|
487
|
+
setError(null);
|
|
488
|
+
try {
|
|
489
|
+
const res = await api.pairDevice();
|
|
490
|
+
setCode(res.code);
|
|
491
|
+
setExpiresAt(Date.now() + res.expires_in * 1000);
|
|
492
|
+
}
|
|
493
|
+
catch (err) {
|
|
494
|
+
setError(errText(err));
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
const minutesLeft = Math.max(1, Math.round((expiresAt - Date.now()) / 60000));
|
|
498
|
+
if (!code) {
|
|
499
|
+
return (jsxs("div", { className: "sf-row", children: [jsx("button", { type: "button", className: "sf-btn sf-btn--primary", onClick: create, children: "Add a computer" }), error && jsx("span", { className: "sf-error", children: error })] }));
|
|
500
|
+
}
|
|
501
|
+
return (jsxs("div", { className: "sf-card", children: [jsx("h4", { children: "Connect a computer" }), jsxs("ol", { className: "sf-steps", children: [jsxs("li", { children: ["On that computer, install the agent (Linux, macOS or Windows with Python 3.9+):", jsxs("div", { className: "sf-row", children: [jsx("code", { className: "sf-codeline", children: "pipx install abstract-securefiles && securefiles setup" }), jsx(CopyButton, { text: "pipx install abstract-securefiles && securefiles setup", label: "Copy" })] }), jsxs("span", { className: "sf-muted", children: ["No pipx? Use ", jsx("code", { children: "python3 -m pip install --user abstract-securefiles" }), " then ", jsx("code", { children: "securefiles setup" }), "."] })] }), jsxs("li", { children: ["In the panel that opens (", jsx("code", { children: "http://127.0.0.1:47821" }), "), enter this code:", jsxs("div", { className: "sf-row", children: [jsx("span", { className: "sf-paircode", children: code }), jsx(CopyButton, { text: code, label: "Copy" })] }), jsxs("span", { className: "sf-muted", children: ["Valid for about ", minutesLeft, " more minutes, once."] })] }), jsxs("li", { children: ["Pick the files and folders to share there. They appear here under ", jsx("code", { children: "devices/<computer>/" }), "."] })] }), jsx("div", { className: "sf-row", children: jsx("button", { type: "button", className: "sf-btn", onClick: () => setCode(null), children: "Done" }) })] }));
|
|
502
|
+
}
|
|
503
|
+
function MyComputers({ api, devices, onChange, }) {
|
|
504
|
+
const [error, setError] = useState(null);
|
|
505
|
+
return (jsxs("details", { className: "sf-admin", children: [jsxs("summary", { children: ["My computers", " ", jsxs("span", { className: "sf-muted", children: ["(", devices.filter((d) => d.online).length, " online / ", devices.length, ")"] })] }), jsxs("div", { className: "sf-stack", children: [jsx("p", { className: "sf-muted", children: "Share files and folders straight from your own computers \u2014 nothing is uploaded. Downloads stream from the computer through this site with the same links, passwords, limits and resume, while it's online." }), devices.length > 0 && (jsx("ul", { className: "sf-roots", children: devices.map((d) => (jsxs("li", { children: [jsxs("span", { children: [jsx("span", { className: `sf-dot${d.online ? " sf-dot--on" : ""}` }), " ", jsx("b", { children: d.name }), " ", jsxs("span", { className: "sf-muted", children: [d.online ? "online" : `offline · last seen ${ago(d.last_seen)}`, d.platform ? ` · ${d.platform}` : ""] })] }), jsx(ConfirmButton, { label: "Remove", onConfirm: async () => {
|
|
506
|
+
try {
|
|
507
|
+
await api.removeDevice(d.id);
|
|
508
|
+
onChange();
|
|
509
|
+
}
|
|
510
|
+
catch (err) {
|
|
511
|
+
setError(errText(err));
|
|
512
|
+
}
|
|
513
|
+
} })] }, d.id))) })), error && jsx("p", { className: "sf-error", children: error }), jsx(AddComputer, { api: api, onPaired: onChange })] })] }));
|
|
514
|
+
}
|
|
515
|
+
|
|
454
516
|
function styleInject(css, ref) {
|
|
455
517
|
if ( ref === void 0 ) ref = {};
|
|
456
518
|
var insertAt = ref.insertAt;
|
|
@@ -478,7 +540,7 @@ function styleInject(css, ref) {
|
|
|
478
540
|
}
|
|
479
541
|
}
|
|
480
542
|
|
|
481
|
-
var css_248z = ".sf-root{--sf-border:color-mix(in srgb,currentColor 22%,transparent);--sf-subtle:color-mix(in srgb,currentColor 6%,transparent);--sf-accent:#2563eb;--sf-danger:#dc2626;display:flex;flex-direction:column;font-size:14px;gap:16px;margin:0 auto;max-width:1100px;padding:16px;text-align:left}.sf-root *,.sf-root :after,.sf-root :before{box-sizing:border-box}.sf-head{align-items:baseline;display:flex;gap:12px;justify-content:space-between}.sf-head h2{font-size:1.4em;margin:0}.sf-muted{opacity:.7}.sf-error{color:var(--sf-danger)}.sf-row{align-items:center;display:flex;flex-wrap:wrap;gap:8px}.sf-row--end{justify-content:flex-end}.sf-field{display:flex;flex:1;flex-direction:column;gap:4px;min-width:200px}.sf-input{background:var(--sf-subtle);border:1px solid var(--sf-border);border-radius:6px;color:inherit;font:inherit;min-width:0;padding:6px 10px;width:100%}.sf-row .sf-input{flex:1;width:auto}.sf-btn{background:var(--sf-subtle);border:1px solid var(--sf-border);border-radius:6px;color:inherit;cursor:pointer;font:inherit;padding:5px 12px;white-space:nowrap}.sf-btn:hover:not(:disabled){border-color:var(--sf-accent)}.sf-btn:disabled{cursor:default;opacity:.45}.sf-btn--primary{background:var(--sf-accent);border-color:var(--sf-accent);color:#fff}.sf-btn--armed,.sf-btn--danger:hover:not(:disabled){border-color:var(--sf-danger);color:var(--sf-danger)}.sf-btn--armed{background:color-mix(in srgb,var(--sf-danger) 12%,transparent)}.sf-icon-btn{background:none;border:1px solid transparent;border-radius:6px;color:inherit;cursor:pointer;font:inherit;padding:3px 6px}.sf-icon-btn:hover{border-color:var(--sf-border)}.sf-upload{display:flex;flex-direction:column;gap:10px}.sf-drop{align-items:center;border:2px dashed var(--sf-border);border-radius:12px;cursor:pointer;display:flex;gap:20px;padding:28px 24px;transition:border-color .15s,background .15s,box-shadow .15s;-webkit-user-select:none;-moz-user-select:none;user-select:none}.sf-drop:focus-visible,.sf-drop:hover{border-color:var(--sf-accent);box-shadow:0 4px 14px color-mix(in srgb,currentColor 10%,transparent);outline:none}.sf-drop p{margin:0}.sf-drop__icon{flex-shrink:0;height:72px;opacity:.6;width:72px}.sf-drop__title{font-size:1.1em;font-weight:600;margin-bottom:4px!important}.sf-drop__count{color:var(--sf-accent);font-weight:600;margin-top:6px!important}.sf-drop--active{background:color-mix(in srgb,var(--sf-accent) 8%,transparent);border-color:var(--sf-accent)}.sf-drop--active .sf-drop__icon{color:var(--sf-accent);opacity:1}.sf-mode{gap:14px}.sf-queue progress{width:120px}.sf-upload>.sf-row{align-items:flex-end}.sf-queue{display:flex;flex-direction:column;gap:4px;list-style:none;margin:0;max-height:200px;overflow:auto;padding:0}.sf-queue li{display:flex;gap:12px;justify-content:space-between}.sf-queue__name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sf-toolbar{display:flex;flex-wrap:wrap;gap:12px;justify-content:space-between}.sf-search{max-width:320px}.sf-table-wrap{border:1px solid var(--sf-border);border-radius:8px;overflow-x:auto}.sf-table{border-collapse:collapse;width:100%}.sf-table td,.sf-table th{border-bottom:1px solid var(--sf-border);padding:8px 10px;text-align:left;vertical-align:middle}.sf-table tbody tr:last-child td{border-bottom:none}.sf-table th{background:var(--sf-subtle);font-weight:600}.sf-col-check{width:36px}.sf-col-actions{text-align:right!important}.sf-dir td{background:var(--sf-subtle)}.sf-dir__toggle{background:none;border:none;color:inherit;cursor:pointer;font:inherit;font-weight:600;padding:0}.sf-busy{opacity:.55}.sf-pill{background:var(--sf-subtle);border:1px solid var(--sf-border);border-radius:999px;color:inherit;cursor:pointer;font:inherit;font-size:12px;padding:2px 10px}.sf-pill--on{background:color-mix(in srgb,#16a34a 18%,transparent);border-color:#16a34a}.sf-overlay{align-items:center;background:rgba(0,0,0,.5);display:flex;inset:0;justify-content:center;padding:16px;position:fixed;z-index:1000}.sf-dialog{--sf-border:color-mix(in srgb,CanvasText 22%,transparent);--sf-subtle:color-mix(in srgb,CanvasText 6%,transparent);background:Canvas;border-radius:10px;color:CanvasText;display:flex;flex-direction:column;gap:12px;max-height:90vh;overflow:auto;padding:18px 20px;text-align:left;width:min(520px,100%)}.sf-dialog__head{align-items:center;display:flex;justify-content:space-between}.sf-dialog__head h3{margin:0}.sf-dialog>.sf-muted{margin:0;word-break:break-all}.sf-dialog__foot{justify-content:flex-end}.sf-fieldset{border:none;display:flex;flex-direction:column;gap:12px;margin:0;padding:0}.sf-fieldset:disabled{opacity:.5}.sf-fieldset p{margin:0}.sf-check{align-items:center;cursor:pointer;display:flex;gap:8px}.sf-check--inline{display:inline-flex}.sf-stack{display:flex;flex-direction:column;gap:12px}.sf-stack p{margin:0}.sf-row--bottom{align-items:flex-end}.sf-field--narrow{flex:0 1 200px;min-width:140px}.sf-admin{border:1px solid var(--sf-border);border-radius:10px;padding:10px 14px}.sf-admin>summary{cursor:pointer;font-weight:600}.sf-admin[open]>summary{margin-bottom:12px}.sf-card{border:1px solid var(--sf-border);border-radius:8px;display:flex;flex-direction:column;gap:10px;padding:12px}.sf-card h4,.sf-card p{margin:0}.sf-code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;resize:vertical}.sf-roots{display:flex;flex-direction:column;gap:6px;list-style:none;margin:0;padding:0}.sf-roots li{align-items:center;display:flex;gap:12px;justify-content:space-between}.sf-root-select{max-width:420px}.sf-browser{border:1px solid var(--sf-border);border-radius:6px;display:flex;flex-direction:column;max-height:320px;overflow:auto}.sf-browser>*{border-bottom:1px solid var(--sf-border);padding:5px 10px}.sf-browser>:last-child{border-bottom:none}.sf-browser__dir{background:none;border:none;border-bottom:1px solid var(--sf-border);color:inherit;cursor:pointer;font:inherit;text-align:left}.sf-browser__all,.sf-browser__dir:hover{background:var(--sf-subtle)}.sf-browser__all{font-weight:600}.sf-browser__name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sf-tag{border:1px solid var(--sf-border);border-radius:999px;display:inline-block;font-size:11px;margin-left:8px;padding:0 7px;vertical-align:middle}.sf-tag--warn{border-color:var(--sf-danger);color:var(--sf-danger)}.sf-strike{opacity:.7;text-decoration:line-through}.sf-browser__row{align-items:center;display:flex;gap:8px}.sf-browser__row .sf-browser__dir{border-bottom:none;flex:1;padding:0}";
|
|
543
|
+
var css_248z = ".sf-root{--sf-border:color-mix(in srgb,currentColor 22%,transparent);--sf-subtle:color-mix(in srgb,currentColor 6%,transparent);--sf-accent:#2563eb;--sf-danger:#dc2626;display:flex;flex-direction:column;font-size:14px;gap:16px;margin:0 auto;max-width:1100px;padding:16px;text-align:left}.sf-root *,.sf-root :after,.sf-root :before{box-sizing:border-box}.sf-head{align-items:baseline;display:flex;gap:12px;justify-content:space-between}.sf-head h2{font-size:1.4em;margin:0}.sf-muted{opacity:.7}.sf-error{color:var(--sf-danger)}.sf-row{align-items:center;display:flex;flex-wrap:wrap;gap:8px}.sf-row--end{justify-content:flex-end}.sf-field{display:flex;flex:1;flex-direction:column;gap:4px;min-width:200px}.sf-input{background:var(--sf-subtle);border:1px solid var(--sf-border);border-radius:6px;color:inherit;font:inherit;min-width:0;padding:6px 10px;width:100%}.sf-row .sf-input{flex:1;width:auto}.sf-btn{background:var(--sf-subtle);border:1px solid var(--sf-border);border-radius:6px;color:inherit;cursor:pointer;font:inherit;padding:5px 12px;white-space:nowrap}.sf-btn:hover:not(:disabled){border-color:var(--sf-accent)}.sf-btn:disabled{cursor:default;opacity:.45}.sf-btn--primary{background:var(--sf-accent);border-color:var(--sf-accent);color:#fff}.sf-btn--armed,.sf-btn--danger:hover:not(:disabled){border-color:var(--sf-danger);color:var(--sf-danger)}.sf-btn--armed{background:color-mix(in srgb,var(--sf-danger) 12%,transparent)}.sf-icon-btn{background:none;border:1px solid transparent;border-radius:6px;color:inherit;cursor:pointer;font:inherit;padding:3px 6px}.sf-icon-btn:hover{border-color:var(--sf-border)}.sf-upload{display:flex;flex-direction:column;gap:10px}.sf-drop{align-items:center;border:2px dashed var(--sf-border);border-radius:12px;cursor:pointer;display:flex;gap:20px;padding:28px 24px;transition:border-color .15s,background .15s,box-shadow .15s;-webkit-user-select:none;-moz-user-select:none;user-select:none}.sf-drop:focus-visible,.sf-drop:hover{border-color:var(--sf-accent);box-shadow:0 4px 14px color-mix(in srgb,currentColor 10%,transparent);outline:none}.sf-drop p{margin:0}.sf-drop__icon{flex-shrink:0;height:72px;opacity:.6;width:72px}.sf-drop__title{font-size:1.1em;font-weight:600;margin-bottom:4px!important}.sf-drop__count{color:var(--sf-accent);font-weight:600;margin-top:6px!important}.sf-drop--active{background:color-mix(in srgb,var(--sf-accent) 8%,transparent);border-color:var(--sf-accent)}.sf-drop--active .sf-drop__icon{color:var(--sf-accent);opacity:1}.sf-mode{gap:14px}.sf-queue progress{width:120px}.sf-upload>.sf-row{align-items:flex-end}.sf-queue{display:flex;flex-direction:column;gap:4px;list-style:none;margin:0;max-height:200px;overflow:auto;padding:0}.sf-queue li{display:flex;gap:12px;justify-content:space-between}.sf-queue__name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sf-toolbar{display:flex;flex-wrap:wrap;gap:12px;justify-content:space-between}.sf-search{max-width:320px}.sf-table-wrap{border:1px solid var(--sf-border);border-radius:8px;overflow-x:auto}.sf-table{border-collapse:collapse;width:100%}.sf-table td,.sf-table th{border-bottom:1px solid var(--sf-border);padding:8px 10px;text-align:left;vertical-align:middle}.sf-table tbody tr:last-child td{border-bottom:none}.sf-table th{background:var(--sf-subtle);font-weight:600}.sf-col-check{width:36px}.sf-col-actions{text-align:right!important}.sf-dir td{background:var(--sf-subtle)}.sf-dir__toggle{background:none;border:none;color:inherit;cursor:pointer;font:inherit;font-weight:600;padding:0}.sf-busy{opacity:.55}.sf-pill{background:var(--sf-subtle);border:1px solid var(--sf-border);border-radius:999px;color:inherit;cursor:pointer;font:inherit;font-size:12px;padding:2px 10px}.sf-pill--on{background:color-mix(in srgb,#16a34a 18%,transparent);border-color:#16a34a}.sf-overlay{align-items:center;background:rgba(0,0,0,.5);display:flex;inset:0;justify-content:center;padding:16px;position:fixed;z-index:1000}.sf-dialog{--sf-border:color-mix(in srgb,CanvasText 22%,transparent);--sf-subtle:color-mix(in srgb,CanvasText 6%,transparent);background:Canvas;border-radius:10px;color:CanvasText;display:flex;flex-direction:column;gap:12px;max-height:90vh;overflow:auto;padding:18px 20px;text-align:left;width:min(520px,100%)}.sf-dialog__head{align-items:center;display:flex;justify-content:space-between}.sf-dialog__head h3{margin:0}.sf-dialog>.sf-muted{margin:0;word-break:break-all}.sf-dialog__foot{justify-content:flex-end}.sf-fieldset{border:none;display:flex;flex-direction:column;gap:12px;margin:0;padding:0}.sf-fieldset:disabled{opacity:.5}.sf-fieldset p{margin:0}.sf-check{align-items:center;cursor:pointer;display:flex;gap:8px}.sf-check--inline{display:inline-flex}.sf-stack{display:flex;flex-direction:column;gap:12px}.sf-stack p{margin:0}.sf-row--bottom{align-items:flex-end}.sf-field--narrow{flex:0 1 200px;min-width:140px}.sf-admin{border:1px solid var(--sf-border);border-radius:10px;padding:10px 14px}.sf-admin>summary{cursor:pointer;font-weight:600}.sf-admin[open]>summary{margin-bottom:12px}.sf-card{border:1px solid var(--sf-border);border-radius:8px;display:flex;flex-direction:column;gap:10px;padding:12px}.sf-card h4,.sf-card p{margin:0}.sf-code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;resize:vertical}.sf-roots{display:flex;flex-direction:column;gap:6px;list-style:none;margin:0;padding:0}.sf-roots li{align-items:center;display:flex;gap:12px;justify-content:space-between}.sf-root-select{max-width:420px}.sf-browser{border:1px solid var(--sf-border);border-radius:6px;display:flex;flex-direction:column;max-height:320px;overflow:auto}.sf-browser>*{border-bottom:1px solid var(--sf-border);padding:5px 10px}.sf-browser>:last-child{border-bottom:none}.sf-browser__dir{background:none;border:none;border-bottom:1px solid var(--sf-border);color:inherit;cursor:pointer;font:inherit;text-align:left}.sf-browser__all,.sf-browser__dir:hover{background:var(--sf-subtle)}.sf-browser__all{font-weight:600}.sf-browser__name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sf-tag{border:1px solid var(--sf-border);border-radius:999px;display:inline-block;font-size:11px;margin-left:8px;padding:0 7px;vertical-align:middle}.sf-tag--warn{border-color:var(--sf-danger);color:var(--sf-danger)}.sf-strike{opacity:.7;text-decoration:line-through}.sf-browser__row{align-items:center;display:flex;gap:8px}.sf-browser__row .sf-browser__dir{border-bottom:none;flex:1;padding:0}.sf-dot{background:var(--sf-danger);border-radius:50%;display:inline-block;height:9px;vertical-align:middle;width:9px}.sf-dot--on{background:#16a34a}.sf-steps{display:flex;flex-direction:column;gap:10px;margin:0;padding-left:20px}.sf-steps li>.sf-row{margin:4px 0}.sf-codeline{background:var(--sf-subtle);border:1px solid var(--sf-border);border-radius:6px;font-size:12px;padding:4px 8px}.sf-codeline,.sf-paircode{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.sf-paircode{font-size:22px;font-weight:700;letter-spacing:.12em}.sf-small{font-size:12px}";
|
|
482
544
|
styleInject(css_248z);
|
|
483
545
|
|
|
484
546
|
function buildTree(files) {
|
|
@@ -504,6 +566,7 @@ function SecureFiles({ apiBase }) {
|
|
|
504
566
|
const [files, setFiles] = useState([]);
|
|
505
567
|
const [user, setUser] = useState(null);
|
|
506
568
|
const [isAdmin, setIsAdmin] = useState(false);
|
|
569
|
+
const [devices, setDevices] = useState([]);
|
|
507
570
|
const [loading, setLoading] = useState(true);
|
|
508
571
|
const [error, setError] = useState(null);
|
|
509
572
|
const [notice, setNotice] = useState(null);
|
|
@@ -518,6 +581,7 @@ function SecureFiles({ apiBase }) {
|
|
|
518
581
|
const data = await api.list();
|
|
519
582
|
setUser(data.user_name);
|
|
520
583
|
setIsAdmin(data.is_admin);
|
|
584
|
+
setDevices(data.devices || []);
|
|
521
585
|
setFiles(data.files);
|
|
522
586
|
setSelected((prev) => new Set([...prev].filter((id) => data.files.some((f) => f.id === id))));
|
|
523
587
|
}
|
|
@@ -600,7 +664,7 @@ function SecureFiles({ apiBase }) {
|
|
|
600
664
|
const searching = search.trim().length > 0;
|
|
601
665
|
const renderFile = (f, depth) => {
|
|
602
666
|
const busy = busyIds.has(f.id);
|
|
603
|
-
return (jsxs("tr", { className: busy ? "sf-busy" : undefined, children: [jsx("td", { className: "sf-col-check", children: jsx("input", { type: "checkbox", "aria-label": `Select ${f.filename}`, checked: selected.has(f.id), onChange: (e) => toggle([f.id], e.target.checked) }) }), jsxs("td", { style: { paddingLeft: 10 + depth * 20 }, children: [jsx("span", { className: f.available ? undefined : "sf-strike", children: f.filename }), f.kind === "folder" && (jsx("span", { className: "sf-tag", title: "Downloads as one uncompressed, resumable .tar of the whole folder", children: "folder \u00B7 .tar" })), f.linked && (jsx("span", { className: "sf-tag", title: f.source_path ?? undefined, children: "linked" })), !f.available && (jsx("span", { className: "sf-tag sf-tag--warn", title: f.linked ? "Source file is gone or its directory is no longer allowed" : "File is missing on disk", children: "missing" }))] }), jsxs("td", { children: [jsx("button", { type: "button", className: `sf-pill${f.shareable ? " sf-pill--on" : ""}`, disabled: busy || !f.available, onClick: () => setShared([f], !f.shareable), title: f.shareable ? "Click to stop sharing" : "Click to share", children: f.shareable ? "Shared" : "Private" }), f.shareable && f.has_password && jsx("span", { title: "Password protected", children: " \uD83D\uDD12" })] }), jsxs("td", { children: [f.download_count, " / ", f.download_limit ?? "∞"] }), jsx("td", { className: "sf-col-actions", children: jsxs("div", { className: "sf-row sf-row--end", children: [jsx("button", { type: "button", className: "sf-btn", disabled: busy || !f.available, onClick: () => download([f]), children: "Download" }), f.shareable && f.available && f.share_url && jsx(CopyButton, { text: f.share_url }), jsx("button", { type: "button", className: "sf-icon-btn", "aria-label": `Share settings for ${f.filename}`, title: "Share settings", onClick: () => setEditing(f), children: "\u2699\uFE0F" }), jsx(ConfirmButton, { label: f.linked ? "Unlink" : "Delete", disabled: busy, onConfirm: () => remove([f]) })] }) })] }, f.id));
|
|
667
|
+
return (jsxs("tr", { className: busy ? "sf-busy" : undefined, children: [jsx("td", { className: "sf-col-check", children: jsx("input", { type: "checkbox", "aria-label": `Select ${f.filename}`, checked: selected.has(f.id), onChange: (e) => toggle([f.id], e.target.checked) }) }), jsxs("td", { style: { paddingLeft: 10 + depth * 20 }, children: [jsx("span", { className: f.available || f.device ? undefined : "sf-strike", children: f.filename }), f.kind === "folder" && (jsx("span", { className: "sf-tag", title: "Downloads as one uncompressed, resumable .tar of the whole folder", children: "folder \u00B7 .tar" })), f.linked && (jsx("span", { className: "sf-tag", title: f.source_path ?? undefined, children: "linked" })), f.device && (jsxs("span", { className: `sf-tag${f.device.online ? "" : " sf-tag--warn"}`, title: "Served from your computer through the relay", children: ["on ", f.device.name, " \u00B7 ", f.device.online ? "online" : "offline"] })), !f.available && !f.device && (jsx("span", { className: "sf-tag sf-tag--warn", title: f.linked ? "Source file is gone or its directory is no longer allowed" : "File is missing on disk", children: "missing" }))] }), jsxs("td", { children: [jsx("button", { type: "button", className: `sf-pill${f.shareable ? " sf-pill--on" : ""}`, disabled: busy || !f.available, onClick: () => setShared([f], !f.shareable), title: f.shareable ? "Click to stop sharing" : "Click to share", children: f.shareable ? "Shared" : "Private" }), f.shareable && f.has_password && jsx("span", { title: "Password protected", children: " \uD83D\uDD12" })] }), jsxs("td", { children: [f.download_count, " / ", f.download_limit ?? "∞"] }), jsx("td", { className: "sf-col-actions", children: jsxs("div", { className: "sf-row sf-row--end", children: [jsx("button", { type: "button", className: "sf-btn", disabled: busy || !f.available, onClick: () => download([f]), children: "Download" }), f.shareable && f.available && f.share_url && jsx(CopyButton, { text: f.share_url }), jsx("button", { type: "button", className: "sf-icon-btn", "aria-label": `Share settings for ${f.filename}`, title: "Share settings", onClick: () => setEditing(f), children: "\u2699\uFE0F" }), f.device ? (jsxs("span", { className: "sf-muted sf-small", title: "Stop sharing it from that computer's Secure Files panel", children: ["managed on ", f.device.name] })) : (jsx(ConfirmButton, { label: f.linked ? "Unlink" : "Delete", disabled: busy, onConfirm: () => remove([f]) }))] }) })] }, f.id));
|
|
604
668
|
};
|
|
605
669
|
const renderFolder = (node, depth) => {
|
|
606
670
|
const rows = [];
|
|
@@ -617,7 +681,7 @@ function SecureFiles({ apiBase }) {
|
|
|
617
681
|
rows.push(...ownFiles.map((f) => renderFile(f, depth)));
|
|
618
682
|
return rows;
|
|
619
683
|
};
|
|
620
|
-
return (jsxs("div", { className: "sf-root", children: [jsxs("header", { className: "sf-head", children: [jsx("h2", { children: "Secure Files" }), user && jsxs("span", { className: "sf-muted", children: ["Signed in as ", user] })] }), jsx(UploadPanel, { api: api, folders: folders, onUploaded: load }), isAdmin && jsx(ServerFiles, { api: api, folders: folders, onLinked: load }), jsxs("section", { className: "sf-toolbar", children: [jsx("input", { className: "sf-input sf-search", type: "search", placeholder: "Search files\u2026", value: search, onChange: (e) => setSearch(e.target.value) }), jsxs("div", { className: "sf-row", children: [jsxs("span", { className: "sf-muted", children: [selected.size, " selected"] }), folders.length > 0 && (jsxs(Fragment, { children: [jsx("button", { type: "button", className: "sf-btn", onClick: () => setCollapsed(new Set()), children: "Expand all" }), jsx("button", { type: "button", className: "sf-btn", onClick: () => setCollapsed(new Set(folders)), children: "Collapse all" })] })), jsx("button", { type: "button", className: "sf-btn", disabled: !selected.size, onClick: () => download(selectedFiles), children: "Download" }), jsx("button", { type: "button", className: "sf-btn", disabled: !selected.size, onClick: () => setShared(selectedFiles, true), children: "Share" }), jsx("button", { type: "button", className: "sf-btn", disabled: !selected.size, onClick: () => setShared(selectedFiles, false), children: "Unshare" }), jsx(ConfirmButton, { label: "Delete", disabled: !selected.size, onConfirm: () => remove(selectedFiles) })] })] }), notice && (jsx("p", { className: "sf-error", role: "alert", children: notice })), loading ? (jsx("p", { className: "sf-muted", children: "Loading\u2026" })) : error ? (jsx("p", { className: "sf-error", role: "alert", children: error })) : files.length === 0 ? (jsx("p", { className: "sf-muted", children: "No files yet \u2014 upload something above." })) : (jsx("div", { className: "sf-table-wrap", children: jsxs("table", { className: "sf-table", children: [jsx("thead", { children: jsxs("tr", { children: [jsx("th", { className: "sf-col-check", children: jsx("input", { type: "checkbox", "aria-label": "Select all", checked: allVisibleSelected, onChange: (e) => toggle(filtered.map((f) => f.id), e.target.checked) }) }), jsx("th", { children: "Name" }), jsx("th", { children: "Sharing" }), jsx("th", { children: "Downloads" }), jsx("th", { className: "sf-col-actions", children: "Actions" })] }) }), jsx("tbody", { children: renderFolder(tree, 0) })] }) })), editing && (jsx(ShareDialog, { file: files.find((f) => f.id === editing.id) ?? editing, onClose: () => setEditing(null), onSave: saveShare }, editing.id))] }));
|
|
684
|
+
return (jsxs("div", { className: "sf-root", children: [jsxs("header", { className: "sf-head", children: [jsx("h2", { children: "Secure Files" }), user && jsxs("span", { className: "sf-muted", children: ["Signed in as ", user] })] }), jsx(UploadPanel, { api: api, folders: folders.filter((d) => d !== "devices" && !d.startsWith("devices/")), onUploaded: load }), jsx(MyComputers, { api: api, devices: devices, onChange: load }), isAdmin && jsx(ServerFiles, { api: api, folders: folders, onLinked: load }), jsxs("section", { className: "sf-toolbar", children: [jsx("input", { className: "sf-input sf-search", type: "search", placeholder: "Search files\u2026", value: search, onChange: (e) => setSearch(e.target.value) }), jsxs("div", { className: "sf-row", children: [jsxs("span", { className: "sf-muted", children: [selected.size, " selected"] }), folders.length > 0 && (jsxs(Fragment, { children: [jsx("button", { type: "button", className: "sf-btn", onClick: () => setCollapsed(new Set()), children: "Expand all" }), jsx("button", { type: "button", className: "sf-btn", onClick: () => setCollapsed(new Set(folders)), children: "Collapse all" })] })), jsx("button", { type: "button", className: "sf-btn", disabled: !selected.size, onClick: () => download(selectedFiles), children: "Download" }), jsx("button", { type: "button", className: "sf-btn", disabled: !selected.size, onClick: () => setShared(selectedFiles, true), children: "Share" }), jsx("button", { type: "button", className: "sf-btn", disabled: !selected.size, onClick: () => setShared(selectedFiles, false), children: "Unshare" }), jsx(ConfirmButton, { label: "Delete", disabled: !selected.size, onConfirm: () => remove(selectedFiles) })] })] }), notice && (jsx("p", { className: "sf-error", role: "alert", children: notice })), loading ? (jsx("p", { className: "sf-muted", children: "Loading\u2026" })) : error ? (jsx("p", { className: "sf-error", role: "alert", children: error })) : files.length === 0 ? (jsx("p", { className: "sf-muted", children: "No files yet \u2014 upload something above." })) : (jsx("div", { className: "sf-table-wrap", children: jsxs("table", { className: "sf-table", children: [jsx("thead", { children: jsxs("tr", { children: [jsx("th", { className: "sf-col-check", children: jsx("input", { type: "checkbox", "aria-label": "Select all", checked: allVisibleSelected, onChange: (e) => toggle(filtered.map((f) => f.id), e.target.checked) }) }), jsx("th", { children: "Name" }), jsx("th", { children: "Sharing" }), jsx("th", { children: "Downloads" }), jsx("th", { className: "sf-col-actions", children: "Actions" })] }) }), jsx("tbody", { children: renderFolder(tree, 0) })] }) })), editing && (jsx(ShareDialog, { file: files.find((f) => f.id === editing.id) ?? editing, onClose: () => setEditing(null), onSave: saveShare }, editing.id))] }));
|
|
621
685
|
}
|
|
622
686
|
|
|
623
687
|
export { ApiError, DEFAULT_API_BASE, SecureFiles, createApi };
|