mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-11-06 02:25:24 +00:00
feat: add host sections, tab embeds and the operations client to the plugin ui
This commit is contained in:
1 parent
cc725c7889
commit
631f6ac9f7
12 files changed
+184
-3
No files matched your search
@@ -79,6 +79,34 @@ export function createPluginApi(pluginId: string): AxiosInstance {
|
||||
}) as AxiosInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* An axios client rooted at core's /operations routes: the action catalog,
|
||||
* runs, approvals, events, signals and cross host resources.
|
||||
*/
|
||||
export function createOperationsApi(): AxiosInstance {
|
||||
return new Proxy(authApi, {
|
||||
get(target, property, receiver) {
|
||||
const value = Reflect.get(target, property, receiver);
|
||||
if (typeof value !== "function") return value;
|
||||
if (
|
||||
property === "get" ||
|
||||
property === "delete" ||
|
||||
property === "post" ||
|
||||
property === "put" ||
|
||||
property === "patch"
|
||||
) {
|
||||
return (url: string, ...rest: unknown[]) =>
|
||||
(value as (...args: unknown[]) => unknown).call(
|
||||
target,
|
||||
`/operations${url.startsWith("/") ? url : `/${url}`}`,
|
||||
...rest,
|
||||
);
|
||||
}
|
||||
return value.bind(target);
|
||||
},
|
||||
}) as AxiosInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* A raw fetch on this plugin's /plugin-api mount point, for a response the
|
||||
* caller reads as a stream (server-sent events). axios buffers the whole
|
||||
|
||||
@@ -41,6 +41,10 @@ import {
|
||||
type KeybindingEditorProps,
|
||||
} from "@/shell/keybinding-registry";
|
||||
import { registerHostProtocol } from "@/sidebar/host-protocols";
|
||||
import {
|
||||
registerHostSection,
|
||||
type HostSectionRenderProps,
|
||||
} from "@/shell/host-sections";
|
||||
import {
|
||||
registerDashboardCard,
|
||||
type DashboardCardRenderProps,
|
||||
@@ -424,6 +428,21 @@ export function createPluginApp(
|
||||
);
|
||||
},
|
||||
|
||||
registerHostSection(section) {
|
||||
return track(
|
||||
registerHostSection({
|
||||
id: section.id,
|
||||
pluginId,
|
||||
titleKey: key(section.titleKey),
|
||||
order: section.order,
|
||||
when: hostCallback(section.when ?? (() => true)) as never,
|
||||
component: scoped(
|
||||
section.component as unknown as ComponentType<HostSectionRenderProps>,
|
||||
),
|
||||
}),
|
||||
);
|
||||
},
|
||||
|
||||
registerHostContextMenuItem(item) {
|
||||
return track(
|
||||
registerHostContextMenuItem({
|
||||
|
||||
@@ -823,3 +823,13 @@ export {
|
||||
rightDockableIds,
|
||||
useRailItems,
|
||||
} from "@/sidebar/rail-items";
|
||||
export {
|
||||
hostSectionsFor,
|
||||
type HostSectionDef,
|
||||
listHostSections,
|
||||
useHostSections,
|
||||
} from "@/shell/host-sections";
|
||||
export { HostTabEmbed } from "@/shell/HostTabEmbed";
|
||||
import { createOperationsApi } from "@/lib/plugin-transport";
|
||||
/** Core's /operations routes, with the user's own session. */
|
||||
export const operationsApi = createOperationsApi();
|
||||
@@ -471,6 +471,7 @@ export {
|
||||
resetHostContributions,
|
||||
} from "@/sidebar/host-contributions";
|
||||
export { registerHostProtocol } from "@/sidebar/host-protocols";
|
||||
export { registerHostSection, resetHostSections } from "@/shell/host-sections";
|
||||
export {
|
||||
RAIL_ITEMS,
|
||||
registerRailItem,
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { useMemo } from "react";
|
||||
import type { Host, Tab } from "@/types/ui-types";
|
||||
import type { TabShellCallbacks } from "@/shell/tab-registry";
|
||||
import { renderTabContent } from "@/shell/tabUtils";
|
||||
|
||||
/**
|
||||
* Draws a registered tab type inside another view, such as a host page, with
|
||||
* the same props the tab stage gives it. The embed has its own instance id, so
|
||||
* it never collides with the same tool opened as a normal tab.
|
||||
*/
|
||||
export function HostTabEmbed({
|
||||
host,
|
||||
type,
|
||||
label,
|
||||
data,
|
||||
isVisible = true,
|
||||
shell,
|
||||
}: {
|
||||
host: Host;
|
||||
type: string;
|
||||
label?: string;
|
||||
data?: Record<string, unknown>;
|
||||
isVisible?: boolean;
|
||||
shell: TabShellCallbacks;
|
||||
}) {
|
||||
const tab = useMemo<Tab>(
|
||||
() => ({
|
||||
id: `embed-${type}-${host.id}`,
|
||||
instanceId: `embed-${type}-${host.id}`,
|
||||
type,
|
||||
label: label ?? host.name ?? host.ip,
|
||||
host,
|
||||
openedAt: Date.now(),
|
||||
data,
|
||||
}),
|
||||
// A new host or tool is a new tab; label and data changes keep it.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[type, host.id],
|
||||
);
|
||||
return (
|
||||
<div className="h-full min-h-0 flex flex-col">
|
||||
{renderTabContent(tab, { shell, isVisible, isFocusedPane: isVisible })}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import type { ComponentType } from "react";
|
||||
import type { Host } from "@/types/ui-types";
|
||||
import { byOrderThenId, createRegistry } from "@/lib/registry";
|
||||
import type { TabShellCallbacks } from "@/shell/tab-registry";
|
||||
|
||||
export interface HostSectionRenderProps {
|
||||
host: Host;
|
||||
isVisible: boolean;
|
||||
shell: TabShellCallbacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* A card a plugin adds to a host page. The page itself belongs to a plugin;
|
||||
* core only keeps the list so any plugin can fill it.
|
||||
*/
|
||||
export interface HostSectionDef {
|
||||
id: string;
|
||||
pluginId?: string;
|
||||
titleKey: string;
|
||||
order?: number;
|
||||
when: (host: Host) => boolean;
|
||||
component: ComponentType<HostSectionRenderProps>;
|
||||
}
|
||||
|
||||
const sections = createRegistry<HostSectionDef>(byOrderThenId);
|
||||
|
||||
export const registerHostSection = sections.register;
|
||||
export const useHostSections = sections.useList;
|
||||
export const listHostSections = sections.list;
|
||||
export const resetHostSections = sections.reset;
|
||||
|
||||
export function hostSectionsFor(
|
||||
all: HostSectionDef[],
|
||||
host: Host,
|
||||
): HostSectionDef[] {
|
||||
return all.filter((section) => {
|
||||
try {
|
||||
return section.when(host);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user