@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
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SecureFilesApi } from "./api";
|
|
2
|
+
import type { Device } from "./types";
|
|
3
|
+
export declare function MyComputers({ api, devices, onChange, }: {
|
|
4
|
+
api: SecureFilesApi;
|
|
5
|
+
devices: Device[];
|
|
6
|
+
onChange: () => void;
|
|
7
|
+
}): import("react/jsx-runtime").JSX.Element;
|
package/dist/api.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BrowseResult, LinkRoot, RootChallenge, SecureFile, ShareUpdate } from "./types";
|
|
1
|
+
import type { BrowseResult, Device, LinkRoot, RootChallenge, SecureFile, ShareUpdate } from "./types";
|
|
2
2
|
export declare const DEFAULT_API_BASE = "https://api.abstractendeavors.com";
|
|
3
3
|
export declare class ApiError extends Error {
|
|
4
4
|
status: number;
|
|
@@ -9,6 +9,17 @@ export declare function createApi(apiBase?: string): {
|
|
|
9
9
|
user_name: string;
|
|
10
10
|
is_admin: boolean;
|
|
11
11
|
files: SecureFile[];
|
|
12
|
+
devices: Device[];
|
|
13
|
+
}>;
|
|
14
|
+
devices: () => Promise<{
|
|
15
|
+
devices: Device[];
|
|
16
|
+
}>;
|
|
17
|
+
pairDevice: () => Promise<{
|
|
18
|
+
code: string;
|
|
19
|
+
expires_in: number;
|
|
20
|
+
}>;
|
|
21
|
+
removeDevice: (id: number) => Promise<{
|
|
22
|
+
devices: Device[];
|
|
12
23
|
}>;
|
|
13
24
|
roots: () => Promise<{
|
|
14
25
|
roots: LinkRoot[];
|
package/dist/index.cjs
CHANGED
|
@@ -37,6 +37,9 @@ function createApi(apiBase = DEFAULT_API_BASE) {
|
|
|
37
37
|
}
|
|
38
38
|
return {
|
|
39
39
|
list: () => json("/userInfo"),
|
|
40
|
+
devices: () => json("/devices"),
|
|
41
|
+
pairDevice: () => json("/devices/pair", { method: "POST", body: "{}" }),
|
|
42
|
+
removeDevice: (id) => json(`/devices/${id}`, { method: "DELETE" }),
|
|
40
43
|
roots: () => json("/admin/roots"),
|
|
41
44
|
requestRoot: (path) => json("/admin/roots", { method: "POST", body: JSON.stringify({ path }) }),
|
|
42
45
|
confirmRoot: (nonce, signature, label) => json("/admin/roots/confirm", {
|
|
@@ -329,7 +332,7 @@ function formatSize(bytes) {
|
|
|
329
332
|
}
|
|
330
333
|
return `${n.toFixed(i ? 1 : 0)} ${units[i]}`;
|
|
331
334
|
}
|
|
332
|
-
const errText = (err) => (err instanceof Error ? err.message : String(err));
|
|
335
|
+
const errText$1 = (err) => (err instanceof Error ? err.message : String(err));
|
|
333
336
|
function AddRoot({ api, onAdded }) {
|
|
334
337
|
const [path, setPath] = react.useState("");
|
|
335
338
|
const [label, setLabel] = react.useState("");
|
|
@@ -345,7 +348,7 @@ function AddRoot({ api, onAdded }) {
|
|
|
345
348
|
setSignature("");
|
|
346
349
|
}
|
|
347
350
|
catch (err) {
|
|
348
|
-
setError(errText(err));
|
|
351
|
+
setError(errText$1(err));
|
|
349
352
|
}
|
|
350
353
|
finally {
|
|
351
354
|
setBusy(false);
|
|
@@ -364,7 +367,7 @@ function AddRoot({ api, onAdded }) {
|
|
|
364
367
|
setSignature("");
|
|
365
368
|
}
|
|
366
369
|
catch (err) {
|
|
367
|
-
setError(errText(err));
|
|
370
|
+
setError(errText$1(err));
|
|
368
371
|
}
|
|
369
372
|
finally {
|
|
370
373
|
setBusy(false);
|
|
@@ -387,7 +390,7 @@ function Browser({ api, roots, folders, onLinked, }) {
|
|
|
387
390
|
setPicked(new Set());
|
|
388
391
|
}
|
|
389
392
|
catch (err) {
|
|
390
|
-
setError(errText(err));
|
|
393
|
+
setError(errText$1(err));
|
|
391
394
|
}
|
|
392
395
|
}, [api]);
|
|
393
396
|
react.useEffect(() => {
|
|
@@ -415,7 +418,7 @@ function Browser({ api, roots, folders, onLinked, }) {
|
|
|
415
418
|
await open(listing.path);
|
|
416
419
|
}
|
|
417
420
|
catch (err) {
|
|
418
|
-
setResult(errText(err));
|
|
421
|
+
setResult(errText$1(err));
|
|
419
422
|
}
|
|
420
423
|
finally {
|
|
421
424
|
setBusy(false);
|
|
@@ -439,7 +442,7 @@ function ServerFiles({ api, folders, onLinked, }) {
|
|
|
439
442
|
setError(null);
|
|
440
443
|
}
|
|
441
444
|
catch (err) {
|
|
442
|
-
setError(errText(err));
|
|
445
|
+
setError(errText$1(err));
|
|
443
446
|
}
|
|
444
447
|
}, [api]);
|
|
445
448
|
return (jsxRuntime.jsxs("details", { className: "sf-admin", onToggle: (e) => e.currentTarget.open && !roots && load(), children: [jsxRuntime.jsx("summary", { children: "Server files (admin)" }), jsxRuntime.jsxs("div", { className: "sf-stack", children: [jsxRuntime.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 && jsxRuntime.jsx("p", { className: "sf-error", children: error }), roots && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: "sf-card", children: [jsxRuntime.jsx("h4", { children: "Allowed directories" }), roots.length === 0 ? (jsxRuntime.jsx("p", { className: "sf-muted", children: "None yet." })) : (jsxRuntime.jsx("ul", { className: "sf-roots", children: roots.map((r) => (jsxRuntime.jsxs("li", { children: [jsxRuntime.jsxs("span", { children: [jsxRuntime.jsx("code", { children: r.path }), " ", r.label && jsxRuntime.jsxs("span", { className: "sf-muted", children: ["\u2014 ", r.label] }), !r.exists && jsxRuntime.jsx("span", { className: "sf-error", children: " (missing)" })] }), jsxRuntime.jsx(ConfirmButton, { label: "Remove", onConfirm: async () => {
|
|
@@ -448,11 +451,70 @@ function ServerFiles({ api, folders, onLinked, }) {
|
|
|
448
451
|
onLinked();
|
|
449
452
|
}
|
|
450
453
|
catch (err) {
|
|
451
|
-
setError(errText(err));
|
|
454
|
+
setError(errText$1(err));
|
|
452
455
|
}
|
|
453
456
|
} })] }, r.id))) }))] }), jsxRuntime.jsx(AddRoot, { api: api, onAdded: setRoots }), jsxRuntime.jsx(Browser, { api: api, roots: roots, folders: folders, onLinked: onLinked })] }))] })] }));
|
|
454
457
|
}
|
|
455
458
|
|
|
459
|
+
const errText = (err) => (err instanceof Error ? err.message : String(err));
|
|
460
|
+
function ago(iso) {
|
|
461
|
+
if (!iso)
|
|
462
|
+
return "never";
|
|
463
|
+
const secs = Math.max(0, (Date.now() - new Date(iso).getTime()) / 1000);
|
|
464
|
+
if (secs < 90)
|
|
465
|
+
return "just now";
|
|
466
|
+
if (secs < 3600)
|
|
467
|
+
return `${Math.round(secs / 60)} min ago`;
|
|
468
|
+
if (secs < 86400)
|
|
469
|
+
return `${Math.round(secs / 3600)} h ago`;
|
|
470
|
+
return `${Math.round(secs / 86400)} d ago`;
|
|
471
|
+
}
|
|
472
|
+
function AddComputer({ api, onPaired }) {
|
|
473
|
+
const [code, setCode] = react.useState(null);
|
|
474
|
+
const [expiresAt, setExpiresAt] = react.useState(0);
|
|
475
|
+
const [, setTick] = react.useState(0);
|
|
476
|
+
const [error, setError] = react.useState(null);
|
|
477
|
+
react.useEffect(() => {
|
|
478
|
+
if (!code)
|
|
479
|
+
return;
|
|
480
|
+
const timer = setInterval(() => {
|
|
481
|
+
if (Date.now() > expiresAt)
|
|
482
|
+
setCode(null);
|
|
483
|
+
setTick((t) => t + 1);
|
|
484
|
+
onPaired();
|
|
485
|
+
}, 5000);
|
|
486
|
+
return () => clearInterval(timer);
|
|
487
|
+
}, [code, expiresAt, onPaired]);
|
|
488
|
+
const create = async () => {
|
|
489
|
+
setError(null);
|
|
490
|
+
try {
|
|
491
|
+
const res = await api.pairDevice();
|
|
492
|
+
setCode(res.code);
|
|
493
|
+
setExpiresAt(Date.now() + res.expires_in * 1000);
|
|
494
|
+
}
|
|
495
|
+
catch (err) {
|
|
496
|
+
setError(errText(err));
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
const minutesLeft = Math.max(1, Math.round((expiresAt - Date.now()) / 60000));
|
|
500
|
+
if (!code) {
|
|
501
|
+
return (jsxRuntime.jsxs("div", { className: "sf-row", children: [jsxRuntime.jsx("button", { type: "button", className: "sf-btn sf-btn--primary", onClick: create, children: "Add a computer" }), error && jsxRuntime.jsx("span", { className: "sf-error", children: error })] }));
|
|
502
|
+
}
|
|
503
|
+
return (jsxRuntime.jsxs("div", { className: "sf-card", children: [jsxRuntime.jsx("h4", { children: "Connect a computer" }), jsxRuntime.jsxs("ol", { className: "sf-steps", children: [jsxRuntime.jsxs("li", { children: ["On that computer, install the agent (Linux, macOS or Windows with Python 3.9+):", jsxRuntime.jsxs("div", { className: "sf-row", children: [jsxRuntime.jsx("code", { className: "sf-codeline", children: "pipx install abstract-securefiles && securefiles setup" }), jsxRuntime.jsx(CopyButton, { text: "pipx install abstract-securefiles && securefiles setup", label: "Copy" })] }), jsxRuntime.jsxs("span", { className: "sf-muted", children: ["No pipx? Use ", jsxRuntime.jsx("code", { children: "python3 -m pip install --user abstract-securefiles" }), " then ", jsxRuntime.jsx("code", { children: "securefiles setup" }), "."] })] }), jsxRuntime.jsxs("li", { children: ["In the panel that opens (", jsxRuntime.jsx("code", { children: "http://127.0.0.1:47821" }), "), enter this code:", jsxRuntime.jsxs("div", { className: "sf-row", children: [jsxRuntime.jsx("span", { className: "sf-paircode", children: code }), jsxRuntime.jsx(CopyButton, { text: code, label: "Copy" })] }), jsxRuntime.jsxs("span", { className: "sf-muted", children: ["Valid for about ", minutesLeft, " more minutes, once."] })] }), jsxRuntime.jsxs("li", { children: ["Pick the files and folders to share there. They appear here under ", jsxRuntime.jsx("code", { children: "devices/<computer>/" }), "."] })] }), jsxRuntime.jsx("div", { className: "sf-row", children: jsxRuntime.jsx("button", { type: "button", className: "sf-btn", onClick: () => setCode(null), children: "Done" }) })] }));
|
|
504
|
+
}
|
|
505
|
+
function MyComputers({ api, devices, onChange, }) {
|
|
506
|
+
const [error, setError] = react.useState(null);
|
|
507
|
+
return (jsxRuntime.jsxs("details", { className: "sf-admin", children: [jsxRuntime.jsxs("summary", { children: ["My computers", " ", jsxRuntime.jsxs("span", { className: "sf-muted", children: ["(", devices.filter((d) => d.online).length, " online / ", devices.length, ")"] })] }), jsxRuntime.jsxs("div", { className: "sf-stack", children: [jsxRuntime.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 && (jsxRuntime.jsx("ul", { className: "sf-roots", children: devices.map((d) => (jsxRuntime.jsxs("li", { children: [jsxRuntime.jsxs("span", { children: [jsxRuntime.jsx("span", { className: `sf-dot${d.online ? " sf-dot--on" : ""}` }), " ", jsxRuntime.jsx("b", { children: d.name }), " ", jsxRuntime.jsxs("span", { className: "sf-muted", children: [d.online ? "online" : `offline · last seen ${ago(d.last_seen)}`, d.platform ? ` · ${d.platform}` : ""] })] }), jsxRuntime.jsx(ConfirmButton, { label: "Remove", onConfirm: async () => {
|
|
508
|
+
try {
|
|
509
|
+
await api.removeDevice(d.id);
|
|
510
|
+
onChange();
|
|
511
|
+
}
|
|
512
|
+
catch (err) {
|
|
513
|
+
setError(errText(err));
|
|
514
|
+
}
|
|
515
|
+
} })] }, d.id))) })), error && jsxRuntime.jsx("p", { className: "sf-error", children: error }), jsxRuntime.jsx(AddComputer, { api: api, onPaired: onChange })] })] }));
|
|
516
|
+
}
|
|
517
|
+
|
|
456
518
|
function styleInject(css, ref) {
|
|
457
519
|
if ( ref === void 0 ) ref = {};
|
|
458
520
|
var insertAt = ref.insertAt;
|
|
@@ -480,7 +542,7 @@ function styleInject(css, ref) {
|
|
|
480
542
|
}
|
|
481
543
|
}
|
|
482
544
|
|
|
483
|
-
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}";
|
|
545
|
+
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}";
|
|
484
546
|
styleInject(css_248z);
|
|
485
547
|
|
|
486
548
|
function buildTree(files) {
|
|
@@ -506,6 +568,7 @@ function SecureFiles({ apiBase }) {
|
|
|
506
568
|
const [files, setFiles] = react.useState([]);
|
|
507
569
|
const [user, setUser] = react.useState(null);
|
|
508
570
|
const [isAdmin, setIsAdmin] = react.useState(false);
|
|
571
|
+
const [devices, setDevices] = react.useState([]);
|
|
509
572
|
const [loading, setLoading] = react.useState(true);
|
|
510
573
|
const [error, setError] = react.useState(null);
|
|
511
574
|
const [notice, setNotice] = react.useState(null);
|
|
@@ -520,6 +583,7 @@ function SecureFiles({ apiBase }) {
|
|
|
520
583
|
const data = await api.list();
|
|
521
584
|
setUser(data.user_name);
|
|
522
585
|
setIsAdmin(data.is_admin);
|
|
586
|
+
setDevices(data.devices || []);
|
|
523
587
|
setFiles(data.files);
|
|
524
588
|
setSelected((prev) => new Set([...prev].filter((id) => data.files.some((f) => f.id === id))));
|
|
525
589
|
}
|
|
@@ -602,7 +666,7 @@ function SecureFiles({ apiBase }) {
|
|
|
602
666
|
const searching = search.trim().length > 0;
|
|
603
667
|
const renderFile = (f, depth) => {
|
|
604
668
|
const busy = busyIds.has(f.id);
|
|
605
|
-
return (jsxRuntime.jsxs("tr", { className: busy ? "sf-busy" : undefined, children: [jsxRuntime.jsx("td", { className: "sf-col-check", children: jsxRuntime.jsx("input", { type: "checkbox", "aria-label": `Select ${f.filename}`, checked: selected.has(f.id), onChange: (e) => toggle([f.id], e.target.checked) }) }), jsxRuntime.jsxs("td", { style: { paddingLeft: 10 + depth * 20 }, children: [jsxRuntime.jsx("span", { className: f.available ? undefined : "sf-strike", children: f.filename }), f.kind === "folder" && (jsxRuntime.jsx("span", { className: "sf-tag", title: "Downloads as one uncompressed, resumable .tar of the whole folder", children: "folder \u00B7 .tar" })), f.linked && (jsxRuntime.jsx("span", { className: "sf-tag", title: f.source_path ?? undefined, children: "linked" })), !f.available && (jsxRuntime.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" }))] }), jsxRuntime.jsxs("td", { children: [jsxRuntime.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 && jsxRuntime.jsx("span", { title: "Password protected", children: " \uD83D\uDD12" })] }), jsxRuntime.jsxs("td", { children: [f.download_count, " / ", f.download_limit ?? "∞"] }), jsxRuntime.jsx("td", { className: "sf-col-actions", children: jsxRuntime.jsxs("div", { className: "sf-row sf-row--end", children: [jsxRuntime.jsx("button", { type: "button", className: "sf-btn", disabled: busy || !f.available, onClick: () => download([f]), children: "Download" }), f.shareable && f.available && f.share_url && jsxRuntime.jsx(CopyButton, { text: f.share_url }), jsxRuntime.jsx("button", { type: "button", className: "sf-icon-btn", "aria-label": `Share settings for ${f.filename}`, title: "Share settings", onClick: () => setEditing(f), children: "\u2699\uFE0F" }), jsxRuntime.jsx(ConfirmButton, { label: f.linked ? "Unlink" : "Delete", disabled: busy, onConfirm: () => remove([f]) })] }) })] }, f.id));
|
|
669
|
+
return (jsxRuntime.jsxs("tr", { className: busy ? "sf-busy" : undefined, children: [jsxRuntime.jsx("td", { className: "sf-col-check", children: jsxRuntime.jsx("input", { type: "checkbox", "aria-label": `Select ${f.filename}`, checked: selected.has(f.id), onChange: (e) => toggle([f.id], e.target.checked) }) }), jsxRuntime.jsxs("td", { style: { paddingLeft: 10 + depth * 20 }, children: [jsxRuntime.jsx("span", { className: f.available || f.device ? undefined : "sf-strike", children: f.filename }), f.kind === "folder" && (jsxRuntime.jsx("span", { className: "sf-tag", title: "Downloads as one uncompressed, resumable .tar of the whole folder", children: "folder \u00B7 .tar" })), f.linked && (jsxRuntime.jsx("span", { className: "sf-tag", title: f.source_path ?? undefined, children: "linked" })), f.device && (jsxRuntime.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 && (jsxRuntime.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" }))] }), jsxRuntime.jsxs("td", { children: [jsxRuntime.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 && jsxRuntime.jsx("span", { title: "Password protected", children: " \uD83D\uDD12" })] }), jsxRuntime.jsxs("td", { children: [f.download_count, " / ", f.download_limit ?? "∞"] }), jsxRuntime.jsx("td", { className: "sf-col-actions", children: jsxRuntime.jsxs("div", { className: "sf-row sf-row--end", children: [jsxRuntime.jsx("button", { type: "button", className: "sf-btn", disabled: busy || !f.available, onClick: () => download([f]), children: "Download" }), f.shareable && f.available && f.share_url && jsxRuntime.jsx(CopyButton, { text: f.share_url }), jsxRuntime.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 ? (jsxRuntime.jsxs("span", { className: "sf-muted sf-small", title: "Stop sharing it from that computer's Secure Files panel", children: ["managed on ", f.device.name] })) : (jsxRuntime.jsx(ConfirmButton, { label: f.linked ? "Unlink" : "Delete", disabled: busy, onConfirm: () => remove([f]) }))] }) })] }, f.id));
|
|
606
670
|
};
|
|
607
671
|
const renderFolder = (node, depth) => {
|
|
608
672
|
const rows = [];
|
|
@@ -619,7 +683,7 @@ function SecureFiles({ apiBase }) {
|
|
|
619
683
|
rows.push(...ownFiles.map((f) => renderFile(f, depth)));
|
|
620
684
|
return rows;
|
|
621
685
|
};
|
|
622
|
-
return (jsxRuntime.jsxs("div", { className: "sf-root", children: [jsxRuntime.jsxs("header", { className: "sf-head", children: [jsxRuntime.jsx("h2", { children: "Secure Files" }), user && jsxRuntime.jsxs("span", { className: "sf-muted", children: ["Signed in as ", user] })] }), jsxRuntime.jsx(UploadPanel, { api: api, folders: folders, onUploaded: load }), isAdmin && jsxRuntime.jsx(ServerFiles, { api: api, folders: folders, onLinked: load }), jsxRuntime.jsxs("section", { className: "sf-toolbar", children: [jsxRuntime.jsx("input", { className: "sf-input sf-search", type: "search", placeholder: "Search files\u2026", value: search, onChange: (e) => setSearch(e.target.value) }), jsxRuntime.jsxs("div", { className: "sf-row", children: [jsxRuntime.jsxs("span", { className: "sf-muted", children: [selected.size, " selected"] }), folders.length > 0 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("button", { type: "button", className: "sf-btn", onClick: () => setCollapsed(new Set()), children: "Expand all" }), jsxRuntime.jsx("button", { type: "button", className: "sf-btn", onClick: () => setCollapsed(new Set(folders)), children: "Collapse all" })] })), jsxRuntime.jsx("button", { type: "button", className: "sf-btn", disabled: !selected.size, onClick: () => download(selectedFiles), children: "Download" }), jsxRuntime.jsx("button", { type: "button", className: "sf-btn", disabled: !selected.size, onClick: () => setShared(selectedFiles, true), children: "Share" }), jsxRuntime.jsx("button", { type: "button", className: "sf-btn", disabled: !selected.size, onClick: () => setShared(selectedFiles, false), children: "Unshare" }), jsxRuntime.jsx(ConfirmButton, { label: "Delete", disabled: !selected.size, onConfirm: () => remove(selectedFiles) })] })] }), notice && (jsxRuntime.jsx("p", { className: "sf-error", role: "alert", children: notice })), loading ? (jsxRuntime.jsx("p", { className: "sf-muted", children: "Loading\u2026" })) : error ? (jsxRuntime.jsx("p", { className: "sf-error", role: "alert", children: error })) : files.length === 0 ? (jsxRuntime.jsx("p", { className: "sf-muted", children: "No files yet \u2014 upload something above." })) : (jsxRuntime.jsx("div", { className: "sf-table-wrap", children: jsxRuntime.jsxs("table", { className: "sf-table", children: [jsxRuntime.jsx("thead", { children: jsxRuntime.jsxs("tr", { children: [jsxRuntime.jsx("th", { className: "sf-col-check", children: jsxRuntime.jsx("input", { type: "checkbox", "aria-label": "Select all", checked: allVisibleSelected, onChange: (e) => toggle(filtered.map((f) => f.id), e.target.checked) }) }), jsxRuntime.jsx("th", { children: "Name" }), jsxRuntime.jsx("th", { children: "Sharing" }), jsxRuntime.jsx("th", { children: "Downloads" }), jsxRuntime.jsx("th", { className: "sf-col-actions", children: "Actions" })] }) }), jsxRuntime.jsx("tbody", { children: renderFolder(tree, 0) })] }) })), editing && (jsxRuntime.jsx(ShareDialog, { file: files.find((f) => f.id === editing.id) ?? editing, onClose: () => setEditing(null), onSave: saveShare }, editing.id))] }));
|
|
686
|
+
return (jsxRuntime.jsxs("div", { className: "sf-root", children: [jsxRuntime.jsxs("header", { className: "sf-head", children: [jsxRuntime.jsx("h2", { children: "Secure Files" }), user && jsxRuntime.jsxs("span", { className: "sf-muted", children: ["Signed in as ", user] })] }), jsxRuntime.jsx(UploadPanel, { api: api, folders: folders.filter((d) => d !== "devices" && !d.startsWith("devices/")), onUploaded: load }), jsxRuntime.jsx(MyComputers, { api: api, devices: devices, onChange: load }), isAdmin && jsxRuntime.jsx(ServerFiles, { api: api, folders: folders, onLinked: load }), jsxRuntime.jsxs("section", { className: "sf-toolbar", children: [jsxRuntime.jsx("input", { className: "sf-input sf-search", type: "search", placeholder: "Search files\u2026", value: search, onChange: (e) => setSearch(e.target.value) }), jsxRuntime.jsxs("div", { className: "sf-row", children: [jsxRuntime.jsxs("span", { className: "sf-muted", children: [selected.size, " selected"] }), folders.length > 0 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("button", { type: "button", className: "sf-btn", onClick: () => setCollapsed(new Set()), children: "Expand all" }), jsxRuntime.jsx("button", { type: "button", className: "sf-btn", onClick: () => setCollapsed(new Set(folders)), children: "Collapse all" })] })), jsxRuntime.jsx("button", { type: "button", className: "sf-btn", disabled: !selected.size, onClick: () => download(selectedFiles), children: "Download" }), jsxRuntime.jsx("button", { type: "button", className: "sf-btn", disabled: !selected.size, onClick: () => setShared(selectedFiles, true), children: "Share" }), jsxRuntime.jsx("button", { type: "button", className: "sf-btn", disabled: !selected.size, onClick: () => setShared(selectedFiles, false), children: "Unshare" }), jsxRuntime.jsx(ConfirmButton, { label: "Delete", disabled: !selected.size, onConfirm: () => remove(selectedFiles) })] })] }), notice && (jsxRuntime.jsx("p", { className: "sf-error", role: "alert", children: notice })), loading ? (jsxRuntime.jsx("p", { className: "sf-muted", children: "Loading\u2026" })) : error ? (jsxRuntime.jsx("p", { className: "sf-error", role: "alert", children: error })) : files.length === 0 ? (jsxRuntime.jsx("p", { className: "sf-muted", children: "No files yet \u2014 upload something above." })) : (jsxRuntime.jsx("div", { className: "sf-table-wrap", children: jsxRuntime.jsxs("table", { className: "sf-table", children: [jsxRuntime.jsx("thead", { children: jsxRuntime.jsxs("tr", { children: [jsxRuntime.jsx("th", { className: "sf-col-check", children: jsxRuntime.jsx("input", { type: "checkbox", "aria-label": "Select all", checked: allVisibleSelected, onChange: (e) => toggle(filtered.map((f) => f.id), e.target.checked) }) }), jsxRuntime.jsx("th", { children: "Name" }), jsxRuntime.jsx("th", { children: "Sharing" }), jsxRuntime.jsx("th", { children: "Downloads" }), jsxRuntime.jsx("th", { className: "sf-col-actions", children: "Actions" })] }) }), jsxRuntime.jsx("tbody", { children: renderFolder(tree, 0) })] }) })), editing && (jsxRuntime.jsx(ShareDialog, { file: files.find((f) => f.id === editing.id) ?? editing, onClose: () => setEditing(null), onSave: saveShare }, editing.id))] }));
|
|
623
687
|
}
|
|
624
688
|
|
|
625
689
|
exports.ApiError = ApiError;
|