mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 06:35:24 +00:00
feat: collapse plugin runtime to one tier behind a plugin SDK
This commit is contained in:
1 parent
6e058f6e9e
commit
4758016f41
96 files changed
+34631
-8551
No files matched your search
@@ -11,20 +11,30 @@ export interface PluginContributions {
|
||||
tabs?: PluginTabContribution[];
|
||||
}
|
||||
|
||||
/**
|
||||
* What GET /plugins returns.
|
||||
*
|
||||
* The admin-only fields are absent for a caller without
|
||||
* admin.plugins.manage: what a plugin may do, what it has been granted and
|
||||
* why it failed are operational details the shell does not need.
|
||||
*/
|
||||
export interface PluginSummary {
|
||||
id: string;
|
||||
name: string;
|
||||
version: string;
|
||||
tier: string;
|
||||
source: string;
|
||||
enabled: boolean;
|
||||
runtimeState: string;
|
||||
lastError: string | null;
|
||||
/** enabled | disabled | blocked | failed, or the loader's live state. */
|
||||
state: string;
|
||||
contributes: PluginContributions | null;
|
||||
/** Capabilities this plugin's manifest declares it may ask for. */
|
||||
permissions: string[];
|
||||
/** The subset of `permissions` an admin has actually granted. */
|
||||
grantedCapabilities: string[];
|
||||
|
||||
tier?: string;
|
||||
source?: string;
|
||||
/** Capabilities this plugin's manifest declares. Admins only. */
|
||||
capabilities?: string[];
|
||||
/** The subset of `capabilities` actually granted. Admins only. */
|
||||
grantedCapabilities?: string[];
|
||||
/** Why the plugin is blocked or failed. Admins only. */
|
||||
lastError?: string | null;
|
||||
}
|
||||
|
||||
export async function getPlugins(): Promise<PluginSummary[]> {
|
||||
|
||||
+89
-25
@@ -3988,31 +3988,7 @@
|
||||
"pluginPermissionGranted": "Granted",
|
||||
"pluginPermissionNotGranted": "Not granted",
|
||||
"pluginGrantFailed": "Failed to grant the capability",
|
||||
"pluginRevokeFailed": "Failed to revoke the capability",
|
||||
"pluginPermissionHostsRead": "Read hosts",
|
||||
"pluginPermissionHostsReadDesc": "See the list of your hosts and their non-secret details.",
|
||||
"pluginPermissionHostsWrite": "Modify hosts",
|
||||
"pluginPermissionHostsWriteDesc": "Create, edit or delete hosts on your behalf.",
|
||||
"pluginPermissionCredentialsUse": "Use stored credentials",
|
||||
"pluginPermissionCredentialsUseDesc": "Connect using a saved credential without you retyping it.",
|
||||
"pluginPermissionSshExec": "Run commands over SSH",
|
||||
"pluginPermissionSshExecDesc": "Open a connection to a host and run commands on it.",
|
||||
"pluginPermissionSshSftp": "Transfer files over SFTP",
|
||||
"pluginPermissionSshSftpDesc": "Read or write files on a host over SFTP.",
|
||||
"pluginPermissionStorageOwn": "Store its own data",
|
||||
"pluginPermissionStorageOwnDesc": "Save and read back its own settings and data.",
|
||||
"pluginPermissionStorageSecrets": "Store secrets",
|
||||
"pluginPermissionStorageSecretsDesc": "Save and read back sensitive values it manages itself.",
|
||||
"pluginPermissionNetworkOutbound": "Make outbound network requests",
|
||||
"pluginPermissionNetworkOutboundDesc": "Contact services outside this server.",
|
||||
"pluginPermissionEventsRead": "Subscribe to server events",
|
||||
"pluginPermissionEventsReadDesc": "React to things happening elsewhere in Termix.",
|
||||
"pluginPermissionNotifySend": "Send notifications",
|
||||
"pluginPermissionNotifySendDesc": "Deliver a notification through your configured channels.",
|
||||
"pluginPermissionUsersRead": "Read user accounts",
|
||||
"pluginPermissionUsersReadDesc": "See basic details about accounts on this server.",
|
||||
"pluginPermissionProcessSidecar": "Run a background process",
|
||||
"pluginPermissionProcessSidecarDesc": "Start and manage its own helper process."
|
||||
"pluginRevokeFailed": "Failed to revoke the capability"
|
||||
},
|
||||
"newUi": {
|
||||
"sidebar": {
|
||||
@@ -5136,5 +5112,93 @@
|
||||
"isolationUnavailable": "This browser cannot isolate web endpoints from your Termix session. Use a browser with credentialless iframe support.",
|
||||
"isolationNotice": "Embedded pages use an isolated session. For pages requiring storage or popup sign-in, use an isolated window in the desktop app.",
|
||||
"openIsolatedWindow": "Open isolated window"
|
||||
},
|
||||
"plugins": {
|
||||
"capabilities": {
|
||||
"credentials:read": {
|
||||
"title": "See your passwords and keys",
|
||||
"consequence": "It can read the stored secret for any host you can reach."
|
||||
},
|
||||
"ssh:connect": {
|
||||
"title": "Run commands on your servers",
|
||||
"consequence": "Any command, on hosts you connect it to, with your access."
|
||||
},
|
||||
"process:spawn": {
|
||||
"title": "Run programs on this machine",
|
||||
"consequence": "Programs run on the Termix server itself, not on your hosts."
|
||||
},
|
||||
"users:write": {
|
||||
"title": "Create and change user accounts",
|
||||
"consequence": "It can add users and change their roles."
|
||||
},
|
||||
"auth:provide": {
|
||||
"title": "Handle sign in",
|
||||
"consequence": "It can add a login method or a second factor."
|
||||
},
|
||||
"system:tls": {
|
||||
"title": "Manage certificates",
|
||||
"consequence": "It can request and replace the server certificate."
|
||||
},
|
||||
"hosts:write": {
|
||||
"title": "Create and change hosts",
|
||||
"consequence": "It can add hosts and edit the ones you already have."
|
||||
},
|
||||
"credentials:use": {
|
||||
"title": "Connect to your servers",
|
||||
"consequence": "It cannot see your passwords or keys."
|
||||
},
|
||||
"network:outbound": {
|
||||
"title": "Reach the internet",
|
||||
"consequence": "It can send requests to outside services."
|
||||
},
|
||||
"network:serve": {
|
||||
"title": "Accept connections",
|
||||
"consequence": "It opens a port on the Termix server."
|
||||
},
|
||||
"users:read": {
|
||||
"title": "See usernames and roles",
|
||||
"consequence": "Never password hashes, never two factor state."
|
||||
},
|
||||
"events:core": {
|
||||
"title": "Watch everything happening",
|
||||
"consequence": "Including activity from other users, not just yours."
|
||||
},
|
||||
"notify:send": {
|
||||
"title": "Send notifications",
|
||||
"consequence": "Through the channels you have already set up."
|
||||
},
|
||||
"audit:read": {
|
||||
"title": "Read the audit log",
|
||||
"consequence": "It can see who did what and when."
|
||||
},
|
||||
"desktop:window": {
|
||||
"title": "Open desktop windows",
|
||||
"consequence": "Only in the Termix desktop app."
|
||||
},
|
||||
"hosts:read": {
|
||||
"title": "See your host list",
|
||||
"consequence": "Names and addresses for hosts you can already see."
|
||||
},
|
||||
"db:own": {
|
||||
"title": "Store its own data",
|
||||
"consequence": "Kept in its own tables, separate from other plugins."
|
||||
},
|
||||
"kv:own": {
|
||||
"title": "Remember small settings",
|
||||
"consequence": "Kept separate from other plugins."
|
||||
},
|
||||
"secrets:own": {
|
||||
"title": "Store its own secrets",
|
||||
"consequence": "Encrypted by Termix. The plugin never holds the key."
|
||||
},
|
||||
"settings:read-core": {
|
||||
"title": "Read Termix settings",
|
||||
"consequence": "Server settings, not your hosts or credentials."
|
||||
},
|
||||
"ui:surface": {
|
||||
"title": "Add its own screens",
|
||||
"consequence": "Tabs, panels and settings you can hide later."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,18 +81,16 @@ export function AdminPluginsSection({
|
||||
key={plugin.id}
|
||||
label={plugin.name}
|
||||
badge={
|
||||
plugin.tier === "first-party"
|
||||
? t("admin.pluginBuiltIn")
|
||||
: undefined
|
||||
plugin.tier === "bundled" ? t("admin.pluginBuiltIn") : undefined
|
||||
}
|
||||
description={
|
||||
plugin.lastError
|
||||
? plugin.lastError
|
||||
: `v${plugin.version} · ${plugin.runtimeState}`
|
||||
: `v${plugin.version} · ${plugin.state}`
|
||||
}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{plugin.permissions.length > 0 && (
|
||||
{(plugin.capabilities?.length ?? 0) > 0 && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
|
||||
@@ -15,83 +15,18 @@ import {
|
||||
type PluginSummary,
|
||||
} from "@/api/plugins-api";
|
||||
import { AdminToggle } from "./AdminSettingsShared";
|
||||
import { CAPABILITY_CATALOG } from "@termix/plugin-sdk/capabilities";
|
||||
|
||||
/**
|
||||
* Every permission a plugin's manifest can declare, with the label and
|
||||
* one-line description shown here. process:transport-owner is left out on
|
||||
* purpose: it is reserved for first-party plugins and enforced by a hardcoded
|
||||
* allowlist, not something an admin grants, so showing a toggle for it would
|
||||
* be misleading. ui.* permissions describe what a plugin contributes to the
|
||||
* shell rather than a capability the broker gates, but they are still shown
|
||||
* here since the manifest can declare them and a plugin's grant list should
|
||||
* not silently hide part of what it asked for.
|
||||
* The catalog is the list. It is ordered worst first, so the riskiest thing a
|
||||
* plugin asked for is the first row an admin reads rather than something they
|
||||
* have to scroll for.
|
||||
*/
|
||||
const LABELED_PERMISSIONS: Array<{
|
||||
capability: string;
|
||||
labelKey: string;
|
||||
descriptionKey: string;
|
||||
}> = [
|
||||
{
|
||||
capability: "hosts.read",
|
||||
labelKey: "admin.pluginPermissionHostsRead",
|
||||
descriptionKey: "admin.pluginPermissionHostsReadDesc",
|
||||
},
|
||||
{
|
||||
capability: "hosts.write",
|
||||
labelKey: "admin.pluginPermissionHostsWrite",
|
||||
descriptionKey: "admin.pluginPermissionHostsWriteDesc",
|
||||
},
|
||||
{
|
||||
capability: "credentials.use",
|
||||
labelKey: "admin.pluginPermissionCredentialsUse",
|
||||
descriptionKey: "admin.pluginPermissionCredentialsUseDesc",
|
||||
},
|
||||
{
|
||||
capability: "ssh.exec",
|
||||
labelKey: "admin.pluginPermissionSshExec",
|
||||
descriptionKey: "admin.pluginPermissionSshExecDesc",
|
||||
},
|
||||
{
|
||||
capability: "ssh.sftp",
|
||||
labelKey: "admin.pluginPermissionSshSftp",
|
||||
descriptionKey: "admin.pluginPermissionSshSftpDesc",
|
||||
},
|
||||
{
|
||||
capability: "storage.own",
|
||||
labelKey: "admin.pluginPermissionStorageOwn",
|
||||
descriptionKey: "admin.pluginPermissionStorageOwnDesc",
|
||||
},
|
||||
{
|
||||
capability: "storage.secrets",
|
||||
labelKey: "admin.pluginPermissionStorageSecrets",
|
||||
descriptionKey: "admin.pluginPermissionStorageSecretsDesc",
|
||||
},
|
||||
{
|
||||
capability: "network.outbound",
|
||||
labelKey: "admin.pluginPermissionNetworkOutbound",
|
||||
descriptionKey: "admin.pluginPermissionNetworkOutboundDesc",
|
||||
},
|
||||
{
|
||||
capability: "events.read",
|
||||
labelKey: "admin.pluginPermissionEventsRead",
|
||||
descriptionKey: "admin.pluginPermissionEventsReadDesc",
|
||||
},
|
||||
{
|
||||
capability: "notify.send",
|
||||
labelKey: "admin.pluginPermissionNotifySend",
|
||||
descriptionKey: "admin.pluginPermissionNotifySendDesc",
|
||||
},
|
||||
{
|
||||
capability: "users.read",
|
||||
labelKey: "admin.pluginPermissionUsersRead",
|
||||
descriptionKey: "admin.pluginPermissionUsersReadDesc",
|
||||
},
|
||||
{
|
||||
capability: "process.sidecar",
|
||||
labelKey: "admin.pluginPermissionProcessSidecar",
|
||||
descriptionKey: "admin.pluginPermissionProcessSidecarDesc",
|
||||
},
|
||||
];
|
||||
const LABELED_PERMISSIONS = CAPABILITY_CATALOG.map((capability) => ({
|
||||
capability: capability.id,
|
||||
labelKey: capability.titleKey,
|
||||
descriptionKey: capability.consequenceKey,
|
||||
}));
|
||||
|
||||
export function PluginPermissionsDialog({
|
||||
plugin,
|
||||
@@ -109,10 +44,11 @@ export function PluginPermissionsDialog({
|
||||
|
||||
if (!plugin) return null;
|
||||
|
||||
const declared = plugin.capabilities ?? [];
|
||||
const rows = LABELED_PERMISSIONS.filter((row) =>
|
||||
plugin.permissions.includes(row.capability),
|
||||
declared.includes(row.capability),
|
||||
);
|
||||
const granted = new Set(plugin.grantedCapabilities);
|
||||
const granted = new Set(plugin.grantedCapabilities ?? []);
|
||||
|
||||
const toggle = async (capability: string, isGranted: boolean) => {
|
||||
setBusy(capability);
|
||||
|
||||
@@ -28,10 +28,10 @@ function plugin(overrides: Partial<PluginSummary> = {}): PluginSummary {
|
||||
id: "ssh-terminal",
|
||||
name: "SSH Terminal",
|
||||
version: "1.0.0",
|
||||
tier: "first-party",
|
||||
tier: "bundled",
|
||||
source: "bundled",
|
||||
enabled: true,
|
||||
runtimeState: "active",
|
||||
state: "active",
|
||||
lastError: null,
|
||||
contributes: {
|
||||
tabs: [
|
||||
@@ -43,7 +43,7 @@ function plugin(overrides: Partial<PluginSummary> = {}): PluginSummary {
|
||||
},
|
||||
],
|
||||
},
|
||||
permissions: [],
|
||||
capabilities: [],
|
||||
grantedCapabilities: [],
|
||||
...overrides,
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user