@brftech/filex-core 0.19.0 → 0.20.0

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 (39) hide show
  1. package/README.md +34 -4
  2. package/dist/filex-core.js +10626 -6501
  3. package/dist/filex-core.js.map +1 -1
  4. package/dist/filex-core.umd.cjs +94 -63
  5. package/dist/filex-core.umd.cjs.map +1 -1
  6. package/dist/index.d.ts +1026 -7
  7. package/dist/style.css +1 -1
  8. package/package.json +3 -3
  9. package/src/FileExplorer.vue +103 -68
  10. package/src/components/ConnectionGuideView.vue +333 -0
  11. package/src/components/ConnectionsPanel.vue +912 -0
  12. package/src/components/NFSExportsPanel.vue +283 -0
  13. package/src/components/S3KeysPanel.vue +381 -0
  14. package/src/components/SSHKeysPanel.vue +222 -0
  15. package/src/components/StorageFields.vue +362 -0
  16. package/src/components/TokensPanel.vue +191 -0
  17. package/src/components/UploadProgress.vue +5 -1
  18. package/src/composables/useConnections.ts +271 -0
  19. package/src/composables/useFileApi.ts +15 -2
  20. package/src/composables/useNFSExports.ts +148 -0
  21. package/src/composables/useS3Keys.ts +175 -0
  22. package/src/composables/useSSHKeys.ts +119 -0
  23. package/src/composables/useThumbs.ts +1 -1
  24. package/src/composables/useTokens.ts +121 -0
  25. package/src/composables/useUploadChunked.ts +433 -164
  26. package/src/index.ts +77 -2
  27. package/src/lib/connectionGuides.ts +1279 -0
  28. package/src/lib/realtime.ts +1 -1
  29. package/src/lib/uploadResume.ts +157 -0
  30. package/src/locales/en.ts +413 -0
  31. package/src/locales/tr.ts +416 -0
  32. package/src/modals/ConvertModal.vue +1 -1
  33. package/src/styles/base.css +12 -12
  34. package/src/types/Connections.ts +122 -0
  35. package/src/types/ExplorerConfig.ts +23 -2
  36. package/src/types/NFSExports.ts +47 -0
  37. package/src/types/S3Keys.ts +55 -0
  38. package/src/types/SSHKeys.ts +54 -0
  39. package/src/types/Tokens.ts +39 -0
package/dist/index.d.ts CHANGED
@@ -7,6 +7,7 @@ import { GlobalComponents } from 'vue';
7
7
  import { GlobalDirectives } from 'vue';
8
8
  import { PublicProps } from 'vue';
9
9
  import { Ref } from 'vue';
10
+ import { ShallowRef } from 'vue';
10
11
 
11
12
  declare type __VLS_Props = {
12
13
  config: ExplorerConfig;
@@ -60,6 +61,65 @@ declare type __VLS_Props_12 = {
60
61
  busy?: boolean;
61
62
  };
62
63
 
64
+ declare type __VLS_Props_13 = {
65
+ config: ExplorerConfig;
66
+ /** Which half to open on. */
67
+ initialTab?: 'storages' | 'connect';
68
+ /** Draw a close control — the desktop app opens this as a full surface
69
+ * and needs a way out; a page-embedded copy has the page's own chrome. */
70
+ closable?: boolean;
71
+ };
72
+
73
+ declare type __VLS_Props_14 = {
74
+ fields: StorageField[];
75
+ modelValue: Record<string, unknown>;
76
+ locale: LocaleCode;
77
+ /** Keys to mark as failing validation (missing required). */
78
+ invalid?: string[];
79
+ disabled?: boolean;
80
+ };
81
+
82
+ declare type __VLS_Props_15 = {
83
+ guide: ProtocolGuide;
84
+ locale: LocaleCode;
85
+ };
86
+
87
+ declare type __VLS_Props_16 = {
88
+ config: ExplorerConfig;
89
+ /** Storage names the caller may see, for the confinement picker. */
90
+ storages: string[];
91
+ };
92
+
93
+ declare type __VLS_Props_17 = {
94
+ config: ExplorerConfig;
95
+ /**
96
+ * Draw the key list and the paste box.
97
+ *
98
+ * ⚠ False for FTPS, which authenticates with a password or an API token and
99
+ * has no use for an SSH key — the panel is still MOUNTED there because it is
100
+ * the call that reports where the FTP endpoint listens and under what login
101
+ * name. Showing a key box on the FTPS page would invite somebody to register
102
+ * a key that protocol will never look at.
103
+ */
104
+ keysVisible?: boolean;
105
+ };
106
+
107
+ declare type __VLS_Props_18 = {
108
+ config: ExplorerConfig;
109
+ /** Storage names the caller may see, for the confinement picker. */
110
+ storages: string[];
111
+ };
112
+
113
+ declare type __VLS_Props_19 = {
114
+ config: ExplorerConfig;
115
+ /**
116
+ * Which protocol the surrounding guide is showing. It only changes the
117
+ * default label — a token minted here works on all of them, and pretending
118
+ * otherwise would have people mint one per protocol.
119
+ */
120
+ protocol?: string;
121
+ };
122
+
63
123
  declare type __VLS_Props_2 = {
64
124
  open: boolean;
65
125
  locale: LocaleCode;
@@ -178,6 +238,45 @@ declare type __VLS_Props_9 = {
178
238
  narrow?: boolean;
179
239
  };
180
240
 
241
+ /**
242
+ * The self-service API-token surface (`/api/tokens`).
243
+ *
244
+ * ⚠ Why this exists at all: three of the six protocols filex serves take an
245
+ * API token as their password — FTPS, WebDAV and `filex mount`. Until
246
+ * 2026-08-17 the only place to mint one was `/api/admin/ai-tokens`, which is
247
+ * admin-only, so a normal user opened the FTPS guide, read "use an API token
248
+ * as the password", and had nowhere to get one. The backend route has always
249
+ * been open to every account; only the UI was missing.
250
+ */
251
+ /** One token row. The secret is NEVER in here — only the hash is stored. */
252
+ export declare interface ApiToken {
253
+ id: number;
254
+ label: string;
255
+ scopes: string;
256
+ /** Per-token display identities for the audit trail; may be absent. */
257
+ usernames?: string | null;
258
+ created_at?: string;
259
+ last_used_at?: string | null;
260
+ expires_at?: string | null;
261
+ }
262
+
263
+ /** What comes back from a mint. `token` is shown exactly once. */
264
+ export declare interface ApiTokenCreated {
265
+ token: string;
266
+ row: ApiToken;
267
+ }
268
+
269
+ export declare interface ApiTokenRequest {
270
+ label: string;
271
+ /**
272
+ * Comma-separated verbs. The server CAPS this against the caller's role and
273
+ * their own grants, so asking for more than you have is refused rather than
274
+ * quietly granted — never send `admin`, it is rejected outright here.
275
+ */
276
+ scopes?: string;
277
+ expires_in_days?: number;
278
+ }
279
+
181
280
  /**
182
281
  * Set (or clear, for the default theme) the selected theme's tokens as
183
282
  * inline CSS variables on the explorer root element, resolved to the
@@ -245,6 +344,42 @@ export declare type AuthConfig = {
245
344
  csrf: string;
246
345
  };
247
346
 
347
+ /**
348
+ * Every command here was RUN against the endpoint on 2026-08-16 with curl
349
+ * 8.5, lftp 4.9 and rclone 1.73 — including the settings each of them needs to
350
+ * negotiate explicit TLS, which is the half of FTPS that goes wrong quietly.
351
+ */
352
+ export declare const buildFtpsGuide: GuideBuilder;
353
+
354
+ export declare function buildGuide(protocol: string, ctx: GuideContext, t: Translate): ProtocolGuide | null;
355
+
356
+ /**
357
+ * Every command here was RUN against the endpoint on 2026-08-16 with the LINUX
358
+ * KERNEL's own NFSv3 client — including the two options a mount fails silently
359
+ * without (`port=` and `mountport=`, because filex serves both on one port and
360
+ * runs no portmapper).
361
+ */
362
+ export declare const buildNfsGuide: GuideBuilder;
363
+
364
+ /**
365
+ * Every command here was RUN against the endpoint on 2026-08-16, not copied
366
+ * from a vendor page: rclone 1.73.5, aws-cli 2.11.6, restic 0.19.1, mc
367
+ * 2025-08-13 and s3fs 1.93 each did a full round trip, and the flags below are
368
+ * the ones those runs actually needed. Five of the endpoint's bugs were found
369
+ * that way — a guide assembled from documentation would have shipped them.
370
+ */
371
+ export declare const buildS3Guide: GuideBuilder;
372
+
373
+ /**
374
+ * Every command here was RUN against the endpoint on 2026-08-16 with OpenSSH
375
+ * 9.6 and rclone 1.73 — including the two settings rclone needs because filex
376
+ * has no shell, which are the difference between a clean run and a pile of
377
+ * warnings about a missing `md5sum`.
378
+ */
379
+ export declare const buildSftpGuide: GuideBuilder;
380
+
381
+ export declare const buildWebdavGuide: GuideBuilder;
382
+
248
383
  export declare interface Capabilities {
249
384
  ffmpeg?: boolean;
250
385
  ghostscript?: boolean;
@@ -261,6 +396,8 @@ export declare interface Capabilities {
261
396
  };
262
397
  }
263
398
 
399
+ export declare function clearResume(store: ResumeStorage | null, key: string): void;
400
+
264
401
  export declare interface ClipboardState {
265
402
  mode: 'cut' | 'copy' | null;
266
403
  items: FileNode[];
@@ -274,6 +411,53 @@ export declare interface ClipboardState {
274
411
  */
275
412
  export declare function comboFromEvent(e: KeyboardEvent): string | null;
276
413
 
414
+ export declare const ConnectionGuideView: DefineComponent<__VLS_Props_15, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, PublicProps, Readonly<__VLS_Props_15> & Readonly<{}>, {}, {}, {}, {}, string, ComponentProvideOptions, false, {}, HTMLDivElement>;
415
+
416
+ export declare type ConnectionsApi = ReturnType<typeof useConnections>;
417
+
418
+ /**
419
+ * The URL prefix the `/api/...` routes hang off.
420
+ *
421
+ * `apiBase: ''` is a legitimate value (the admin SPA is same-origin), so
422
+ * only `undefined`/`null` means "not given" — a falsy check would send the
423
+ * SPA's requests to the wrong place. Legacy embedders configure `endpoint`
424
+ * instead of `apiBase`; the prefix is recovered from it so they are not
425
+ * excluded.
426
+ */
427
+ export declare function connectionsBase(config: ExplorerConfig): string;
428
+
429
+ /**
430
+ * The origin a client program should be pointed at.
431
+ *
432
+ * The instruction pages are only worth anything if they name the real
433
+ * deployment, so this resolves to an absolute origin: the configured
434
+ * apiBase when there is one (the desktop app, embeds), otherwise the page's
435
+ * own origin (the admin SPA, which is served by the same binary).
436
+ */
437
+ export declare function connectionsOrigin(config: ExplorerConfig): string;
438
+
439
+ export declare const ConnectionsPanel: DefineComponent<__VLS_Props_13, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
440
+ error: (err: {
441
+ message: string;
442
+ }) => any;
443
+ close: () => any;
444
+ changed: () => any;
445
+ }, string, PublicProps, Readonly<__VLS_Props_13> & Readonly<{
446
+ onError?: ((err: {
447
+ message: string;
448
+ }) => any) | undefined;
449
+ onClose?: (() => any) | undefined;
450
+ onChanged?: (() => any) | undefined;
451
+ }>, {}, {}, {}, {}, string, ComponentProvideOptions, false, {}, HTMLDivElement>;
452
+
453
+ /** The signed-in account, from `GET /api/auth/me`. */
454
+ export declare interface ConnectionsUser {
455
+ id: number;
456
+ email: string;
457
+ display_name?: string;
458
+ role?: string;
459
+ }
460
+
277
461
  declare interface ContextAction {
278
462
  key: string;
279
463
  label: string;
@@ -312,6 +496,11 @@ export declare function decryptFile(kek: CryptoKey, data: ArrayBuffer): Promise<
312
496
 
313
497
  export declare const DEFAULT_THEME_ID = "default";
314
498
 
499
+ /** The default backing store, or null where the platform has none. Reading
500
+ * localStorage throws in a partitioned/blocked context, so the probe is a real
501
+ * try, not a typeof check. */
502
+ export declare function defaultResumeStorage(): ResumeStorage | null;
503
+
315
504
  declare type Density = 'comfortable' | 'compact';
316
505
 
317
506
  /**
@@ -406,6 +595,15 @@ export declare function encryptFile(kek: CryptoKey, content: ArrayBuffer): Promi
406
595
  */
407
596
  export declare interface EndpointMap {
408
597
  manager: string;
598
+ /**
599
+ * Staged upload — the chunked, resumable, driver-agnostic path every client
600
+ * speaks (docs/UPLOADS.md). The per-upload routes (`PUT/GET/DELETE {id}`,
601
+ * `POST {id}/commit`) are derived from it by stripping `/begin`, so one
602
+ * override moves the whole protocol.
603
+ */
604
+ uploadBegin: string | null;
605
+ /** Legacy S3-presigned chunked upload. Still served by the backend for
606
+ * older embedders; nothing in this package calls it. */
409
607
  uploadInit: string | null;
410
608
  uploadFinalize: string | null;
411
609
  uploadAbort: string | null;
@@ -448,6 +646,8 @@ export declare interface ExplorerConfig {
448
646
  apiBase?: string;
449
647
  /** Legacy main endpoint (Vuefinder-compat: GET/POST `?q=…`). */
450
648
  endpoint?: string;
649
+ /** Staged upload entry point; the `{id}` routes hang off it. */
650
+ uploadBegin?: string;
451
651
  uploadInit?: string;
452
652
  uploadFinalize?: string;
453
653
  uploadAbort?: string;
@@ -538,9 +738,19 @@ export declare interface ExplorerConfig {
538
738
  * embed). Defaults to `/files/viewer`.
539
739
  */
540
740
  viewerBaseUrl?: string;
541
- /** Upload chunk size (bytes). Default 5 MB. */
741
+ /**
742
+ * Upload chunk size (bytes). Default 8 MB — the server's default too, so
743
+ * "large enough to chunk" means the same thing on both ends. The value the
744
+ * server returns from `begin` is binding; this is what the client asks for
745
+ * and the threshold above which a file goes on the staged path at all.
746
+ */
542
747
  chunkSize?: number;
543
- /** Parallel chunks. Default 4. */
748
+ /**
749
+ * @deprecated Ignored since the move to the staged protocol. Chunks are sent
750
+ * sequentially because `offset` — the resume point — is the contiguous run
751
+ * from part 1; parallel parts would leave holes that a resumed upload has to
752
+ * re-send anyway.
753
+ */
544
754
  parallelChunks?: number;
545
755
  /** Theme. */
546
756
  theme?: ThemeMode;
@@ -1064,6 +1274,22 @@ export declare interface FileNode {
1064
1274
  */
1065
1275
  export declare function findShortcutConflict(combo: string, excludeId: string): ShortcutConflict | null;
1066
1276
 
1277
+ /** What an FTP client needs to be told, computed on the server. */
1278
+ declare interface FTPSFacts {
1279
+ enabled: boolean;
1280
+ host: string;
1281
+ port: number;
1282
+ /**
1283
+ * ⚠ The passive data-port range. A firewall that blocks it makes every
1284
+ * transfer HANG with no error on either side — the classic FTP failure, and
1285
+ * impossible to diagnose from the client end. It belongs in the guide.
1286
+ */
1287
+ pasv_min: number;
1288
+ pasv_max: number;
1289
+ /** True when no certificate was configured and the server generated one. */
1290
+ self_signed: boolean;
1291
+ }
1292
+
1067
1293
  /**
1068
1294
  * Generate a stylesheet that mirrors styles/variables.css' selector
1069
1295
  * cascade 1:1 (light base → explicit-dark selectors → prefers-dark media
@@ -1120,9 +1346,112 @@ declare interface Grant {
1120
1346
  inherited?: boolean;
1121
1347
  }
1122
1348
 
1349
+ /**
1350
+ * Protocol id → builder.
1351
+ *
1352
+ * S3 and SFTP land here as one entry each once their servers exist; the
1353
+ * panel, the copy buttons, the client tabs and the i18n plumbing are
1354
+ * already written for them. That is the whole point of the shape.
1355
+ */
1356
+ export declare const GUIDE_BUILDERS: Record<string, GuideBuilder>;
1357
+
1358
+ export declare interface GuideBlock {
1359
+ kind: GuideBlockKind;
1360
+ /** Prose for `text` / `note` / `warn`. */
1361
+ text?: string;
1362
+ /** Ordered instructions for `steps`. */
1363
+ steps?: string[];
1364
+ /** Copyable payload for `code`. */
1365
+ code?: string;
1366
+ /** Syntax hint / file name shown above a code block. */
1367
+ caption?: string;
1368
+ }
1369
+
1370
+ export declare type GuideBlockKind = 'text' | 'steps' | 'code' | 'note' | 'warn';
1371
+
1372
+ export declare type GuideBuilder = (ctx: GuideContext, t: Translate) => ProtocolGuide;
1373
+
1374
+ export declare interface GuideClient {
1375
+ id: string;
1376
+ /** Product name — never translated ("Cyberduck" is Cyberduck). */
1377
+ name: string;
1378
+ platform: 'windows' | 'macos' | 'linux' | 'any';
1379
+ blocks: GuideBlock[];
1380
+ }
1381
+
1382
+ /** Everything a guide is allowed to know about the deployment. */
1383
+ export declare interface GuideContext {
1384
+ /** Absolute origin of the server, e.g. `https://fm.example.com`. */
1385
+ origin: string;
1386
+ /** The caller's own account e-mail — the username on every protocol. */
1387
+ user: string;
1388
+ /** Storage names the caller may see. */
1389
+ storages: string[];
1390
+ /** The storage the page is currently focused on, when any. */
1391
+ storage?: string;
1392
+ /**
1393
+ * The S3 endpoint, as the server computes it.
1394
+ *
1395
+ * ⚠ NOT derived from `origin` here. With a dedicated host the endpoint is
1396
+ * that host's root; without one it lives under `/s3`, and a client pointed
1397
+ * at the application root reaches the web app — rclone reported
1398
+ * "XML syntax error on line 10" against an HTML redirect page. The server
1399
+ * returns this with every key, and the guide repeats it verbatim.
1400
+ */
1401
+ s3Endpoint?: string;
1402
+ /** True when clients must be told to force path-style addressing. */
1403
+ s3PathStyle?: boolean;
1404
+ /** The access key id to write into the examples, when the caller has one. */
1405
+ s3AccessKeyID?: string;
1406
+ /** The freshly minted secret, shown only while the user is looking at it. */
1407
+ s3Secret?: string;
1408
+ /** The SFTP endpoint, as the server reports it. */
1409
+ sftpHost?: string;
1410
+ sftpPort?: number;
1411
+ /** False when the operator has the endpoint switched off. */
1412
+ sftpEnabled?: boolean;
1413
+ /** The login name a client must use — the username when there is one. */
1414
+ sftpLogin?: string;
1415
+ /** True when the account has at least one usable key registered. */
1416
+ sftpHasKey?: boolean;
1417
+ /** The FTPS endpoint, as the server reports it. */
1418
+ ftpsHost?: string;
1419
+ ftpsPort?: number;
1420
+ ftpsEnabled?: boolean;
1421
+ /** The passive data-port range, which the client's firewall must allow. */
1422
+ ftpsPasvMin?: number;
1423
+ ftpsPasvMax?: number;
1424
+ /** True when the server is using a self-signed certificate. */
1425
+ ftpsSelfSigned?: boolean;
1426
+ /** The NFS endpoint, as the server reports it. */
1427
+ nfsHost?: string;
1428
+ nfsPort?: number;
1429
+ nfsEnabled?: boolean;
1430
+ /** The freshly minted export path — the credential, shown once. */
1431
+ nfsPath?: string;
1432
+ /** True when the active export refuses writes. */
1433
+ nfsReadOnly?: boolean;
1434
+ }
1435
+
1436
+ /** One connection fact, rendered as a copyable row. */
1437
+ export declare interface GuideFact {
1438
+ label: string;
1439
+ value: string;
1440
+ hint?: string;
1441
+ /** Rendered as a hint rather than a value — filex never has the
1442
+ * plaintext of your password and must not pretend otherwise. */
1443
+ placeholderOnly?: boolean;
1444
+ }
1445
+
1446
+ /** Protocol ids that have a guide, in display order. */
1447
+ export declare function guideProtocols(): string[];
1448
+
1123
1449
  /** True when the buffer starts with the 'filexe2e' magic. */
1124
1450
  export declare function hasMagic(buf: ArrayBuffer | Uint8Array): boolean;
1125
1451
 
1452
+ /** `https://fm.example.com` → `fm.example.com`; anything unparseable comes back whole. */
1453
+ export declare function hostOf(origin: string): string;
1454
+
1126
1455
  declare interface InviteResponse {
1127
1456
  mode: 'granted' | 'user_created' | 'shared';
1128
1457
  user_id?: number;
@@ -1138,8 +1467,31 @@ declare interface InviteResponse {
1138
1467
  */
1139
1468
  export declare function isExternalUsable(s: ExternalServiceStatus | undefined): boolean;
1140
1469
 
1470
+ /** True when the deployment is not on TLS — several clients refuse that,
1471
+ * and Windows refuses it silently, which is worse. */
1472
+ export declare function isPlainHttp(origin: string): boolean;
1473
+
1474
+ /** True when the staged protocol is simply not there. */
1475
+ export declare function isStagedUnsupported(err: unknown): boolean;
1476
+
1477
+ /** Every unfinished upload this browser knows about, newest first. Surfaces
1478
+ * use it to say "you have an unfinished upload" instead of forgetting. */
1479
+ export declare function listResume(store: ResumeStorage | null, now?: number): ResumeRecord[];
1480
+
1481
+ /** The record for this exact (destination, file), or null. */
1482
+ export declare function loadResume(store: ResumeStorage | null, key: string, now?: number): ResumeRecord | null;
1483
+
1141
1484
  export declare type LocaleCode = 'tr' | 'en';
1142
1485
 
1486
+ /**
1487
+ * Why the caller cannot manage storages, when they cannot.
1488
+ *
1489
+ * `none` is the honest answer for a signed-in non-admin: the surface then
1490
+ * shows the connection guides (which every user needs) and a plain "ask
1491
+ * your administrator" line instead of a form that would 403 on submit.
1492
+ */
1493
+ export declare type ManageDenial = 'none' | 'anonymous' | 'unreachable';
1494
+
1143
1495
  export declare interface ManagerResponse {
1144
1496
  adapter: string;
1145
1497
  storages: string[];
@@ -1158,6 +1510,71 @@ export declare function matchedInContent(matched: unknown): boolean;
1158
1510
 
1159
1511
  export declare const messages: Record<LocaleCode, Record<string, string>>;
1160
1512
 
1513
+ /** Where an NFS client should point, reported by the server. */
1514
+ export declare interface NFSConnection {
1515
+ /** The FILEX_NFS kill switch. */
1516
+ enabled: boolean;
1517
+ host: string;
1518
+ /**
1519
+ * ⚠ 2049 by convention, but filex serves mount AND nfs on this one port and
1520
+ * runs no portmapper — so a client needs `port=` and `mountport=` both set to
1521
+ * it. A guide that omitted either produces a mount that hangs.
1522
+ */
1523
+ port: number;
1524
+ }
1525
+
1526
+ /**
1527
+ * NFS exports, as the frontend sees them.
1528
+ *
1529
+ * ⚠⚠ The `path` a freshly minted export returns IS the credential. NFSv3 cannot
1530
+ * authenticate a request without Kerberos, so filex binds the identity to the
1531
+ * export path instead: 32 bytes of entropy, shown once, stored hashed. Whoever
1532
+ * knows it can mount as that account — which is why it belongs in a config file
1533
+ * treated like a password, and why the UI has to say so rather than letting
1534
+ * somebody discover it from a mount table.
1535
+ */
1536
+ export declare interface NFSExport {
1537
+ id: number;
1538
+ user_id: number;
1539
+ api_token_id?: number | null;
1540
+ label: string;
1541
+ /** Confinement, in the same shape the S3 keys use. */
1542
+ storage_name?: string;
1543
+ prefix?: string;
1544
+ /** Refuses every write through this mount, whatever the account may do. */
1545
+ read_only: boolean;
1546
+ /** Comma-separated CIDR allow-list. Empty = any address the listener takes. */
1547
+ allow_cidrs?: string;
1548
+ created_at: string;
1549
+ last_used_at?: string | null;
1550
+ expires_at?: string | null;
1551
+ disabled_at?: string | null;
1552
+ }
1553
+
1554
+ /** What minting returns. The path appears exactly once. */
1555
+ export declare interface NFSExportCreated extends NFSConnection {
1556
+ export: NFSExport;
1557
+ path: string;
1558
+ }
1559
+
1560
+ export declare const NFSExportsPanel: DefineComponent<__VLS_Props_18, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
1561
+ active: (v: {
1562
+ host: string;
1563
+ port: number;
1564
+ enabled: boolean;
1565
+ path?: string;
1566
+ readOnly: boolean;
1567
+ }) => any;
1568
+ }, string, PublicProps, Readonly<__VLS_Props_18> & Readonly<{
1569
+ onActive?: ((v: {
1570
+ host: string;
1571
+ port: number;
1572
+ enabled: boolean;
1573
+ path?: string;
1574
+ readOnly: boolean;
1575
+ }) => any) | undefined;
1576
+ }>, {}, {}, {}, {}, string, ComponentProvideOptions, false, {}, HTMLElement>;
1577
+
1161
1578
  /** Mirrors backend `model.NodeComment` (GET /api/files/comments?node_id=…). */
1162
1579
  declare interface NodeComment {
1163
1580
  id: number;
@@ -1350,6 +1767,20 @@ monacoEl: HTMLDivElement;
1350
1767
  officeEl: HTMLDivElement;
1351
1768
  }, any>;
1352
1769
 
1770
+ export declare interface ProtocolGuide {
1771
+ id: string;
1772
+ /** Protocol name — a wire protocol is not translated either. */
1773
+ name: string;
1774
+ summary: string;
1775
+ /** Server + credential, filled in from this deployment. */
1776
+ facts: GuideFact[];
1777
+ clients: GuideClient[];
1778
+ notes: GuideBlock[];
1779
+ }
1780
+
1781
+ /** Drop records older than RESUME_TTL_MS. Returns what survived. */
1782
+ export declare function pruneResume(store: ResumeStorage | null, now?: number): Store;
1783
+
1353
1784
  export declare const QuickLook: DefineComponent<__VLS_Props_8, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
1354
1785
  close: () => any;
1355
1786
  nav: (delta: number) => any;
@@ -1401,6 +1832,106 @@ declare interface ResolveEmailResponse {
1401
1832
  */
1402
1833
  export declare function resolveEndpoints(config: ExplorerConfig): EndpointMap;
1403
1834
 
1835
+ /** How long an unfinished record is worth keeping. The server sweeps its own
1836
+ * staging after FILEX_UPLOAD_STAGING_TTL (24 h by default); a note that
1837
+ * outlives the bytes it describes only produces a confusing "resuming…" that
1838
+ * immediately restarts. */
1839
+ export declare const RESUME_TTL_MS: number;
1840
+
1841
+ export declare interface ResumeRecord {
1842
+ /** Server-side staged upload id. */
1843
+ uploadId: string;
1844
+ /** Destination directory, qualified (`adapter://sub/dir`). */
1845
+ path: string;
1846
+ name: string;
1847
+ size: number;
1848
+ /** File.lastModified — part of the identity, not metadata. */
1849
+ lastModified: number;
1850
+ chunkSize: number;
1851
+ /** Last offset the server acknowledged. Display + triage only. */
1852
+ offset: number;
1853
+ updatedAt: number;
1854
+ }
1855
+
1856
+ /** A Storage-shaped thing. Injected so tests (and any surface without
1857
+ * localStorage — SSR, a locked-down embed) do not have to fake globals. */
1858
+ export declare interface ResumeStorage {
1859
+ getItem(key: string): string | null;
1860
+ setItem(key: string, value: string): void;
1861
+ removeItem(key: string): void;
1862
+ }
1863
+
1864
+ /**
1865
+ * The S3 access-key surface, as the frontend sees it.
1866
+ *
1867
+ * Mirrors `model.S3AccessKey` and the connection facts the key endpoints
1868
+ * return alongside it. The facts travel WITH the keys on purpose: a key
1869
+ * without an endpoint is not a usable credential, and a UI that assembled
1870
+ * the URL itself would be a second place to get it wrong (the application
1871
+ * URL and the S3 endpoint are different hosts whenever one is configured).
1872
+ */
1873
+ export declare interface S3AccessKey {
1874
+ id: number;
1875
+ access_key_id: string;
1876
+ user_id: number;
1877
+ api_token_id?: number | null;
1878
+ label: string;
1879
+ /** Optional confinement, in the shape S3 clients already understand. */
1880
+ bucket?: string;
1881
+ prefix?: string;
1882
+ created_at: string;
1883
+ last_used_at?: string | null;
1884
+ expires_at?: string | null;
1885
+ /** Set = the key is switched off but still in the audit trail. */
1886
+ disabled_at?: string | null;
1887
+ }
1888
+
1889
+ /** Where to point a client, and how to address it. */
1890
+ export declare interface S3Connection {
1891
+ /** Absolute endpoint URL — the S3 host's root, or `<app>/s3`. */
1892
+ endpoint: string;
1893
+ /** The FILEX_S3 kill switch. False = the operator turned the endpoint off. */
1894
+ enabled: boolean;
1895
+ /**
1896
+ * True when there is no dedicated host, so clients must be told to force
1897
+ * path-style addressing. A current SDK defaults to virtual-hosted and then
1898
+ * fails at DNS with an error that names neither filex nor the cause.
1899
+ */
1900
+ path_style: boolean;
1901
+ }
1902
+
1903
+ /** What a freshly minted key returns. The secret appears exactly once. */
1904
+ export declare interface S3KeyCreated extends S3Connection {
1905
+ key: S3AccessKey;
1906
+ secret: string;
1907
+ }
1908
+
1909
+ /** What the caller asked for when minting. */
1910
+ export declare interface S3KeyRequest {
1911
+ label: string;
1912
+ /** Mint FROM an API token, inheriting its scopes, confinement and expiry. */
1913
+ api_token_id?: number;
1914
+ bucket?: string;
1915
+ prefix?: string;
1916
+ expires_at?: string;
1917
+ }
1918
+
1919
+ export declare const S3KeysPanel: DefineComponent<__VLS_Props_16, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
1920
+ active: (v: {
1921
+ accessKeyID: string;
1922
+ secret?: string;
1923
+ endpoint: string;
1924
+ pathStyle: boolean;
1925
+ }) => any;
1926
+ }, string, PublicProps, Readonly<__VLS_Props_16> & Readonly<{
1927
+ onActive?: ((v: {
1928
+ accessKeyID: string;
1929
+ secret?: string;
1930
+ endpoint: string;
1931
+ pathStyle: boolean;
1932
+ }) => any) | undefined;
1933
+ }>, {}, {}, {}, {}, string, ComponentProvideOptions, false, {}, HTMLElement>;
1934
+
1404
1935
  /** Matched-in values the search contract allows on a hit. */
1405
1936
  export declare type SearchMatched = 'name' | 'content' | 'both';
1406
1937
 
@@ -1555,6 +2086,64 @@ export declare interface SnippetSegment {
1555
2086
  */
1556
2087
  export declare function snippetSegments(snippet: string): SnippetSegment[];
1557
2088
 
2089
+ /** Where an SSH client should connect, and as whom. */
2090
+ export declare interface SSHConnection {
2091
+ /** The FILEX_SFTP kill switch. False = the operator turned the port off. */
2092
+ enabled: boolean;
2093
+ host: string;
2094
+ /**
2095
+ * ⚠ Not 22 and not the web port. SFTP is raw TCP on a port of its own, and
2096
+ * a guide that printed 443 would send every client at a proxy that speaks
2097
+ * only HTTP.
2098
+ */
2099
+ port: number;
2100
+ /** The login name: the account's username, or its e-mail when it has none. */
2101
+ login: string;
2102
+ /** The FTPS endpoint, reported by the same call. */
2103
+ ftps?: FTPSFacts;
2104
+ }
2105
+
2106
+ export declare const SSHKeysPanel: DefineComponent<__VLS_Props_17, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
2107
+ active: (v: {
2108
+ host: string;
2109
+ port: number;
2110
+ login: string;
2111
+ enabled: boolean;
2112
+ hasKey: boolean;
2113
+ ftps?: FTPSFacts;
2114
+ }) => any;
2115
+ }, string, PublicProps, Readonly<__VLS_Props_17> & Readonly<{
2116
+ onActive?: ((v: {
2117
+ host: string;
2118
+ port: number;
2119
+ login: string;
2120
+ enabled: boolean;
2121
+ hasKey: boolean;
2122
+ ftps?: FTPSFacts;
2123
+ }) => any) | undefined;
2124
+ }>, {}, {}, {}, {}, string, ComponentProvideOptions, false, {}, any>;
2125
+
2126
+ /**
2127
+ * The SSH key surface, as the frontend sees it.
2128
+ *
2129
+ * Mirrors `model.SSHPublicKey` plus the connection facts the endpoint returns
2130
+ * with it — the host, the port and the LOGIN NAME, which is the piece people
2131
+ * most often get wrong when they type it themselves.
2132
+ */
2133
+ export declare interface SSHPublicKey {
2134
+ id: number;
2135
+ user_id: number;
2136
+ name: string;
2137
+ /** SHA256 fingerprint, base64, without the `SHA256:` prefix. */
2138
+ fingerprint: string;
2139
+ /** The normalised wire form, `<type> <base64>`. */
2140
+ public_key: string;
2141
+ created_at: string;
2142
+ last_used_at?: string | null;
2143
+ /** Set = the key is switched off but still listed. */
2144
+ disabled_at?: string | null;
2145
+ }
2146
+
1558
2147
  export declare const StarButton: DefineComponent<__VLS_Props_3, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
1559
2148
  error: (message: string) => any;
1560
2149
  change: (value: boolean) => any;
@@ -1563,6 +2152,122 @@ onError?: ((message: string) => any) | undefined;
1563
2152
  onChange?: ((value: boolean) => any) | undefined;
1564
2153
  }>, {}, {}, {}, {}, string, ComponentProvideOptions, false, {}, HTMLButtonElement>;
1565
2154
 
2155
+ export declare interface StorageDriverCapabilities {
2156
+ read?: boolean;
2157
+ write?: boolean;
2158
+ move?: boolean;
2159
+ copy?: boolean;
2160
+ delete?: boolean;
2161
+ mkdir?: boolean;
2162
+ presign?: boolean;
2163
+ watch?: boolean;
2164
+ }
2165
+
2166
+ export declare interface StorageDriverDescriptor {
2167
+ driver: string;
2168
+ label: string;
2169
+ i18n_key: string;
2170
+ fields: StorageField[];
2171
+ capabilities: StorageDriverCapabilities;
2172
+ }
2173
+
2174
+ /** One config key of a storage driver, as declared by the driver itself. */
2175
+ export declare interface StorageField {
2176
+ key: string;
2177
+ type: StorageFieldType;
2178
+ /** English fallback label — used only when `i18n_key` is missing from
2179
+ * the catalogue, so a driver released after this build still renders
2180
+ * readable labels instead of raw keys. */
2181
+ label: string;
2182
+ help?: string;
2183
+ i18n_key: string;
2184
+ help_i18n_key?: string;
2185
+ required: boolean;
2186
+ /** Credential material: render masked, never log, never put in a URL. */
2187
+ secret: boolean;
2188
+ default?: unknown;
2189
+ placeholder?: string;
2190
+ options?: StorageFieldOption[];
2191
+ min?: number;
2192
+ max?: number;
2193
+ monospace?: boolean;
2194
+ multiline?: boolean;
2195
+ advanced?: boolean;
2196
+ /** THE field that scopes the storage inside the backend (s3 prefix,
2197
+ * local path, sftp/ftp/webdav root). The backend rejects an empty or
2198
+ * "/" value with ROOT_PATH_FORBIDDEN. */
2199
+ root?: boolean;
2200
+ /** Legacy spellings the driver still reads for this field. */
2201
+ aliases?: string[];
2202
+ }
2203
+
2204
+ export declare interface StorageFieldOption {
2205
+ value: string;
2206
+ /** English fallback; `i18n_key` wins when the catalogue has it. */
2207
+ label: string;
2208
+ i18n_key?: string;
2209
+ }
2210
+
2211
+ export declare const StorageFields: DefineComponent<__VLS_Props_14, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
2212
+ "update:modelValue": (v: Record<string, unknown>) => any;
2213
+ }, string, PublicProps, Readonly<__VLS_Props_14> & Readonly<{
2214
+ "onUpdate:modelValue"?: ((v: Record<string, unknown>) => any) | undefined;
2215
+ }>, {}, {}, {}, {}, string, ComponentProvideOptions, false, {}, HTMLDivElement>;
2216
+
2217
+ /**
2218
+ * Connections — the shapes the storage-connection surface works with.
2219
+ *
2220
+ * Every one of these mirrors something the BACKEND declares, on purpose.
2221
+ * `StorageField` / `StorageDriverDescriptor` are the wire form of
2222
+ * `backend/internal/storage/descriptor.go`, served by
2223
+ * `GET /api/admin/storage-drivers`: a driver states its own config keys,
2224
+ * their types, which one is the storage root and which hold credentials,
2225
+ * and every surface renders that one declaration.
2226
+ *
2227
+ * The alternative — a form per surface — is exactly what shipped broken:
2228
+ * three of the four drivers the admin UI offered could not be created
2229
+ * through it because the form collected keys the backend never read. A
2230
+ * second hand-written form in the desktop app would have been a fourth
2231
+ * copy of the same mistake.
2232
+ */
2233
+ export declare type StorageFieldType = 'string' | 'int' | 'bool' | 'password' | 'select';
2234
+
2235
+ /** A configured storage, as `GET /api/admin/storages` returns it. */
2236
+ export declare interface StorageRow {
2237
+ id: number;
2238
+ name: string;
2239
+ driver: string;
2240
+ enabled: boolean;
2241
+ read_only: boolean;
2242
+ config: Record<string, unknown>;
2243
+ rbac_enabled?: boolean;
2244
+ sync_mode?: string;
2245
+ created_at?: string;
2246
+ updated_at?: string;
2247
+ stats?: {
2248
+ file_count?: number;
2249
+ total_size_bytes?: number;
2250
+ };
2251
+ }
2252
+
2253
+ export declare interface StorageTestResult {
2254
+ ok: boolean;
2255
+ error?: string;
2256
+ object_count?: number;
2257
+ sample_listing?: unknown[];
2258
+ }
2259
+
2260
+ /** POST/PATCH body for a storage. */
2261
+ export declare interface StorageWrite {
2262
+ name: string;
2263
+ driver: string;
2264
+ config: Record<string, unknown>;
2265
+ read_only?: boolean;
2266
+ enabled?: boolean;
2267
+ }
2268
+
2269
+ declare type Store = Record<string, ResumeRecord>;
2270
+
1566
2271
  /**
1567
2272
  * Create/update/empty the singleton `<style data-filex-theme>` element in
1568
2273
  * <head>. Idempotent — safe to call from every explorer instance.
@@ -1662,9 +2367,43 @@ export declare const THEMES: ThemeDef[];
1662
2367
  /** Map of `--fe-*` custom property → value. */
1663
2368
  export declare type ThemeTokenMap = Record<string, string>;
1664
2369
 
2370
+ export declare const TokensPanel: DefineComponent<__VLS_Props_19, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
2371
+ active: (v: {
2372
+ hasToken: boolean;
2373
+ }) => any;
2374
+ }, string, PublicProps, Readonly<__VLS_Props_19> & Readonly<{
2375
+ onActive?: ((v: {
2376
+ hasToken: boolean;
2377
+ }) => any) | undefined;
2378
+ }>, {}, {}, {}, {}, string, ComponentProvideOptions, false, {}, HTMLElement>;
2379
+
1665
2380
  /** Turkish UI strings. */
1666
2381
  export declare const tr: Record<string, string>;
1667
2382
 
2383
+ /**
2384
+ * connectionGuides — "how do I connect to this thing?", generated from the
2385
+ * live deployment instead of written as prose.
2386
+ *
2387
+ * A published document says `https://fm.example.com/dav/` and leaves the
2388
+ * reader to substitute three values, one of which (their own username) they
2389
+ * usually get wrong. What ships here is the real host, the real storage
2390
+ * name and the caller's own credential, with a copy button — which is the
2391
+ * difference between a doc and a working paste. No surveyed product
2392
+ * (Backblaze, R2, Storj, MinIO, Garage) does this; it is the cheapest win
2393
+ * on the whole surface.
2394
+ *
2395
+ * Shape of the thing: a protocol is a `GuideBuilder` registered in
2396
+ * `GUIDE_BUILDERS`. WebDAV is built here because WebDAV is what filex
2397
+ * serves today; S3 and SFTP are a builder each and a registry line when
2398
+ * their servers land — no second implementation, no second UI.
2399
+ *
2400
+ * The commands are not invented. They are the ones verified on 2026-08-14
2401
+ * and written down in `docs/WEBDAV.md` and §13.5 of the write-path
2402
+ * handover, including the three Windows registry limits that otherwise
2403
+ * look exactly like filex bugs.
2404
+ */
2405
+ export declare type Translate = (key: string, vars?: Record<string, string | number>) => string;
2406
+
1668
2407
  /** A soft-deleted node as returned by the filex trash listing endpoint. */
1669
2408
  declare interface TrashEntry {
1670
2409
  id: number;
@@ -1683,6 +2422,13 @@ export declare interface UploadFinalizeResponse {
1683
2422
  url?: string;
1684
2423
  }
1685
2424
 
2425
+ /** Identity of one (destination, file) pair. */
2426
+ export declare function uploadFingerprint(path: string, file: {
2427
+ name: string;
2428
+ size: number;
2429
+ lastModified?: number;
2430
+ }): string;
2431
+
1686
2432
  export declare interface UploadInitResponse {
1687
2433
  uploadId: string;
1688
2434
  parts: Array<{
@@ -1697,11 +2443,22 @@ export declare interface UploadJob {
1697
2443
  id: string;
1698
2444
  file: File;
1699
2445
  path: string;
2446
+ /** Server-side staged upload id, once `begin` has answered. */
1700
2447
  uploadId?: string;
1701
2448
  totalBytes: number;
2449
+ /** Bytes filex has acknowledged (plus the chunk currently in flight). */
1702
2450
  uploadedBytes: number;
1703
2451
  percent: number;
1704
- status: 'pending' | 'initializing' | 'uploading' | 'finalizing' | 'done' | 'error' | 'aborted';
2452
+ status: 'pending' | 'initializing' | 'uploading'
2453
+ /** Every byte is in filex; the commit is being accepted. */
2454
+ | 'committing'
2455
+ /** Committed and listed; the ops worker is writing it to the backend. */
2456
+ | 'transferring' | 'done' | 'error' | 'aborted';
2457
+ /** Byte offset an interrupted session was picked up from, when it was. */
2458
+ resumedFrom?: number;
2459
+ /** Background transfer op, once the commit is accepted. */
2460
+ opId?: number;
2461
+ nodeId?: number;
1705
2462
  error?: string;
1706
2463
  cancel(): void;
1707
2464
  }
@@ -1714,12 +2471,62 @@ export declare interface UploadOptions {
1714
2471
  path: string;
1715
2472
  file: File;
1716
2473
  chunkSize?: number;
1717
- parallelChunks?: number;
1718
2474
  onProgress?: (job: UploadJob) => void;
1719
- onDone?: (job: UploadJob, result: UploadFinalizeResponse) => void;
2475
+ onDone?: (job: UploadJob, result: UploadResult) => void;
1720
2476
  onError?: (job: UploadJob, err: Error) => void;
2477
+ /**
2478
+ * Wait for the backend transfer before resolving. Default false: the node is
2479
+ * listed the moment the commit is accepted, which is the whole point of
2480
+ * staging. `true` is for a caller that must not report success until the
2481
+ * bytes are on the driver.
2482
+ */
2483
+ waitForTransfer?: boolean;
2484
+ }
2485
+
2486
+ /** What `commit` answers, plus the fields a caller wants afterwards. */
2487
+ export declare interface UploadResult {
2488
+ id: string;
2489
+ op_id?: number;
2490
+ node_id?: number;
2491
+ path?: string;
2492
+ transfer_state?: string;
1721
2493
  }
1722
2494
 
2495
+ export declare function useConnections(config: ExplorerConfig): {
2496
+ drivers: ShallowRef<StorageDriverDescriptor[], StorageDriverDescriptor[]>;
2497
+ driverNames: ComputedRef<string[]>;
2498
+ storages: ShallowRef<StorageRow[], StorageRow[]>;
2499
+ visible: ShallowRef<string[], string[]>;
2500
+ me: Ref< {
2501
+ id: number;
2502
+ email: string;
2503
+ display_name?: string | undefined;
2504
+ role?: string | undefined;
2505
+ } | null, ConnectionsUser | {
2506
+ id: number;
2507
+ email: string;
2508
+ display_name?: string | undefined;
2509
+ role?: string | undefined;
2510
+ } | null>;
2511
+ loading: Ref<boolean, boolean>;
2512
+ loaded: Ref<boolean, boolean>;
2513
+ error: Ref<string | null, string | null>;
2514
+ canManage: Ref<boolean | null, boolean | null>;
2515
+ denial: Ref<ManageDenial | null, ManageDenial | null>;
2516
+ load: () => Promise<void>;
2517
+ createStorage: (body: StorageWrite) => Promise<StorageRow>;
2518
+ updateStorage: (id: number, body: Partial<StorageWrite>) => Promise<StorageRow>;
2519
+ deleteStorage: (id: number) => Promise<void>;
2520
+ testStorage: (body: {
2521
+ driver: string;
2522
+ config: Record<string, unknown>;
2523
+ }) => Promise<StorageTestResult>;
2524
+ descriptor: (driver: string) => StorageDriverDescriptor | undefined;
2525
+ fields: (driver: string) => StorageField[];
2526
+ defaults: (driver: string) => Record<string, unknown>;
2527
+ missingRequired: (driver: string, cfg: Record<string, unknown>) => StorageField[];
2528
+ };
2529
+
1723
2530
  export declare function useFileApi(config: ExplorerConfig): {
1724
2531
  wsTicket: () => Promise<{
1725
2532
  ticket: string;
@@ -1876,6 +2683,74 @@ export declare function useMonacoLoader(): {
1876
2683
  getHighlight: typeof getHighlight;
1877
2684
  };
1878
2685
 
2686
+ export declare function useNFSExports(config: ExplorerConfig): {
2687
+ exports: ShallowRef<NFSExport[], NFSExport[]>;
2688
+ connection: Ref< {
2689
+ enabled: boolean;
2690
+ host: string;
2691
+ port: number;
2692
+ } | null, NFSConnection | {
2693
+ enabled: boolean;
2694
+ host: string;
2695
+ port: number;
2696
+ } | null>;
2697
+ loading: Ref<boolean, boolean>;
2698
+ loaded: Ref<boolean, boolean>;
2699
+ error: Ref<string | null, string | null>;
2700
+ canMint: Ref<boolean | null, boolean | null>;
2701
+ revealed: Ref< {
2702
+ export: {
2703
+ id: number;
2704
+ user_id: number;
2705
+ api_token_id?: number | null | undefined;
2706
+ label: string;
2707
+ storage_name?: string | undefined;
2708
+ prefix?: string | undefined;
2709
+ read_only: boolean;
2710
+ allow_cidrs?: string | undefined;
2711
+ created_at: string;
2712
+ last_used_at?: string | null | undefined;
2713
+ expires_at?: string | null | undefined;
2714
+ disabled_at?: string | null | undefined;
2715
+ };
2716
+ path: string;
2717
+ enabled: boolean;
2718
+ host: string;
2719
+ port: number;
2720
+ } | null, NFSExportCreated | {
2721
+ export: {
2722
+ id: number;
2723
+ user_id: number;
2724
+ api_token_id?: number | null | undefined;
2725
+ label: string;
2726
+ storage_name?: string | undefined;
2727
+ prefix?: string | undefined;
2728
+ read_only: boolean;
2729
+ allow_cidrs?: string | undefined;
2730
+ created_at: string;
2731
+ last_used_at?: string | null | undefined;
2732
+ expires_at?: string | null | undefined;
2733
+ disabled_at?: string | null | undefined;
2734
+ };
2735
+ path: string;
2736
+ enabled: boolean;
2737
+ host: string;
2738
+ port: number;
2739
+ } | null>;
2740
+ guideExport: ComputedRef<NFSExport | null>;
2741
+ load: () => Promise<void>;
2742
+ create: (req: {
2743
+ label: string;
2744
+ storage?: string;
2745
+ prefix?: string;
2746
+ read_only?: boolean;
2747
+ allow_cidrs?: string;
2748
+ }) => Promise<NFSExportCreated | null>;
2749
+ setDisabled: (id: number, disabled: boolean) => Promise<void>;
2750
+ remove: (id: number) => Promise<void>;
2751
+ dismissPath: () => void;
2752
+ };
2753
+
1879
2754
  export declare function useOperations(): {
1880
2755
  active: Ref< {
1881
2756
  key: string;
@@ -2009,6 +2884,66 @@ declare interface UserSuggestion {
2009
2884
  role: string;
2010
2885
  }
2011
2886
 
2887
+ export declare function useS3Keys(config: ExplorerConfig): {
2888
+ keys: ShallowRef<S3AccessKey[], S3AccessKey[]>;
2889
+ connection: Ref< {
2890
+ endpoint: string;
2891
+ enabled: boolean;
2892
+ path_style: boolean;
2893
+ } | null, S3Connection | {
2894
+ endpoint: string;
2895
+ enabled: boolean;
2896
+ path_style: boolean;
2897
+ } | null>;
2898
+ loading: Ref<boolean, boolean>;
2899
+ loaded: Ref<boolean, boolean>;
2900
+ error: Ref<string | null, string | null>;
2901
+ canMint: Ref<boolean | null, boolean | null>;
2902
+ revealed: Ref< {
2903
+ key: {
2904
+ id: number;
2905
+ access_key_id: string;
2906
+ user_id: number;
2907
+ api_token_id?: number | null | undefined;
2908
+ label: string;
2909
+ bucket?: string | undefined;
2910
+ prefix?: string | undefined;
2911
+ created_at: string;
2912
+ last_used_at?: string | null | undefined;
2913
+ expires_at?: string | null | undefined;
2914
+ disabled_at?: string | null | undefined;
2915
+ };
2916
+ secret: string;
2917
+ endpoint: string;
2918
+ enabled: boolean;
2919
+ path_style: boolean;
2920
+ } | null, S3KeyCreated | {
2921
+ key: {
2922
+ id: number;
2923
+ access_key_id: string;
2924
+ user_id: number;
2925
+ api_token_id?: number | null | undefined;
2926
+ label: string;
2927
+ bucket?: string | undefined;
2928
+ prefix?: string | undefined;
2929
+ created_at: string;
2930
+ last_used_at?: string | null | undefined;
2931
+ expires_at?: string | null | undefined;
2932
+ disabled_at?: string | null | undefined;
2933
+ };
2934
+ secret: string;
2935
+ endpoint: string;
2936
+ enabled: boolean;
2937
+ path_style: boolean;
2938
+ } | null>;
2939
+ guideKey: ComputedRef<S3AccessKey | null>;
2940
+ load: () => Promise<void>;
2941
+ create: (req: S3KeyRequest) => Promise<S3KeyCreated | null>;
2942
+ setDisabled: (id: number, disabled: boolean) => Promise<void>;
2943
+ remove: (id: number) => Promise<void>;
2944
+ dismissSecret: () => void;
2945
+ };
2946
+
2012
2947
  export declare function useSelection(items: () => FileNode[]): {
2013
2948
  selected: Ref<Set<string> & Omit<Set<string>, keyof Set<any>>, Set<string> | (Set<string> & Omit<Set<string>, keyof Set<any>>)>;
2014
2949
  anchor: Ref<string | null, string | null>;
@@ -2029,6 +2964,46 @@ export declare function useSelection(items: () => FileNode[]): {
2029
2964
  /** Reactive, override-aware view of the registry (registry order). */
2030
2965
  export declare function useShortcutList(): ComputedRef<ShortcutView[]>;
2031
2966
 
2967
+ export declare function useSSHKeys(config: ExplorerConfig): {
2968
+ keys: ShallowRef<SSHPublicKey[], SSHPublicKey[]>;
2969
+ connection: Ref< {
2970
+ enabled: boolean;
2971
+ host: string;
2972
+ port: number;
2973
+ login: string;
2974
+ ftps?: {
2975
+ enabled: boolean;
2976
+ host: string;
2977
+ port: number;
2978
+ pasv_min: number;
2979
+ pasv_max: number;
2980
+ self_signed: boolean;
2981
+ } | undefined;
2982
+ } | null, SSHConnection | {
2983
+ enabled: boolean;
2984
+ host: string;
2985
+ port: number;
2986
+ login: string;
2987
+ ftps?: {
2988
+ enabled: boolean;
2989
+ host: string;
2990
+ port: number;
2991
+ pasv_min: number;
2992
+ pasv_max: number;
2993
+ self_signed: boolean;
2994
+ } | undefined;
2995
+ } | null>;
2996
+ loading: Ref<boolean, boolean>;
2997
+ loaded: Ref<boolean, boolean>;
2998
+ error: Ref<string | null, string | null>;
2999
+ canAdd: Ref<boolean | null, boolean | null>;
3000
+ hasUsableKey: ComputedRef<boolean>;
3001
+ load: () => Promise<void>;
3002
+ add: (key: string, name?: string) => Promise<boolean>;
3003
+ setDisabled: (id: number, disabled: boolean) => Promise<void>;
3004
+ remove: (id: number) => Promise<void>;
3005
+ };
3006
+
2032
3007
  export declare function useTabs(opts: UseTabsOptions): {
2033
3008
  tabs: Ref<TabState[], TabState[]>;
2034
3009
  activeId: Ref<string, string>;
@@ -2061,8 +3036,52 @@ export declare function useThemeState(): {
2061
3036
  setTheme: (id: string) => void;
2062
3037
  };
2063
3038
 
2064
- export declare function useUploadChunked(config: ExplorerConfig, api: FileApi): {
2065
- uploadFile: (opts: UploadOptions) => Promise<UploadFinalizeResponse>;
3039
+ export declare function useTokens(config: ExplorerConfig): {
3040
+ tokens: ShallowRef<ApiToken[], ApiToken[]>;
3041
+ loading: Ref<boolean, boolean>;
3042
+ loaded: Ref<boolean, boolean>;
3043
+ error: Ref<string | null, string | null>;
3044
+ canMint: Ref<boolean | null, boolean | null>;
3045
+ revealed: Ref< {
3046
+ token: string;
3047
+ row: {
3048
+ id: number;
3049
+ label: string;
3050
+ scopes: string;
3051
+ usernames?: string | null | undefined;
3052
+ created_at?: string | undefined;
3053
+ last_used_at?: string | null | undefined;
3054
+ expires_at?: string | null | undefined;
3055
+ };
3056
+ } | null, ApiTokenCreated | {
3057
+ token: string;
3058
+ row: {
3059
+ id: number;
3060
+ label: string;
3061
+ scopes: string;
3062
+ usernames?: string | null | undefined;
3063
+ created_at?: string | undefined;
3064
+ last_used_at?: string | null | undefined;
3065
+ expires_at?: string | null | undefined;
3066
+ };
3067
+ } | null>;
3068
+ load: () => Promise<void>;
3069
+ create: (req: ApiTokenRequest) => Promise<ApiTokenCreated | null>;
3070
+ remove: (id: number) => Promise<void>;
3071
+ dismiss: () => void;
3072
+ };
3073
+
3074
+ export declare function useUploadChunked(config: ExplorerConfig, api: FileApi, storage?: ResumeStorage | null): {
3075
+ uploadFile: (opts: UploadOptions) => Promise<UploadResult>;
3076
+ /** True when this file is big enough (and the endpoints exist) to chunk. */
3077
+ shouldChunk: (file: {
3078
+ size: number;
3079
+ }) => boolean;
3080
+ /** The size at which chunking kicks in. */
3081
+ threshold: () => number;
3082
+ resumableFor: (path: string, file: File) => ResumeRecord | null;
3083
+ listResumable: () => ResumeRecord[];
3084
+ discardResumable: (rec: ResumeRecord) => Promise<void>;
2066
3085
  };
2067
3086
 
2068
3087
  /**