mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 06:35:24 +00:00
feat: finish the tailscale plugin, including SSH auth (B3)
This commit is contained in:
1 parent
c77301577f
commit
d2ff5fc78f
35 files changed
+1251
-526
No files matched your search
@@ -417,13 +417,21 @@ boundary and Suspense.
|
||||
Hooks: `useTranslation` (the plugin's namespace), `usePermission` (a short
|
||||
name resolves to `<id>.<name>`), `useSettings`, `useHost`, `useHosts`,
|
||||
`useCurrentUser`, `useTheme`, `useToast`, `usePluginApi`, `useTabs`,
|
||||
`useSshAuthTypes` (every SSH auth type the server knows, with its plugin). The SDK
|
||||
has no runtime dependencies: the hooks delegate to a host bridge core installs.
|
||||
`useSshAuthTypes` (every SSH auth type the server knows, with its plugin),
|
||||
`useSlotContributions` (the visible contributions to a slot another plugin
|
||||
owns, with their metadata, for a slot owner that builds a catalog from what
|
||||
was contributed rather than just rendering it in place - host-metrics uses it
|
||||
to list manager cards plugins added to `host-metrics.managers` next to its
|
||||
own). The SDK has no runtime dependencies: the hooks delegate to a host
|
||||
bridge core installs.
|
||||
|
||||
Slots core owns: `terminal.toolbar`, `terminal.dock`, `terminal.overlay`
|
||||
(declared by ssh-terminal), `onboarding.steps`, `onboarding.features`,
|
||||
`onboarding.workflow`, `hosts.importMenu`, `hosts.panel`, `proxmox.hostEditor`
|
||||
and `session.remoteDisplay`. Cross-plugin frontend calls go through actions:
|
||||
and `session.remoteDisplay`. `host-metrics.managers` (component slot,
|
||||
**B3**) is host-metrics's own: tailscale contributes its manager card there
|
||||
instead of host-metrics knowing tailscale exists, and host-metrics works with
|
||||
or without tailscale enabled. Cross-plugin frontend calls go through actions:
|
||||
`automations.list`, `fleets.list`, `session.remoteDisplay.token`.
|
||||
|
||||
**B2** added a live-terminal-session surface, for a plugin that needs to push
|
||||
@@ -610,6 +618,15 @@ ctx.auth.registerSshAuthProvider({
|
||||
},
|
||||
onKeyboardInteractive: (round, host) => null, // claim a prompt style
|
||||
onAuthFailed: (host, env, context) => undefined, // clear caches, retry once
|
||||
onBanner: (banner, host, env) => undefined,
|
||||
// claim an ssh2 "banner" event during the handshake, for a server that
|
||||
// holds the connection open pending an out-of-band step (Tailscale SSH
|
||||
// check mode). Return { action: "hold", timeoutMs, message, details? } to
|
||||
// extend the connect timeout and have the terminal send
|
||||
// "<type>_check_required" with `details` merged in, or
|
||||
// { action: "release", details? } once the banner says it is done, which
|
||||
// sends "<type>_check_completed". Undefined leaves the banner unhandled.
|
||||
// B3 (tailscale) is the only caller today.
|
||||
startInteraction: async (request) => {}, // start the browser step
|
||||
});
|
||||
```
|
||||
@@ -1539,7 +1556,7 @@ What the lint fence enforces today, in `eslint.config.mjs`:
|
||||
| Core importing a plugin backend | **Error** | 0 | - |
|
||||
| A plugin backend importing frontend code or `@/` | **Error** | 0 | - |
|
||||
| The shell importing plugin code | **Error** | 0 | - |
|
||||
| A plugin frontend importing core through `@/` | Warning | 107 files | D1 |
|
||||
| A plugin frontend importing core through `@/` | Warning | 108 files | D1 |
|
||||
| A plugin importing core by relative path | Warning | 67 files | D1 |
|
||||
| A plugin importing another plugin's source | Warning | 3 files | B18 |
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Finish list
|
||||
|
||||
Running list of things intentionally left unfinished by a Phase B/C step,
|
||||
so D0 (or an earlier owner named below) can pick them up. Remove a line once
|
||||
it's done.
|
||||
|
||||
- **B3 (tailscale):** `ManagerCardShell`/`ManagerSearch`-equivalent presentation
|
||||
is duplicated as `TailscaleManagerShell`/`TailscaleManagerSearch` in
|
||||
`plugins/tailscale/src/frontend/TailscaleManagerShell.tsx` rather than
|
||||
shared with host-metrics's originals. Promote a shared version into
|
||||
`@termix/plugin-sdk/ui` once a second manager-card plugin needs one, per the
|
||||
contract's "add to the SDK when a second plugin needs it" rule. Owner: D1.
|
||||
- **B3 (tailscale):** `plugins/tailscale/src/backend/host-metrics-manager.ts`
|
||||
and `routes.ts` still resolve hosts and access through core's
|
||||
`PermissionManager`/`resolveHostById`/`DataCrypto` by relative import
|
||||
instead of a `ctx.hosts`-style SDK API, because `ctx.hosts` is not built yet
|
||||
(status **B** in the ctx surface table). Same debt every other host-metrics
|
||||
manager already carries; not new, but tailscale now carries it too. Owner:
|
||||
whichever step builds `ctx.hosts`.
|
||||
@@ -448,6 +448,20 @@ export type PluginSshAuthOutcome =
|
||||
message: string;
|
||||
};
|
||||
|
||||
export type PluginSshBannerDecision =
|
||||
| {
|
||||
/** Hold the connect timeout open and wait for the out-of-band step. */
|
||||
action: "hold";
|
||||
timeoutMs: number;
|
||||
message: string;
|
||||
details?: Record<string, unknown>;
|
||||
}
|
||||
| {
|
||||
/** The out-of-band step finished; resume the normal connect timeout. */
|
||||
action: "release";
|
||||
details?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export interface PluginKeyboardInteractivePrompt {
|
||||
prompt: string;
|
||||
echo?: boolean;
|
||||
@@ -644,6 +658,18 @@ export interface PluginSshAuthProvider {
|
||||
methodNotAvailable: boolean;
|
||||
},
|
||||
) => PluginSshAuthOutcome | undefined;
|
||||
/**
|
||||
* Claims an ssh2 "banner" event during the handshake, for a server that
|
||||
* holds the connection open pending an out-of-band step (Tailscale SSH
|
||||
* check mode). A transport that can show this sends `<type>_check_required`
|
||||
* / `<type>_check_completed` over its socket, with `details` merged in.
|
||||
* Return nothing to leave the banner unhandled.
|
||||
*/
|
||||
onBanner?: (
|
||||
banner: string,
|
||||
host: PluginSshHost,
|
||||
env: PluginSshAuthEnv,
|
||||
) => PluginSshBannerDecision | undefined;
|
||||
/** Starts the browser step behind an interaction-required outcome. */
|
||||
startInteraction?: (request: {
|
||||
userId: string;
|
||||
|
||||
@@ -610,6 +610,7 @@ export interface PluginHostBridge {
|
||||
useTabs: () => TabsApi;
|
||||
invokeAction: (id: string, ...args: unknown[]) => Promise<unknown>;
|
||||
useSshAuthTypes: () => { types: SshAuthTypeInfo[]; loaded: boolean };
|
||||
useSlotContributions: (slotId: string) => SlotContribution[];
|
||||
}
|
||||
|
||||
let host: PluginHostBridge | null = null;
|
||||
@@ -712,6 +713,17 @@ export function useSshAuthTypes(): {
|
||||
return requireHost().useSshAuthTypes();
|
||||
}
|
||||
|
||||
/**
|
||||
* The visible contributions to a slot another plugin owns, with their
|
||||
* metadata (id, titleKey, component). For an owner that needs to build a
|
||||
* catalog from what was contributed rather than just render it in place, e.g.
|
||||
* host-metrics listing manager cards plugins added to "host-metrics.managers"
|
||||
* next to its own. Same permission and `when` filtering as ComponentSlot.
|
||||
*/
|
||||
export function useSlotContributions(slotId: string): SlotContribution[] {
|
||||
return requireHost().useSlotContributions(slotId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs an action another plugin registered, from anywhere in a plugin's code.
|
||||
* Resolves undefined when nobody registered it, so an optional dependency
|
||||
|
||||
@@ -111,6 +111,7 @@ const offline = () =>
|
||||
})),
|
||||
loaded: true,
|
||||
}),
|
||||
useSlotContributions: () => [],
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
Reference in new issue
Block a user