@termix-ssh/plugin-sdk 1.0.20 → 1.0.22
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/CHANGELOG.md +29 -0
- package/PLUGIN-GUIDE.md +66 -4
- package/cli/commands/build.mjs +55 -36
- package/cli/commands/validate.mjs +4 -0
- package/dist/backend.d.ts +32 -7
- package/dist/frontend.d.ts +7 -0
- package/dist/host/{PluginsTab-ZSMTJ5C5.js → PluginsTab-CM2B33BD.js} +26 -16
- package/dist/host/chunk-7J4QNGFM.js +345 -0
- package/dist/host/{chunk-J3ZQPBDK.js → chunk-HQ6NNFSX.js} +115 -1575
- package/dist/host/chunk-I5F2PSMR.js +1526 -0
- package/dist/host/{chunk-2MU6WNSX.js → chunk-JRV2HIDK.js} +1 -1
- package/dist/host/{chunk-7LEJ3HYF.js → chunk-P26QKKU5.js} +41 -10
- package/dist/host/{chunk-CITUENSU.js → chunk-ROB2SH5Q.js} +77 -49
- package/dist/host/chunk-TK3Y437H.js +53 -0
- package/dist/host/{chunk-EYONBBRJ.js → chunk-VKNGNEF3.js} +34650 -34968
- package/dist/host/core-classes.json +1 -1
- package/dist/host/frame-host.css +2 -0
- package/dist/host/frame-host.js +242 -0
- package/dist/host/{linked-server-OUF3POT7.js → linked-server-J5G457OA.js} +1 -1
- package/dist/host/{rbac-api-LZVENREG.js → rbac-api-PY22W5UY.js} +5 -1
- package/dist/host/testing-host.js +9 -6
- package/dist/host/types/src/types/host-sidebar-preferences.d.ts +2 -0
- package/dist/host/types/src/types/index.d.ts +8 -0
- package/dist/host/types/src/ui/api/look-api.d.ts +2 -0
- package/dist/host/types/src/ui/api/plugins-api.d.ts +3 -2
- package/dist/host/types/src/ui/api/rbac-api.d.ts +19 -1
- package/dist/host/types/src/ui/components/data-view.d.ts +1 -1
- package/dist/host/types/src/ui/lib/app-return.d.ts +13 -0
- package/dist/host/types/src/ui/main-axios.d.ts +1 -1
- package/dist/host/types/src/ui/plugin-host/loader.d.ts +6 -0
- package/dist/host/types/src/ui/plugin-host/sdk-ui.d.ts +1 -1
- package/dist/host/types/src/ui/plugins/plugin-model.d.ts +1 -1
- package/dist/host/types/src/ui/plugins/use-plugins-manager.d.ts +1 -1
- package/dist/host/types/src/ui/shell/tab-registry.d.ts +2 -0
- package/dist/host/ui.js +418 -388
- package/dist/host/{use-permissions-BYCDS5PD.js → use-permissions-EWRI3WLT.js} +1 -1
- package/dist/host-commands.d.ts +9 -6
- package/dist/host-commands.js +38 -11
- package/dist/host-scripts/docker.d.ts +137 -0
- package/dist/host-scripts/docker.js +345 -0
- package/dist/host-scripts/index.d.ts +15 -0
- package/dist/host-scripts/index.js +15 -0
- package/dist/host-scripts/journal.d.ts +45 -0
- package/dist/host-scripts/journal.js +194 -0
- package/dist/host-scripts/metrics.d.ts +134 -0
- package/dist/host-scripts/metrics.js +320 -0
- package/dist/host-scripts/openrc.d.ts +6 -0
- package/dist/host-scripts/openrc.js +90 -0
- package/dist/host-scripts/shell.d.ts +43 -0
- package/dist/host-scripts/shell.js +95 -0
- package/dist/host-scripts/systemd.d.ts +73 -0
- package/dist/host-scripts/systemd.js +320 -0
- package/dist/host-scripts/text.d.ts +23 -0
- package/dist/host-scripts/text.js +111 -0
- package/dist/manifest.d.ts +28 -1
- package/dist/manifest.js +108 -1
- package/package.json +9 -1
- package/schema/manifest.schema.json +42 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.22
|
|
4
|
+
|
|
5
|
+
Plugin API 1.13.0.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `contributes.mobile` in the manifest says how a plugin shows in the Termix mobile app: `webview`, `native` or `none`, with an optional mobile bundle, screen sizes, views and a note
|
|
10
|
+
- `termix-plugin build` bundles `src/mobile` into the `contributes.mobile.entry` file, and `validate` checks that it exists
|
|
11
|
+
- `@termix-ssh/plugin-sdk/frame-host` runs one plugin view outside the shell, with core's bridge, registries and CSS, so the mobile app can show `webview` plugins
|
|
12
|
+
- SSH transport providers get the host's owner, sync id, connection origin and local only flag in `claims` and `open`, and core's SSH relay takes a `syncId` instead of a host id
|
|
13
|
+
- `ctx.sync.host.entities.idsOf` maps sync ids to the local ids of the user's own rows of one type
|
|
14
|
+
- Rail items take `useStatus` for a small ok, warning or error dot on the icon
|
|
15
|
+
- `shell.editHost(hostId)` opens the editor of a saved host
|
|
16
|
+
- A sync entity can be read only with `load`, `write` and `erase`, so a plugin can sync rows a user does not own, like a shared library, down to linked desktops
|
|
17
|
+
- `getFolderAuthOverride` and `setFolderAuthOverride` in `@termix-ssh/plugin-sdk/ui` read and set a user's own login for the shared hosts in a folder
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- `@termix-ssh/plugin-sdk/ui` is built in production mode, so outside Termix it no longer sends requests to the Vite dev server path
|
|
22
|
+
- `execCommand` buffers output and decodes it once as strict UTF-8 with a GBK fallback, exposed as `decodeCommandOutput`, so messages from a Windows host on a legacy codepage are readable instead of � characters
|
|
23
|
+
|
|
24
|
+
## 1.0.21
|
|
25
|
+
|
|
26
|
+
Plugin API 1.12.0.
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- `@termix-ssh/plugin-sdk/host-scripts`: Docker, Linux metrics, systemd, journal and OpenRC command builders and parsers, plus `scriptShell()` and `passwordSudoScript()` to send scripts to `bash -s` on stdin. It has no Node or ssh2 imports, so the Termix mobile app runs the same code
|
|
31
|
+
|
|
3
32
|
## 1.0.20
|
|
4
33
|
|
|
5
34
|
Plugin API 1.12.0.
|
package/PLUGIN-GUIDE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Termix plugin guide
|
|
2
2
|
|
|
3
|
-
SDK 1.0.
|
|
3
|
+
SDK 1.0.22 | Plugin API 1.13.0
|
|
4
4
|
|
|
5
5
|
This guide is for anyone, person or AI coding tool, building a Termix plugin. It ships inside
|
|
6
6
|
`@termix-ssh/plugin-sdk`, so the copy in your `node_modules` always matches the SDK you have installed.
|
|
@@ -175,7 +175,49 @@ Unknown fields are refused at every level, so a typo fails validation. The schem
|
|
|
175
175
|
|
|
176
176
|
`contributes` holds `tabs`, `panels`, `dashboardCards`, `actions`, `actionSlots`, `permissions`,
|
|
177
177
|
`settings`, `protocols`, `auth`, `syncEntities`, `keybindingActions`, `http`, `guest`, `guestViews`,
|
|
178
|
-
`uiPresets`, `uses`, `layouts` and `
|
|
178
|
+
`uiPresets`, `uses`, `layouts`, `themes` and `mobile`. Frontend `register*` calls need their ids declared here first.
|
|
179
|
+
|
|
180
|
+
### Mobile app
|
|
181
|
+
|
|
182
|
+
`contributes.mobile` says how the plugin shows in the Termix mobile app. Leave it out and the app treats the
|
|
183
|
+
plugin as having nothing to show (official plugins are mapped by the app itself).
|
|
184
|
+
|
|
185
|
+
```json
|
|
186
|
+
"mobile": { "mode": "webview", "views": ["hello"], "sizes": ["phone", "tablet"] }
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
| Field | Notes |
|
|
190
|
+
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
191
|
+
| `mode` | `webview` (the app shows your UI from the server), `native` (the app has its own screens) or `none`. |
|
|
192
|
+
| `entry` | `webview` only. A bundle made for the app, like `dist/mobile.js`, next to `frontend.js`. Unset: your normal frontend runs in a phone sized frame. |
|
|
193
|
+
| `sizes` | `webview` only. `phone`, `tablet` or both (the default). |
|
|
194
|
+
| `views` | `webview` only. Ids from your `tabs`, `panels` or `dashboardCards` the app offers. Default: every tab. |
|
|
195
|
+
| `feature` | `native` only. The app feature that stands in for the plugin. Default: the plugin id. |
|
|
196
|
+
| `note` | A locale key or plain text the app shows next to the plugin. |
|
|
197
|
+
|
|
198
|
+
`build` makes `entry` from `src/mobile/index.tsx`, and it shares `frontend.css`.
|
|
199
|
+
|
|
200
|
+
How a `webview` plugin runs in the app:
|
|
201
|
+
|
|
202
|
+
- Only your plugin loads, with the same app object, SDK hooks and `/ui` kit as the web app. Each view opens
|
|
203
|
+
on its own screen: a tab with `host-context-menu` in `openFrom` opens from a host, the rest from the app's
|
|
204
|
+
plugin list.
|
|
205
|
+
- Every request and socket goes through the app, which adds the login, proxy headers and client
|
|
206
|
+
certificate. The app only lets through your own `/plugin-api`, `/plugin-ws` and `/plugin-assets`, your
|
|
207
|
+
settings and a short list of core reads (hosts, folders, permissions, current user, version, health,
|
|
208
|
+
activity). Other core routes answer 403, so call your own backend instead.
|
|
209
|
+
- Images load straight from the server, without proxy headers or a client certificate.
|
|
210
|
+
- Nothing in `localStorage` survives closing the view. Keep preferences in settings.
|
|
211
|
+
- Other plugins' views, extensions and components are not there, and neither is the shell (`useShell()`
|
|
212
|
+
is null). `/ui` names newer than the app's copy of the SDK make the view say it needs a newer app.
|
|
213
|
+
- The app checks your `engine.api` against the plugin API of its own copy of the SDK. When the app's API
|
|
214
|
+
is too old for the range, it asks the user to update the app instead of opening the view. When the range
|
|
215
|
+
is for an older API major, it asks to update the plugin. Ask only for the API you use.
|
|
216
|
+
- Turning a plugin off on the server hides it in the app the next time the app checks: when it comes back
|
|
217
|
+
to the front, when the list is pulled down, or when any request answers 503 with your `pluginId`.
|
|
218
|
+
|
|
219
|
+
Termix 26.10.5 and older refuse any manifest with `mobile`, so a plugin that declares it needs
|
|
220
|
+
`"engine": { "termix": ">=26.10.6", "api": "^1.13" }`.
|
|
179
221
|
|
|
180
222
|
Capabilities and permissions are different things. A capability (`hosts:read`, with a colon) is what your
|
|
181
223
|
code may reach. A permission (`hello.use`, with a dot) is what a user may do.
|
|
@@ -304,7 +346,7 @@ router.post("/notes", async (req, res) => {
|
|
|
304
346
|
- `ctx.rbac.require("manage")` on a single route, `await ctx.rbac.has("manage")` in code. A full id like
|
|
305
347
|
`hosts.edit` checks a core permission.
|
|
306
348
|
- Host access: `ctx.hosts.checkAccess(hostId, "connect" | "view" | "edit" | "manage")`.
|
|
307
|
-
- Transports: with `ssh:transport`, `ctx.ssh.transports.provide({ claims(hostId), open(
|
|
349
|
+
- Transports: with `ssh:transport`, `ctx.ssh.transports.provide({ claims(hostId, host), open(host) })`, where `host` has `id`, `ip`, `port` and on Termix 26.10.6+ also `userId`, `syncId`, `connectionOrigin` and `localOnly`
|
|
308
350
|
returns a stream the SSH client uses instead of a direct, proxy or jump host connection. Core also serves
|
|
309
351
|
`/plugin-ws/core/ssh-relay?hostId=` for callers with connect access, so one Termix can relay to another.
|
|
310
352
|
- Managed hosts: with `hosts:lock`, `ctx.hosts.locks.provide({ isLocked, note, allowManualCreate })` makes
|
|
@@ -335,7 +377,9 @@ try {
|
|
|
335
377
|
|
|
336
378
|
`ctx.ssh.withConnection(hostId, { pool, purpose }, fn)` borrows a pooled connection for frequent short
|
|
337
379
|
commands. `@termix-ssh/plugin-sdk/host-commands` has helpers to detect the OS and run package manager and
|
|
338
|
-
sudo commands.
|
|
380
|
+
sudo commands. `@termix-ssh/plugin-sdk/host-scripts` builds Docker, metrics, systemd and journal
|
|
381
|
+
commands and parses their output; send a script on stdin to the command `scriptShell()` returns. Hosts from
|
|
382
|
+
`ctx.hosts` never carry secrets. Pass the host or its id to `ctx.ssh`.
|
|
339
383
|
|
|
340
384
|
### The desktop app
|
|
341
385
|
|
|
@@ -451,6 +495,23 @@ Other storage: `ctx.kv` for a few values, `ctx.settings` for settings, `ctx.secr
|
|
|
451
495
|
`ctx.files.dataDir()` for files. To sync a table to a linked desktop, use `ctx.sync.registerEntity` and
|
|
452
496
|
`contributes.syncEntities`. The entity name is part of the protocol and never changes.
|
|
453
497
|
|
|
498
|
+
A registered table syncs the rows each user owns, both ways. For data you keep for the whole server (a shared
|
|
499
|
+
library, rows other users share), register a read only entity with `table: null`, `readOnly: true` and `load`,
|
|
500
|
+
`write` and `erase` (Termix 26.10.6+). `load(userId)` returns the rows that user should get, each with a
|
|
501
|
+
`syncId`. The desktop keeps them with `write` and drops them with `erase`, and never pushes them back:
|
|
502
|
+
|
|
503
|
+
```ts
|
|
504
|
+
ctx.sync.registerEntity({
|
|
505
|
+
type: "sharedIcons",
|
|
506
|
+
table: null,
|
|
507
|
+
readOnly: true,
|
|
508
|
+
order: 70,
|
|
509
|
+
load: (userId) => iconsVisibleTo(userId),
|
|
510
|
+
write: (userId, row) => storeCopy(userId, row),
|
|
511
|
+
erase: (userId, syncId) => dropCopy(userId, syncId),
|
|
512
|
+
});
|
|
513
|
+
```
|
|
514
|
+
|
|
454
515
|
## Frontend
|
|
455
516
|
|
|
456
517
|
```tsx
|
|
@@ -905,6 +966,7 @@ To run your own registry instead, see https://docs.termix.site/develop/registrie
|
|
|
905
966
|
| `@termix-ssh/plugin-sdk/testing` | `createMockCtx`, `createTestDb`, `renderWithApp`, `createFakeContext` |
|
|
906
967
|
| `@termix-ssh/plugin-sdk/vitest-preset` | `pluginVitestConfig` |
|
|
907
968
|
| `@termix-ssh/plugin-sdk/host-commands` | OS detection, package manager and sudo helpers over ssh2 |
|
|
969
|
+
| `@termix-ssh/plugin-sdk/host-scripts` | Docker, metrics, systemd and journal command builders and parsers, scripts sent to `bash -s` |
|
|
908
970
|
| `@termix-ssh/plugin-sdk/ssh-certs` | `applyCertificateAuth()` for certificate based SSH auth |
|
|
909
971
|
| `@termix-ssh/plugin-sdk/ssh-keys` | `generateSshKeyPair()`, `detectSshKeyType()`, `publicKeyFromPrivate()`, `validateSshKeyPair()` |
|
|
910
972
|
| `@termix-ssh/plugin-sdk/desktop` | The desktop app bridge, typed. Check it exists before use |
|
package/cli/commands/build.mjs
CHANGED
|
@@ -25,6 +25,43 @@ const FRONTEND_ENTRIES = [
|
|
|
25
25
|
"src/frontend/index.mjs",
|
|
26
26
|
"src/frontend/index.js",
|
|
27
27
|
];
|
|
28
|
+
const MOBILE_ENTRIES = [
|
|
29
|
+
"src/mobile/index.tsx",
|
|
30
|
+
"src/mobile/index.ts",
|
|
31
|
+
"src/mobile/index.mjs",
|
|
32
|
+
"src/mobile/index.js",
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
/** Builds a bundle that runs in a browser against core's shared modules. */
|
|
36
|
+
async function buildBrowserBundle(entry, outfile, outDir) {
|
|
37
|
+
await esbuild.build({
|
|
38
|
+
entryPoints: [entry],
|
|
39
|
+
outfile,
|
|
40
|
+
bundle: true,
|
|
41
|
+
format: "esm",
|
|
42
|
+
platform: "browser",
|
|
43
|
+
target: "es2022",
|
|
44
|
+
jsx: "automatic",
|
|
45
|
+
sourcemap: true,
|
|
46
|
+
logLevel: "warning",
|
|
47
|
+
external: FRONTEND_EXTERNALS,
|
|
48
|
+
// Vite replaces these in the dev server; a plugin bundle has to have
|
|
49
|
+
// them too, or code that reads them throws when the plugin loads.
|
|
50
|
+
define: {
|
|
51
|
+
"process.env.NODE_ENV": '"production"',
|
|
52
|
+
"import.meta.env.DEV": "false",
|
|
53
|
+
"import.meta.env.PROD": "true",
|
|
54
|
+
"import.meta.env.MODE": '"production"',
|
|
55
|
+
"import.meta.env.SSR": "false",
|
|
56
|
+
"import.meta.env":
|
|
57
|
+
'{"DEV":false,"PROD":true,"MODE":"production","SSR":false,"BASE_URL":"/"}',
|
|
58
|
+
},
|
|
59
|
+
plugins: [
|
|
60
|
+
externalRequireInterop(FRONTEND_EXTERNALS),
|
|
61
|
+
staticUrlImports({ outDir }),
|
|
62
|
+
],
|
|
63
|
+
});
|
|
64
|
+
}
|
|
28
65
|
|
|
29
66
|
/**
|
|
30
67
|
* A bundled CommonJS package that calls require("react/jsx-runtime") (or any
|
|
@@ -192,15 +229,12 @@ export async function buildTailwind(cwd, outDir) {
|
|
|
192
229
|
// build() adds to what a compiler already emitted, so each half gets its own.
|
|
193
230
|
const compiler = () =>
|
|
194
231
|
compile(input, { base: path.dirname(THEME_CSS), onDependency: () => {} });
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
},
|
|
202
|
-
],
|
|
203
|
-
});
|
|
232
|
+
// The mobile bundle uses the same stylesheet.
|
|
233
|
+
const sources = ["frontend", "mobile"]
|
|
234
|
+
.map((dir) => path.join(cwd, "src", dir))
|
|
235
|
+
.filter((dir, index) => index === 0 || fs.existsSync(dir))
|
|
236
|
+
.map((base) => ({ base, pattern: "**/*", negated: false }));
|
|
237
|
+
const scanner = new Scanner({ sources });
|
|
204
238
|
// Flattened and minified the way core's own CSS is, so it does not rely
|
|
205
239
|
// on native CSS nesting.
|
|
206
240
|
const emit = async (candidates, options) =>
|
|
@@ -297,33 +331,18 @@ export async function build({ cwd }) {
|
|
|
297
331
|
|
|
298
332
|
const frontendEntry = resolveEntry(cwd, FRONTEND_ENTRIES);
|
|
299
333
|
if (frontendEntry) {
|
|
300
|
-
await
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
// them too, or code that reads them throws when the plugin loads.
|
|
313
|
-
define: {
|
|
314
|
-
"process.env.NODE_ENV": '"production"',
|
|
315
|
-
"import.meta.env.DEV": "false",
|
|
316
|
-
"import.meta.env.PROD": "true",
|
|
317
|
-
"import.meta.env.MODE": '"production"',
|
|
318
|
-
"import.meta.env.SSR": "false",
|
|
319
|
-
"import.meta.env":
|
|
320
|
-
'{"DEV":false,"PROD":true,"MODE":"production","SSR":false,"BASE_URL":"/"}',
|
|
321
|
-
},
|
|
322
|
-
plugins: [
|
|
323
|
-
externalRequireInterop(FRONTEND_EXTERNALS),
|
|
324
|
-
staticUrlImports({ outDir }),
|
|
325
|
-
],
|
|
326
|
-
});
|
|
334
|
+
await buildBrowserBundle(
|
|
335
|
+
frontendEntry,
|
|
336
|
+
path.join(outDir, "frontend.js"),
|
|
337
|
+
outDir,
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// A bundle the mobile app loads in its WebView, next to the frontend one.
|
|
342
|
+
const mobileEntry = resolveEntry(cwd, MOBILE_ENTRIES);
|
|
343
|
+
const mobileOut = manifest.contributes?.mobile?.entry;
|
|
344
|
+
if (mobileEntry && typeof mobileOut === "string") {
|
|
345
|
+
await buildBrowserBundle(mobileEntry, path.join(cwd, mobileOut), outDir);
|
|
327
346
|
}
|
|
328
347
|
|
|
329
348
|
if (frontendEntry) await buildTailwind(cwd, outDir);
|
|
@@ -28,6 +28,10 @@ export async function validate({ cwd }) {
|
|
|
28
28
|
// A theme only plugin has no frontend code.
|
|
29
29
|
...(isThemeOnly(raw) ? [] : [raw.frontend ?? "dist/frontend.js"]),
|
|
30
30
|
raw.locales ?? "locales",
|
|
31
|
+
// Built from src/mobile by termix-plugin build.
|
|
32
|
+
...(typeof raw.contributes?.mobile?.entry === "string"
|
|
33
|
+
? [raw.contributes.mobile.entry]
|
|
34
|
+
: []),
|
|
31
35
|
];
|
|
32
36
|
for (const rel of referenced) {
|
|
33
37
|
if (!fs.existsSync(path.join(cwd, rel))) {
|
package/dist/backend.d.ts
CHANGED
|
@@ -149,8 +149,24 @@ export interface SyncWriteEvent {
|
|
|
149
149
|
export interface SyncEntityRegistration {
|
|
150
150
|
/** Stable wire name. Never change it: records on both sides match on it. */
|
|
151
151
|
type: string;
|
|
152
|
-
/** The table, as returned by ctx.db.define. */
|
|
152
|
+
/** The table, as returned by ctx.db.define. Null for an entity with `load`. */
|
|
153
153
|
table: unknown;
|
|
154
|
+
/**
|
|
155
|
+
* Server to desktop only: a desktop never pushes these rows. Required with
|
|
156
|
+
* `load`.
|
|
157
|
+
*/
|
|
158
|
+
readOnly?: boolean;
|
|
159
|
+
/**
|
|
160
|
+
* Builds the rows the user gets instead of reading the rows they own in
|
|
161
|
+
* `table`, for data kept for the whole server (a shared library, rows other
|
|
162
|
+
* users share with them). Rows are on the wire: plaintext, each with a
|
|
163
|
+
* syncId. Needs `readOnly: true`, `write` and `erase`.
|
|
164
|
+
*/
|
|
165
|
+
load?: (userId: string) => Promise<SyncRow[]>;
|
|
166
|
+
/** Keeps a copy of an inbound row on the desktop, for an entity with `load`. */
|
|
167
|
+
write?: (userId: string, wire: SyncRow) => Promise<void>;
|
|
168
|
+
/** Drops the desktop copy with this syncId, for an entity with `load`. */
|
|
169
|
+
erase?: (userId: string, syncId: string) => Promise<void>;
|
|
154
170
|
/** Column holding the owning user id. Defaults to "userId". */
|
|
155
171
|
userColumn?: string;
|
|
156
172
|
/** Fields stored encrypted with the owner's data key. Plaintext on the wire. */
|
|
@@ -391,6 +407,8 @@ export interface PluginSyncHost {
|
|
|
391
407
|
ensureCore: () => Promise<void>;
|
|
392
408
|
onChanged: (listener: () => void) => () => void;
|
|
393
409
|
onLocalWrite: (listener: () => void) => () => void;
|
|
410
|
+
/** Local ids of the user's own rows of a type, by sync id. Termix 26.10.6+. */
|
|
411
|
+
idsOf?: (type: string, userId: string, syncIds: string[]) => Promise<Record<string, number>>;
|
|
394
412
|
};
|
|
395
413
|
/** Rows and bookkeeping of one local user's replica. */
|
|
396
414
|
replica: {
|
|
@@ -1503,13 +1521,20 @@ export interface PluginNetwork {
|
|
|
1503
1521
|
* asked for every connection, so it must be quick. `open` returns a stream
|
|
1504
1522
|
* the SSH client runs over, like a relayed socket.
|
|
1505
1523
|
*/
|
|
1524
|
+
/** The host a transport is asked about. Fields past port come from Termix 26.10.6. */
|
|
1525
|
+
export interface PluginSshTransportHost {
|
|
1526
|
+
id: number;
|
|
1527
|
+
ip: string;
|
|
1528
|
+
port: number;
|
|
1529
|
+
/** Owner of the host row. */
|
|
1530
|
+
userId?: string | null;
|
|
1531
|
+
syncId?: string | null;
|
|
1532
|
+
connectionOrigin?: "local" | "remote" | null;
|
|
1533
|
+
localOnly?: boolean;
|
|
1534
|
+
}
|
|
1506
1535
|
export interface PluginSshTransportProvider {
|
|
1507
|
-
claims: (hostId: number) => boolean;
|
|
1508
|
-
open: (host:
|
|
1509
|
-
id: number;
|
|
1510
|
-
ip: string;
|
|
1511
|
-
port: number;
|
|
1512
|
-
}) => Promise<import("node:stream").Duplex>;
|
|
1536
|
+
claims: (hostId: number, host?: PluginSshTransportHost) => boolean;
|
|
1537
|
+
open: (host: PluginSshTransportHost) => Promise<import("node:stream").Duplex>;
|
|
1513
1538
|
}
|
|
1514
1539
|
export interface PluginSshTransports {
|
|
1515
1540
|
/** Needs ssh:transport. Removed on deactivate. */
|
package/dist/frontend.d.ts
CHANGED
|
@@ -185,6 +185,8 @@ export interface ShellApi {
|
|
|
185
185
|
* user reviews and saves it. Absent in some shells.
|
|
186
186
|
*/
|
|
187
187
|
openHostEditor?: (draft?: HostDraft) => void;
|
|
188
|
+
/** Opens the editor of a saved host. Termix 26.10.6+, absent in some shells. */
|
|
189
|
+
editHost?: (hostId: number) => void;
|
|
188
190
|
/** Opens a rail view in the left sidebar. */
|
|
189
191
|
openRailView: (id: string) => void;
|
|
190
192
|
/** Closes a rail view wherever it is shown. */
|
|
@@ -412,6 +414,11 @@ export interface RailItemContribution {
|
|
|
412
414
|
* Zero or nothing shows no badge.
|
|
413
415
|
*/
|
|
414
416
|
useBadge?: () => number | null | undefined;
|
|
417
|
+
/**
|
|
418
|
+
* A hook for a small status dot on the icon: "ok" (green), "warning" or
|
|
419
|
+
* "error". Nothing shows no dot. Layouts from Termix 26.10.6 draw it.
|
|
420
|
+
*/
|
|
421
|
+
useStatus?: () => "ok" | "warning" | "error" | null | undefined;
|
|
415
422
|
}
|
|
416
423
|
export interface PanelProps {
|
|
417
424
|
/**
|
|
@@ -29,12 +29,19 @@ import {
|
|
|
29
29
|
sortPlugins,
|
|
30
30
|
uploadEntry,
|
|
31
31
|
youtubeEmbedUrl
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-ROB2SH5Q.js";
|
|
33
|
+
import {
|
|
34
|
+
Button,
|
|
35
|
+
FakeSwitch,
|
|
36
|
+
Input,
|
|
37
|
+
PluginIcon,
|
|
38
|
+
SectionCard,
|
|
39
|
+
hasSettingsPage
|
|
40
|
+
} from "./chunk-7J4QNGFM.js";
|
|
33
41
|
import {
|
|
34
42
|
ArrowDownWideNarrow,
|
|
35
43
|
BookOpen,
|
|
36
44
|
Bug,
|
|
37
|
-
Button,
|
|
38
45
|
Check,
|
|
39
46
|
ChevronLeft,
|
|
40
47
|
ChevronRight,
|
|
@@ -44,13 +51,11 @@ import {
|
|
|
44
51
|
Download,
|
|
45
52
|
ExternalLink,
|
|
46
53
|
Facts,
|
|
47
|
-
FakeSwitch,
|
|
48
54
|
FlaskConical,
|
|
49
55
|
GRID_SIZES,
|
|
50
56
|
Globe,
|
|
51
57
|
GroupHeading,
|
|
52
58
|
InlineView,
|
|
53
|
-
Input,
|
|
54
59
|
Library,
|
|
55
60
|
Lightbulb,
|
|
56
61
|
ListFilter,
|
|
@@ -58,12 +63,10 @@ import {
|
|
|
58
63
|
PanePrompt,
|
|
59
64
|
PanelShell,
|
|
60
65
|
Pin,
|
|
61
|
-
PluginIcon,
|
|
62
66
|
Puzzle,
|
|
63
67
|
RefreshCw,
|
|
64
68
|
RotateCcw,
|
|
65
69
|
Search,
|
|
66
|
-
SectionCard,
|
|
67
70
|
Select,
|
|
68
71
|
SelectContent,
|
|
69
72
|
SelectItem,
|
|
@@ -88,7 +91,6 @@ import {
|
|
|
88
91
|
getPluginRegistry,
|
|
89
92
|
getPlugins,
|
|
90
93
|
getRegistries,
|
|
91
|
-
hasSettingsPage,
|
|
92
94
|
installPlugin,
|
|
93
95
|
installUploadedPlugin,
|
|
94
96
|
openPluginSettings,
|
|
@@ -107,8 +109,8 @@ import {
|
|
|
107
109
|
updatePlugin,
|
|
108
110
|
uploadPlugin,
|
|
109
111
|
useConfirm
|
|
110
|
-
} from "./chunk-
|
|
111
|
-
import "./chunk-
|
|
112
|
+
} from "./chunk-VKNGNEF3.js";
|
|
113
|
+
import "./chunk-P26QKKU5.js";
|
|
112
114
|
import {
|
|
113
115
|
__toESM,
|
|
114
116
|
define_import_meta_env_default,
|
|
@@ -1560,9 +1562,12 @@ function usePluginsManager() {
|
|
|
1560
1562
|
[run, t, failToast]
|
|
1561
1563
|
);
|
|
1562
1564
|
const requestUpload = useCallback2(
|
|
1563
|
-
async (file) => {
|
|
1565
|
+
async (file, signatureFile) => {
|
|
1564
1566
|
try {
|
|
1565
|
-
const preview = await uploadPlugin(
|
|
1567
|
+
const preview = await uploadPlugin(
|
|
1568
|
+
file,
|
|
1569
|
+
signatureFile ? await signatureFile.text() : void 0
|
|
1570
|
+
);
|
|
1566
1571
|
setConsent({
|
|
1567
1572
|
plugin: uploadEntry(preview),
|
|
1568
1573
|
mode: "upload",
|
|
@@ -2576,12 +2581,15 @@ function UploadButton({ manager }) {
|
|
|
2576
2581
|
{
|
|
2577
2582
|
ref: input,
|
|
2578
2583
|
type: "file",
|
|
2579
|
-
accept: ".tmxplug",
|
|
2584
|
+
accept: ".tmxplug,.sig",
|
|
2585
|
+
multiple: true,
|
|
2580
2586
|
className: "hidden",
|
|
2581
2587
|
onChange: (e) => {
|
|
2582
|
-
const
|
|
2588
|
+
const files = [...e.target.files ?? []];
|
|
2583
2589
|
e.target.value = "";
|
|
2584
|
-
|
|
2590
|
+
const file = files.find((f) => f.name.endsWith(".tmxplug"));
|
|
2591
|
+
const signature = files.find((f) => f.name.endsWith(".sig"));
|
|
2592
|
+
if (file) void manager.requestUpload(file, signature);
|
|
2585
2593
|
}
|
|
2586
2594
|
}
|
|
2587
2595
|
),
|
|
@@ -2681,7 +2689,7 @@ function InstalledCard({
|
|
|
2681
2689
|
)
|
|
2682
2690
|
] }) : !running ? /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2 border-t border-border px-3 py-2", children: [
|
|
2683
2691
|
/* @__PURE__ */ jsx6("span", { className: "size-1.5 shrink-0 bg-muted-foreground/40" }),
|
|
2684
|
-
/* @__PURE__ */ jsx6("span", { className: "text-[11px] text-muted-foreground", children: plugin.status === "server" ? t("plugins.manager.status.server") : t("plugins.manager.status.disabled") })
|
|
2692
|
+
/* @__PURE__ */ jsx6("span", { className: "text-[11px] text-muted-foreground", children: plugin.status === "server" ? t("plugins.manager.status.server") : plugin.status === "safeMode" ? t("plugins.manager.status.safeMode") : t("plugins.manager.status.disabled") })
|
|
2685
2693
|
] }) : /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2 border-t border-border px-3 py-2", children: [
|
|
2686
2694
|
/* @__PURE__ */ jsx6("span", { className: "size-1.5 shrink-0 bg-green-500" }),
|
|
2687
2695
|
/* @__PURE__ */ jsx6("span", { className: "text-[11px] text-muted-foreground", children: plugin.pinnedVersion ? t("plugins.manager.pinnedTo", {
|
|
@@ -2759,7 +2767,9 @@ function StatusCell({ plugin }) {
|
|
|
2759
2767
|
const { t } = useTranslation7();
|
|
2760
2768
|
const running = plugin.status === "running";
|
|
2761
2769
|
const failed = plugin.status === "failed" || plugin.status === "blocked";
|
|
2762
|
-
const label = plugin.status === "blocked" ? t("plugins.manager.status.blocked") : failed ? t("plugins.manager.status.failed") : plugin.status === "server" ? t("plugins.manager.status.server") :
|
|
2770
|
+
const label = plugin.status === "blocked" ? t("plugins.manager.status.blocked") : failed ? t("plugins.manager.status.failed") : plugin.status === "server" ? t("plugins.manager.status.server") : plugin.status === "safeMode" ? t("plugins.manager.status.safeMode") : !running ? t("plugins.manager.status.disabled") : plugin.pinnedVersion ? t("plugins.manager.pinnedTo", {
|
|
2771
|
+
version: plugin.pinnedVersion
|
|
2772
|
+
}) : t("plugins.manager.status.running");
|
|
2763
2773
|
return /* @__PURE__ */ jsxs6(
|
|
2764
2774
|
"span",
|
|
2765
2775
|
{
|