@termix-ssh/plugin-sdk 1.0.2 → 1.0.5
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 +31 -0
- package/LICENSE +202 -0
- package/README.md +4 -2
- package/cli/commands/build.mjs +44 -19
- package/cli/commands/dev.mjs +1 -1
- package/cli/commands/migrations.mjs +2 -2
- package/cli/commands/openapi.mjs +39 -0
- package/cli/commands/validate.mjs +72 -0
- package/cli/index.mjs +3 -0
- package/cli/lib/css-classes.mjs +37 -0
- package/dist/changelog.d.ts +3 -1
- package/dist/changelog.js +12 -2
- package/dist/db.d.ts +1 -1
- package/dist/db.js +1 -1
- package/dist/ddl.js +4 -1
- package/dist/docs.d.ts +28 -0
- package/dist/docs.js +39 -0
- package/dist/frontend.d.ts +17 -0
- package/dist/frontend.js +8 -0
- package/dist/host/{chunk-XKMRKQFN.js → chunk-KOOUKYW3.js} +1 -1
- package/dist/host/{chunk-LKYAVM4T.js → chunk-OEOKIKXC.js} +18 -9
- package/dist/host/{chunk-SS43S6VI.js → chunk-VETPF5WI.js} +478 -431
- package/dist/host/core-classes.json +1 -0
- package/dist/host/{linked-server-DW5P3NLZ.js → linked-server-RWBK427T.js} +1 -1
- package/dist/host/{rbac-api-JKISXZEP.js → rbac-api-UJGGTQVA.js} +1 -1
- package/dist/host/testing-host.js +3 -3
- package/dist/host/types/src/types/credential-sidebar-preferences.d.ts +1 -1
- package/dist/host/types/src/ui/api/plugins-api.d.ts +6 -0
- package/dist/host/types/src/ui/components/docs-link.d.ts +25 -0
- package/dist/host/types/src/ui/components/panel-layout.d.ts +3 -1
- package/dist/host/types/src/ui/lib/docs.d.ts +10 -0
- package/dist/host/types/src/ui/lib/error-message.d.ts +1 -1
- package/dist/host/types/src/ui/main-axios.d.ts +1 -2
- package/dist/host/types/src/ui/plugin-host/sdk-ui.d.ts +1 -0
- package/dist/host/ui.js +18 -8
- package/dist/host/{use-permissions-5CZEA3P4.js → use-permissions-P3DC7ZDR.js} +1 -1
- package/dist/host-commands.js +1 -1
- package/dist/manifest.d.ts +7 -1
- package/dist/manifest.js +48 -1
- package/dist/openapi.d.ts +43 -0
- package/dist/openapi.js +60 -0
- package/dist/testing/setup.js +1 -0
- package/package.json +22 -1
- package/schema/manifest.schema.json +21 -0
package/dist/ddl.js
CHANGED
|
@@ -67,10 +67,13 @@ function columnType(dialect, column, keyed) {
|
|
|
67
67
|
// text/json column, so in practice this only catches a primary key.
|
|
68
68
|
if (keyed && dialect !== "sqlite")
|
|
69
69
|
return `varchar(${KEY_LENGTH})`;
|
|
70
|
+
// MySQL TEXT stops at 64KB, so free text, json and secrets go long.
|
|
71
|
+
if (dialect === "mysql" && column.type !== "timestamp")
|
|
72
|
+
return "longtext";
|
|
70
73
|
return "text";
|
|
71
74
|
}
|
|
72
75
|
}
|
|
73
|
-
/** Column types that are TEXT on MySQL when nothing keys them. */
|
|
76
|
+
/** Column types that are TEXT or LONGTEXT on MySQL when nothing keys them. */
|
|
74
77
|
const TEXT_ON_MYSQL = new Set([
|
|
75
78
|
"text",
|
|
76
79
|
"json",
|
package/dist/docs.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docs links and env var entries from a manifest. Kept apart from manifest.ts
|
|
3
|
+
* so the browser can use them without pulling in semver.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* A docs link, or null for anything that is not an https URL. Core only ever
|
|
7
|
+
* opens it in a new tab, but a javascript: or file: link should never get there.
|
|
8
|
+
*/
|
|
9
|
+
export declare function pluginDocsUrl(value: unknown): string | null;
|
|
10
|
+
/** Joins a page and anchor onto a plugin's docs URL. */
|
|
11
|
+
export declare function pluginDocsPage(base: string, page?: string, anchor?: string): string;
|
|
12
|
+
export declare const ENV_NAME_PATTERN: RegExp;
|
|
13
|
+
export declare const MAX_ENV_DESCRIPTION_LENGTH = 300;
|
|
14
|
+
/**
|
|
15
|
+
* An environment variable a plugin reads. Only shown in docs and on the
|
|
16
|
+
* plugin's page, so people know what they can set.
|
|
17
|
+
*/
|
|
18
|
+
export interface PluginEnvVar {
|
|
19
|
+
name: string;
|
|
20
|
+
description: string;
|
|
21
|
+
/** The value used when it is not set, as written in a compose file. */
|
|
22
|
+
default?: string;
|
|
23
|
+
required?: boolean;
|
|
24
|
+
/** Holds a password or key. */
|
|
25
|
+
secret?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/** The usable env entries of a manifest. Bad ones are dropped. */
|
|
28
|
+
export declare function pluginEnvVars(value: unknown): PluginEnvVar[];
|
package/dist/docs.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docs links and env var entries from a manifest. Kept apart from manifest.ts
|
|
3
|
+
* so the browser can use them without pulling in semver.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* A docs link, or null for anything that is not an https URL. Core only ever
|
|
7
|
+
* opens it in a new tab, but a javascript: or file: link should never get there.
|
|
8
|
+
*/
|
|
9
|
+
export function pluginDocsUrl(value) {
|
|
10
|
+
if (typeof value !== "string")
|
|
11
|
+
return null;
|
|
12
|
+
try {
|
|
13
|
+
const url = new URL(value.trim());
|
|
14
|
+
if (url.protocol !== "https:")
|
|
15
|
+
return null;
|
|
16
|
+
return url.toString().replace(/\/$/, "");
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** Joins a page and anchor onto a plugin's docs URL. */
|
|
23
|
+
export function pluginDocsPage(base, page, anchor) {
|
|
24
|
+
const clean = (page ?? "").replace(/^\/+|\/+$/g, "");
|
|
25
|
+
const url = clean ? `${base}/${clean}` : base;
|
|
26
|
+
return anchor ? `${url}#${anchor.replace(/^#/, "")}` : url;
|
|
27
|
+
}
|
|
28
|
+
export const ENV_NAME_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/;
|
|
29
|
+
export const MAX_ENV_DESCRIPTION_LENGTH = 300;
|
|
30
|
+
/** The usable env entries of a manifest. Bad ones are dropped. */
|
|
31
|
+
export function pluginEnvVars(value) {
|
|
32
|
+
if (!Array.isArray(value))
|
|
33
|
+
return [];
|
|
34
|
+
return value.filter((v) => typeof v === "object" &&
|
|
35
|
+
v !== null &&
|
|
36
|
+
typeof v.name === "string" &&
|
|
37
|
+
ENV_NAME_PATTERN.test(v.name) &&
|
|
38
|
+
typeof v.description === "string");
|
|
39
|
+
}
|
package/dist/frontend.d.ts
CHANGED
|
@@ -816,6 +816,15 @@ export interface DesktopApi {
|
|
|
816
816
|
/** Called when the desktop app connects to or leaves a remote server. */
|
|
817
817
|
onRemoteServerChange: (listener: () => void) => Disposer;
|
|
818
818
|
}
|
|
819
|
+
/** Links into this plugin's docs, from the manifest's docs field. */
|
|
820
|
+
export interface DocsApi {
|
|
821
|
+
/** The docs link, or null when the manifest has none. */
|
|
822
|
+
readonly url: string | null;
|
|
823
|
+
/** A page of the docs ("setup"), with an optional anchor. */
|
|
824
|
+
page: (page?: string, anchor?: string) => string | null;
|
|
825
|
+
/** Opens a page in a new tab. Does nothing without a docs link. */
|
|
826
|
+
open: (page?: string, anchor?: string) => void;
|
|
827
|
+
}
|
|
819
828
|
export interface TermixAppInfo {
|
|
820
829
|
readonly pluginId: string;
|
|
821
830
|
readonly manifest: PluginManifest;
|
|
@@ -913,6 +922,8 @@ export interface TermixApp extends TermixAppInfo {
|
|
|
913
922
|
confirm: (options: ConfirmRequest) => Promise<boolean>;
|
|
914
923
|
/** The desktop app, when the frontend runs in it. */
|
|
915
924
|
desktop: DesktopApi;
|
|
925
|
+
/** This plugin's docs. Needs plugin API 1.2. */
|
|
926
|
+
docs: DocsApi;
|
|
916
927
|
/**
|
|
917
928
|
* Fires after this plugin's settings are saved from the settings screen or
|
|
918
929
|
* the host editor. Disposed automatically.
|
|
@@ -966,6 +977,7 @@ export interface SshAuthTypeInfo {
|
|
|
966
977
|
*/
|
|
967
978
|
export interface PluginHostBridge {
|
|
968
979
|
usePluginId: () => string;
|
|
980
|
+
useDocsUrl: (pluginId: string, page?: string, anchor?: string) => string | null;
|
|
969
981
|
useTranslation: (pluginId: string) => {
|
|
970
982
|
t: TranslateFn;
|
|
971
983
|
language: string;
|
|
@@ -1107,6 +1119,11 @@ export interface HostStatusInfo {
|
|
|
1107
1119
|
export declare function __setPluginHost(bridge: PluginHostBridge | null): void;
|
|
1108
1120
|
/** The id of the plugin that registered the component being rendered. */
|
|
1109
1121
|
export declare function usePluginId(): string;
|
|
1122
|
+
/**
|
|
1123
|
+
* A page of this plugin's docs, or null when the manifest has no docs link.
|
|
1124
|
+
* Pass it to PanelShell's docs prop or use DocsLink. Needs plugin API 1.2.
|
|
1125
|
+
*/
|
|
1126
|
+
export declare function useDocsUrl(page?: string, anchor?: string): string | null;
|
|
1110
1127
|
/**
|
|
1111
1128
|
* Translation bound to this plugin's namespace. Keys the plugin does not
|
|
1112
1129
|
* define fall back to core's shared strings (common.*, hosts.*).
|
package/dist/frontend.js
CHANGED
|
@@ -49,6 +49,14 @@ function requireHost() {
|
|
|
49
49
|
export function usePluginId() {
|
|
50
50
|
return requireHost().usePluginId();
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* A page of this plugin's docs, or null when the manifest has no docs link.
|
|
54
|
+
* Pass it to PanelShell's docs prop or use DocsLink. Needs plugin API 1.2.
|
|
55
|
+
*/
|
|
56
|
+
export function useDocsUrl(page, anchor) {
|
|
57
|
+
const bridge = requireHost();
|
|
58
|
+
return bridge.useDocsUrl(bridge.usePluginId(), page, anchor);
|
|
59
|
+
}
|
|
52
60
|
/**
|
|
53
61
|
* Translation bound to this plugin's namespace. Keys the plugin does not
|
|
54
62
|
* define fall back to core's shared strings (common.*, hosts.*).
|
|
@@ -21,7 +21,7 @@ var inFlight = null;
|
|
|
21
21
|
function load() {
|
|
22
22
|
if (cache) return Promise.resolve(cache);
|
|
23
23
|
if (inFlight) return inFlight;
|
|
24
|
-
inFlight = import("./rbac-api-
|
|
24
|
+
inFlight = import("./rbac-api-UJGGTQVA.js").then(({ getMyPermissions }) => getMyPermissions()).then((result) => {
|
|
25
25
|
cache = {
|
|
26
26
|
permissions: result.permissions ?? [],
|
|
27
27
|
isAdmin: !!result.isAdmin
|
|
@@ -3801,7 +3801,7 @@ var {
|
|
|
3801
3801
|
|
|
3802
3802
|
// src/ui/main-axios.ts
|
|
3803
3803
|
import { toast } from "sonner";
|
|
3804
|
-
import
|
|
3804
|
+
import i18n2 from "i18next";
|
|
3805
3805
|
|
|
3806
3806
|
// src/ui/lib/base-path.ts
|
|
3807
3807
|
function getBasePath() {
|
|
@@ -4998,12 +4998,13 @@ async function logActivity(type, hostId, hostName) {
|
|
|
4998
4998
|
}
|
|
4999
4999
|
|
|
5000
5000
|
// src/ui/api/rbac-api.ts
|
|
5001
|
+
import i18n from "i18next";
|
|
5001
5002
|
async function getSharingTarget(hostId, syncId) {
|
|
5002
5003
|
const api = await getConnectedRemoteApi();
|
|
5003
5004
|
if (!api || !syncId) return { api: rbacApi, hostId };
|
|
5004
5005
|
const remoteHostId = await resolveRemoteHostId(syncId);
|
|
5005
5006
|
if (remoteHostId === null) {
|
|
5006
|
-
throw new Error("
|
|
5007
|
+
throw new Error(i18n.t("errors.syncedHostMissing"));
|
|
5007
5008
|
}
|
|
5008
5009
|
return { api, hostId: remoteHostId };
|
|
5009
5010
|
}
|
|
@@ -5012,7 +5013,7 @@ async function getSharedCopyTarget(hostId, syncId) {
|
|
|
5012
5013
|
const api = await getConnectedRemoteApi();
|
|
5013
5014
|
const remoteHostId = api ? await resolveRemoteHostId(syncId) : null;
|
|
5014
5015
|
if (!api || remoteHostId === null) {
|
|
5015
|
-
throw new Error("
|
|
5016
|
+
throw new Error(i18n.t("errors.linkedServerUnreachable"));
|
|
5016
5017
|
}
|
|
5017
5018
|
return { api, hostId: remoteHostId };
|
|
5018
5019
|
}
|
|
@@ -5037,7 +5038,7 @@ async function updateRole(roleId, roleData) {
|
|
|
5037
5038
|
try {
|
|
5038
5039
|
const response = await rbacApi.put(`/rbac/roles/${roleId}`, roleData);
|
|
5039
5040
|
if (roleData.permissions) {
|
|
5040
|
-
const { notifyPermissionsChanged } = await import("./use-permissions-
|
|
5041
|
+
const { notifyPermissionsChanged } = await import("./use-permissions-P3DC7ZDR.js");
|
|
5041
5042
|
notifyPermissionsChanged();
|
|
5042
5043
|
}
|
|
5043
5044
|
return response.data;
|
|
@@ -5266,6 +5267,7 @@ async function getUserPreferences() {
|
|
|
5266
5267
|
}
|
|
5267
5268
|
|
|
5268
5269
|
// src/ui/main-axios.ts
|
|
5270
|
+
var TRUST_TOKEN_KEY = "termixTrustToken";
|
|
5269
5271
|
function getLoggerForService(serviceName) {
|
|
5270
5272
|
if (serviceName.includes("SSH") || serviceName.includes("ssh")) {
|
|
5271
5273
|
return sshLogger;
|
|
@@ -5292,7 +5294,6 @@ if (isElectron()) {
|
|
|
5292
5294
|
if (!localStorage.getItem(key)) {
|
|
5293
5295
|
electronSettingsCache.set(key, value);
|
|
5294
5296
|
localStorage.setItem(key, value);
|
|
5295
|
-
console.log(`[Electron] Loaded setting ${key} from main process`);
|
|
5296
5297
|
} else {
|
|
5297
5298
|
electronSettingsCache.set(key, localStorage.getItem(key));
|
|
5298
5299
|
}
|
|
@@ -5318,7 +5319,6 @@ function setCookie(name, value, days = 7) {
|
|
|
5318
5319
|
console.error(`[Electron] Failed to persist setting ${name}:`, err);
|
|
5319
5320
|
});
|
|
5320
5321
|
}
|
|
5321
|
-
console.log(`[Electron] Set setting: ${name}`);
|
|
5322
5322
|
} catch (error) {
|
|
5323
5323
|
console.error(`[Electron] Failed to set setting: ${name}`, error);
|
|
5324
5324
|
}
|
|
@@ -5340,7 +5340,6 @@ function getCookie(name) {
|
|
|
5340
5340
|
if (token) {
|
|
5341
5341
|
electronSettingsCache.set(name, token);
|
|
5342
5342
|
}
|
|
5343
|
-
console.log(`[Electron] Get setting: ${name} = ${token}`);
|
|
5344
5343
|
return token;
|
|
5345
5344
|
} catch (error) {
|
|
5346
5345
|
console.error(`[Electron] Failed to get setting: ${name}`, error);
|
|
@@ -5427,6 +5426,15 @@ function createApiInstance(baseURL, serviceName = "API") {
|
|
|
5427
5426
|
config.headers["Authorization"] = `Bearer ${jwt}`;
|
|
5428
5427
|
}
|
|
5429
5428
|
}
|
|
5429
|
+
const trustToken = localStorage.getItem(TRUST_TOKEN_KEY);
|
|
5430
|
+
const isLogin = config.url?.includes("/users/login") || /\/users\/auth\/[^/]+\/verify$/.test(config.url ?? "");
|
|
5431
|
+
if (trustToken && isLogin) {
|
|
5432
|
+
if (config.headers.set) {
|
|
5433
|
+
config.headers.set("X-Termix-Trust-Token", trustToken);
|
|
5434
|
+
} else {
|
|
5435
|
+
config.headers["X-Termix-Trust-Token"] = trustToken;
|
|
5436
|
+
}
|
|
5437
|
+
}
|
|
5430
5438
|
}
|
|
5431
5439
|
if (typeof window !== "undefined" && window.ReactNativeWebView) {
|
|
5432
5440
|
let platform = "Unknown";
|
|
@@ -5538,7 +5546,7 @@ function createApiInstance(baseURL, serviceName = "API") {
|
|
|
5538
5546
|
clearClientAuthState();
|
|
5539
5547
|
if (typeof window !== "undefined") {
|
|
5540
5548
|
console.warn("Session expired - please log in again");
|
|
5541
|
-
toast.warning(
|
|
5549
|
+
toast.warning(i18n2.t("errors.sessionExpiredLogin"));
|
|
5542
5550
|
window.dispatchEvent(new Event("termix:logout"));
|
|
5543
5551
|
}
|
|
5544
5552
|
dbHealthMonitor.reportSessionExpired();
|
|
@@ -5587,7 +5595,7 @@ function createRemoteOriginApiInstance(path) {
|
|
|
5587
5595
|
});
|
|
5588
5596
|
instance.interceptors.request.use(
|
|
5589
5597
|
async (config) => {
|
|
5590
|
-
const { getLinkedSession: getLinkedSession2 } = await import("./linked-server-
|
|
5598
|
+
const { getLinkedSession: getLinkedSession2 } = await import("./linked-server-RWBK427T.js");
|
|
5591
5599
|
const linked = await getLinkedSession2();
|
|
5592
5600
|
config.baseURL = linked ? `${linked.serverUrl}${path}` : "http://no-server-configured";
|
|
5593
5601
|
if (config.headers.set) {
|
|
@@ -6009,6 +6017,7 @@ export {
|
|
|
6009
6017
|
patchOpenTab,
|
|
6010
6018
|
parseCustomKeybindings,
|
|
6011
6019
|
getUserPreferences,
|
|
6020
|
+
TRUST_TOKEN_KEY,
|
|
6012
6021
|
setCookie,
|
|
6013
6022
|
getCookie,
|
|
6014
6023
|
markUserAuthenticated,
|