mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 14:45:24 +00:00
v26.10.3 (#1692)
* feat: shell controller, TabStage, layout and theme plugins with repair view * feat: move the classic layout into its own bundled plugin * feat: move color themes into a bundled themes plugin * fix: plugin tests and type checks work with a linked sdk * fix: home tab, layout switch and missing layout fallbacks for looks * chore: add classic layout and themes to the readme * chore: drop example plugin links from the plugin guide * feat: plugin filters by type, paged browse, hide plugins needing newer termix * feat: move desktop, status, import, audit and branding features into plugins * feat: host lock api so a plugin can load hosts from a config file (#1581) * chore: add hosts config to the plugin table * feat: login policy hook for plugins and optional https redirect (#1584) * chore: add login security to the plugin table * feat: api key socket tickets so a master can open remote sessions (#1654) * feat: sync moves to a plugin, desktop gets multiple accounts (#1580, #1654) * chore: add sync to the plugin table * feat: move the sync screens and wizard out of core into the plugin (#1580) * fix: add a neutral linked desktop hook for plugins (#1580) * fix: show the linked account's name and admin flag in the app (#1580) * feat: ssh transport providers, core ssh relay and plugin folders api * feat: support FIDO2 security keys through ssh agent * feat: add companion panels beside host tabs * feat: mTLS client certificates for desktop server links * feat: native web view for plugins in the desktop app * fix: detect the desktop link window by Electron's default user agent Fixes #1675 * fix: changelog for desktop link fix * fix: keep text primary keys in the plugin data export * fix: ship sharp binaries for Windows arm64 builds * feat: client cert on the sync link types (sdk 1.0.18) * chore: update translations * feat: themed title bar for the desktop app (#1690) * chore: changelog for the themed title bar * fix: draw the title bar in the sidebar color * feat: per host layout for the terminal panel bar * chore: sdk 1.0.19 for the panel bar layout * chore: bundle all 43 plugins for 26.10.3 * chore: update translations --------- Co-authored-by: SirHephaistos <60616553+SirHephaistos@users.noreply.github.com> Co-authored-by: Giovanny Aranda <garanda21@gmail.com>
This commit is contained in:
577 files changed
+76479
-101831
No files matched your search
@@ -248,6 +248,129 @@ export interface TabsApi extends Pick<
|
||||
onReady: (listener: () => void) => Disposer;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Layouts (plugin API 1.4)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* A whole-app layout. Core renders the chosen one inside its providers and an
|
||||
* error boundary; everything it shows comes from useShell(). Declare it in
|
||||
* manifest contributes.layouts and add the ui:layout capability.
|
||||
*/
|
||||
export interface LayoutContribution {
|
||||
id: string;
|
||||
/** Your own i18n key or plain text, the same as the manifest's label. */
|
||||
label: string;
|
||||
component: ComponentType;
|
||||
/** Shown on phones and narrow windows. The desktop app never uses it. */
|
||||
mobileComponent?: ComponentType;
|
||||
/** Theme to start with when nobody chose one. */
|
||||
defaultTheme?: string;
|
||||
}
|
||||
|
||||
/** A tab as a layout sees it. Splits are tabs too, with a `split` field. */
|
||||
export interface ShellTabState extends PluginTabRecord {
|
||||
instanceId: string;
|
||||
customLabel?: string;
|
||||
/** Set while the tab sits in a split pane. */
|
||||
parentSplitTabId?: string;
|
||||
openedAt?: number;
|
||||
}
|
||||
|
||||
/** What a dock region shows and how it is sized, in Normal-size pixels. */
|
||||
export interface ShellDockState {
|
||||
view: string | null;
|
||||
open: boolean;
|
||||
width: number;
|
||||
dragging: boolean;
|
||||
setView: (view: string | null) => void;
|
||||
setWidth: (width: number) => void;
|
||||
/** Starts a drag from the dock's edge, from a mousedown. */
|
||||
startResize: (event: { clientX: number; preventDefault: () => void }) => void;
|
||||
resetWidth: () => void;
|
||||
}
|
||||
|
||||
/** The live shell: state and actions, nothing drawn. Read with useShell(). */
|
||||
export interface ShellController {
|
||||
tabs: {
|
||||
list: ShellTabState[];
|
||||
/** What a tab bar shows: tabs not sitting in a split. */
|
||||
topLevel: ShellTabState[];
|
||||
activeId: string;
|
||||
/** True once saved tabs were restored after login. */
|
||||
ready: boolean;
|
||||
/** The user turned on tab numbers. */
|
||||
showNumbers: boolean;
|
||||
setActive: (id: string) => void;
|
||||
reorder: (topLevel: ShellTabState[]) => void;
|
||||
close: (id: string) => Promise<void>;
|
||||
rename: (id: string, label: string) => void;
|
||||
/** Reconnects or reloads a session tab. */
|
||||
refresh: (id: string) => void;
|
||||
reconnectAll: () => void;
|
||||
openSingleton: (type: string) => string | undefined;
|
||||
};
|
||||
split: {
|
||||
/** The split shown now, or null. */
|
||||
activeId: string | null;
|
||||
focusedPaneId: string | null;
|
||||
/** Every split with its panes, for a split menu. */
|
||||
summaries: Array<{
|
||||
id: string;
|
||||
label: string;
|
||||
full: boolean;
|
||||
panes: Array<{
|
||||
id: string;
|
||||
index: number;
|
||||
tabId: string | null;
|
||||
label: string | null;
|
||||
}>;
|
||||
}>;
|
||||
activeFull: boolean;
|
||||
/** Split a tab, apply a preset, unsplit, add to a pane. */
|
||||
handleAction: (action: Record<string, unknown> & { kind: string }) => void;
|
||||
};
|
||||
hosts: {
|
||||
list: PluginHostRecord[];
|
||||
loading: boolean;
|
||||
loaded: boolean;
|
||||
reload: () => Promise<void>;
|
||||
};
|
||||
regions: {
|
||||
left: ShellDockState & { view: string };
|
||||
right: ShellDockState;
|
||||
/** What a rail click does: toggles or switches the left dock. */
|
||||
railClick: (view: string) => void;
|
||||
openInRightDock: (view: string) => void;
|
||||
toggleRightDock: () => void;
|
||||
};
|
||||
palette: { isOpen: boolean; open: () => void; close: () => void };
|
||||
fullscreen: { active: boolean; toggle: () => void };
|
||||
/** The same callbacks tabs and panels get. */
|
||||
actions: ShellApi;
|
||||
env: {
|
||||
isMobile: boolean;
|
||||
isElectron: boolean;
|
||||
user: { username: string; isAdmin: boolean };
|
||||
permissions: string[];
|
||||
branding: {
|
||||
appName: string | null;
|
||||
tagline: string | null;
|
||||
logo: string | null;
|
||||
};
|
||||
};
|
||||
/** Core registries, as hooks a layout calls while rendering. */
|
||||
registries: {
|
||||
useRailItems: () => ReadonlyArray<{ id: string } & Record<string, unknown>>;
|
||||
usePanels: () => ReadonlyArray<{ id: string } & Record<string, unknown>>;
|
||||
useTabTypes: () => ReadonlyArray<{ id: string } & Record<string, unknown>>;
|
||||
useDashboardCards: () => ReadonlyArray<
|
||||
{ id: string } & Record<string, unknown>
|
||||
>;
|
||||
};
|
||||
logout: (options?: { manual?: boolean }) => void;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Registrations
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -530,8 +653,16 @@ export interface HostBadgeContribution {
|
||||
id: string;
|
||||
when: (host: PluginHostRecord) => boolean;
|
||||
component: ComponentType<{ host: PluginHostRecord }>;
|
||||
/** Lower comes first. Ties sort by id. */
|
||||
order?: number;
|
||||
}
|
||||
|
||||
/** The dot or stripe that shows a host's status on its row. */
|
||||
export type HostStatusIndicatorContribution = HostBadgeContribution;
|
||||
|
||||
/** A control in the host editor's toolbar. */
|
||||
export type HostEditorToolbarItemContribution = HostBadgeContribution;
|
||||
|
||||
export interface HostContextMenuItemContribution {
|
||||
id: string;
|
||||
titleKey: string;
|
||||
@@ -683,6 +814,66 @@ export interface SettingsComponentProps {
|
||||
running: boolean;
|
||||
}
|
||||
|
||||
/** What a companion starts with. Read from the plugin's own settings. */
|
||||
export interface CompanionDefaults {
|
||||
/** Open by itself the first time a matching tab shows. */
|
||||
open?: boolean;
|
||||
side?: "left" | "right" | "bottom";
|
||||
/** Pixels of width, or height at the bottom. */
|
||||
size?: number;
|
||||
/** Show the edge button that opens and closes it. Default true. */
|
||||
edgeButton?: boolean;
|
||||
}
|
||||
|
||||
/** A panel a tab type can show beside it, for a picker. */
|
||||
export interface CompanionInfo {
|
||||
/** The id registerCompanionLayout's hidden and order lists use. */
|
||||
id: string;
|
||||
titleKey: string;
|
||||
icon?: IconComponent;
|
||||
}
|
||||
|
||||
/** How a host wants the panel bar beside a tab. */
|
||||
export interface CompanionLayout {
|
||||
/** False removes the bar and its panels. */
|
||||
enabled?: boolean;
|
||||
/** Where a panel opens until the user moves it. */
|
||||
side?: "left" | "right" | "bottom";
|
||||
/** Companion ids in bar order. */
|
||||
order?: string[];
|
||||
/** Companion ids left out. */
|
||||
hidden?: string[];
|
||||
}
|
||||
|
||||
export interface CompanionProps {
|
||||
/** The tab it sits beside. */
|
||||
tab: PluginTabRecord;
|
||||
host?: PluginHostRecord;
|
||||
/** The host in the shape the connection APIs take. */
|
||||
sshHost?: Record<string, unknown>;
|
||||
/** The tab's surface handle (see TabHandle), read at call time. */
|
||||
getHandle: () => TabHandle | null;
|
||||
side: "left" | "right" | "bottom";
|
||||
/** False while the tab is in the background or the panel is closed. */
|
||||
isVisible: boolean;
|
||||
close: () => void;
|
||||
setSide: (side: "left" | "right" | "bottom") => void;
|
||||
}
|
||||
|
||||
export interface CompanionOptions {
|
||||
icon?: IconComponent;
|
||||
titleKey?: string;
|
||||
order?: number;
|
||||
/** Tab types it applies to. Any tab with a host when empty. */
|
||||
tabTypes?: string[];
|
||||
when?: (tab: PluginTabRecord, host?: PluginHostRecord) => boolean;
|
||||
/**
|
||||
* A hook returning the starting state. It runs in the plugin's scope, so it
|
||||
* can read the plugin's settings.
|
||||
*/
|
||||
useDefaults?: () => CompanionDefaults;
|
||||
}
|
||||
|
||||
export type ActionHandler = (...args: never[]) => unknown;
|
||||
|
||||
export interface ActionSlotDefinition {
|
||||
@@ -715,10 +906,11 @@ export type OnboardingMode = "full" | "partial" | "rerun";
|
||||
* Where a plugin step sits in onboarding. "setup" comes right after the
|
||||
* appearance steps, for things that need doing before the feature works
|
||||
* (like pointing at a guacd server). "explore" (the default) comes after
|
||||
* that, "security" after the account security step. Onboarding is for
|
||||
* that, "security" after the account security step. "start" comes before
|
||||
* the appearance steps, for choices that shape how the rest looks. Onboarding is for
|
||||
* choices, not for explaining features.
|
||||
*/
|
||||
export type OnboardingSection = "setup" | "explore" | "security";
|
||||
export type OnboardingSection = "start" | "setup" | "explore" | "security";
|
||||
|
||||
export interface OnboardingStepProps {
|
||||
/**
|
||||
@@ -761,6 +953,16 @@ export interface OnboardingStepContribution {
|
||||
permission?: string;
|
||||
section?: OnboardingSection;
|
||||
order?: number;
|
||||
/**
|
||||
* Exact place in the flow, 1 to 99, instead of a section. Core steps sit at
|
||||
* 10 (plugins), 20, 25, 30 (preset), 40 (appearance) and 90 (security).
|
||||
*/
|
||||
position?: number;
|
||||
/**
|
||||
* Older step keys this step took over, like "preset". People who already
|
||||
* saw one of them are not shown this step again.
|
||||
*/
|
||||
replaces?: string[];
|
||||
when?: (context: {
|
||||
isAdmin: boolean;
|
||||
isDesktop: boolean;
|
||||
@@ -926,12 +1128,40 @@ export interface TermixApp extends TermixAppInfo {
|
||||
component: ComponentType<TabProps>,
|
||||
options?: TabOptions,
|
||||
) => Disposer;
|
||||
/**
|
||||
* A panel that sits beside any host tab the options match (a terminal, a
|
||||
* desktop, ...), bound to that tab's host. The user opens, resizes and moves
|
||||
* it; the tab keeps running underneath.
|
||||
*/
|
||||
registerCompanion: (
|
||||
id: string,
|
||||
component: ComponentType<CompanionProps>,
|
||||
options?: CompanionOptions,
|
||||
) => Disposer;
|
||||
/**
|
||||
* Lets a plugin decide, per host, how the panel bar looks on one tab type.
|
||||
* Read from the host payload, so it must not call hooks.
|
||||
*/
|
||||
registerCompanionLayout: (
|
||||
tabType: string,
|
||||
resolve: (host: PluginHostRecord | undefined) => CompanionLayout,
|
||||
) => Disposer;
|
||||
/** Opens a companion on a tab, or closes it when it is showing. */
|
||||
toggleCompanion: (tab: PluginTabRecord, id?: string) => void;
|
||||
registerHostEditorSection: (
|
||||
section: HostEditorSectionContribution,
|
||||
) => Disposer;
|
||||
registerHostAction: (action: HostActionContribution) => Disposer;
|
||||
registerHostProtocol: (protocol: HostProtocolContribution) => Disposer;
|
||||
registerHostBadge: (badge: HostBadgeContribution) => Disposer;
|
||||
/** Draws a host row's status. A layout renders every registered one. */
|
||||
registerHostStatusIndicator: (
|
||||
indicator: HostStatusIndicatorContribution,
|
||||
) => Disposer;
|
||||
/** Adds a control to the host editor's toolbar. */
|
||||
registerHostEditorToolbarItem: (
|
||||
item: HostEditorToolbarItemContribution,
|
||||
) => Disposer;
|
||||
registerHostContextMenuItem: (
|
||||
item: HostContextMenuItemContribution,
|
||||
) => Disposer;
|
||||
@@ -988,6 +1218,12 @@ export interface TermixApp extends TermixAppInfo {
|
||||
registerSshAuthEditor: (editor: SshAuthEditorContribution) => Disposer;
|
||||
registerLoginMethod: (method: LoginMethodContribution) => Disposer;
|
||||
registerSecondFactorUI: (factor: SecondFactorContribution) => Disposer;
|
||||
/**
|
||||
* A whole-app layout. Declared in manifest contributes.layouts with the
|
||||
* ui:layout capability. Only the chosen layout's plugin is loaded, so
|
||||
* register it in activate. Needs plugin API 1.4.
|
||||
*/
|
||||
registerLayout: (layout: LayoutContribution) => Disposer;
|
||||
|
||||
/**
|
||||
* Translates a key from the plugin's own locales, for code outside a
|
||||
@@ -1122,6 +1358,7 @@ export interface PluginHostBridge {
|
||||
useHosts: () => { hosts: PluginHostRecord[]; loaded: boolean };
|
||||
useCurrentUser: () => CurrentUser | null;
|
||||
useTheme: () => { theme: "light" | "dark" };
|
||||
useShell: () => ShellController | null;
|
||||
toast: ToastApi;
|
||||
getApi: (pluginId: string) => PluginApiClient;
|
||||
getApiFor: (pluginId: string, origin: unknown) => PluginApiClient;
|
||||
@@ -1138,6 +1375,7 @@ export interface PluginHostBridge {
|
||||
hostProtocols: (host: PluginHostRecord) => string[];
|
||||
useHostStatus: (hostId: number | undefined) => HostStatusInfo | null;
|
||||
useHostActions: () => HostActionContribution[];
|
||||
useCompanions: (tabType: string) => CompanionInfo[];
|
||||
useActivityTypes: () => string[];
|
||||
activityTarget: (type: string) => ActivityTargetInfo | undefined;
|
||||
useExtensions: (pointId: string) => ExtensionContribution[];
|
||||
@@ -1372,6 +1610,14 @@ export function useTheme(): { theme: "light" | "dark" } {
|
||||
return requireHost().useTheme();
|
||||
}
|
||||
|
||||
/**
|
||||
* The live shell, for a layout. Null outside the signed-in app. Needs plugin
|
||||
* API 1.4.
|
||||
*/
|
||||
export function useShell(): ShellController | null {
|
||||
return requireHost().useShell();
|
||||
}
|
||||
|
||||
export function useToast(): ToastApi {
|
||||
return requireHost().toast;
|
||||
}
|
||||
@@ -1539,6 +1785,14 @@ export function useHostActions(): HostActionContribution[] {
|
||||
return requireHost().useHostActions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Every panel that can open beside a tab of this type, for a settings screen
|
||||
* that lets the user hide or reorder them. Reactive.
|
||||
*/
|
||||
export function useCompanions(tabType: string): CompanionInfo[] {
|
||||
return requireHost().useCompanions(tabType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recent-activity types every registered tab claims (registerTab's
|
||||
* activityTypes), core's and plugins'. For a filter that lists what a user
|
||||
|
||||
Reference in new issue
Block a user