feat: ship docker as a first-party in-process plugin

Moves docker backend and frontend into plugins/docker/, alongside
ssh-terminal. Adds a host editor tab seam, a dashboard card registry,
and a build step to compile plugin backends into dist.
This commit is contained in:
LukeGus committed 2026-09-21 09:55:37 -05:00
1 parent c94a453f1e
commit a2479c19b0
34 files changed
+729 -143

No files matched your search

@@ -0,0 +1,36 @@
import { afterEach, describe, expect, it } from "vitest";
import {
registerDashboardCard,
registeredDashboardCardList,
unregisterDashboardCard,
} from "@/dashboard/dashboard-cards-registry";
const FAKE_CARD_ID = "__test_plugin_dashboard_card__";
describe("registerDashboardCard seam", () => {
afterEach(() => {
unregisterDashboardCard(FAKE_CARD_ID);
});
it("is not registered by default", () => {
expect(registeredDashboardCardList()).toEqual([]);
});
it("registers and unregisters a plugin dashboard card", () => {
registerDashboardCard({
id: FAKE_CARD_ID,
titleKey: "nav.fakePluginItem",
render: () => null,
});
expect(registeredDashboardCardList().map((c) => c.id)).toContain(
FAKE_CARD_ID,
);
unregisterDashboardCard(FAKE_CARD_ID);
expect(registeredDashboardCardList().map((c) => c.id)).not.toContain(
FAKE_CARD_ID,
);
});
});
@@ -0,0 +1,59 @@
import { afterEach, describe, expect, it } from "vitest";
import { Puzzle } from "lucide-react";
import {
getRegisteredHostEditorTab,
makeHostSshSubTabs,
registerHostEditorTab,
registeredHostEditorTabList,
unregisterHostEditorTab,
} from "@/sidebar/HostManagerTabs";
const FAKE_TAB_ID = "__test_plugin_host_editor_tab__";
function FakeTabComponent() {
return null;
}
describe("registerHostEditorTab seam", () => {
afterEach(() => {
unregisterHostEditorTab(FAKE_TAB_ID);
});
it("is not registered by default", () => {
expect(getRegisteredHostEditorTab(FAKE_TAB_ID)).toBeUndefined();
expect(registeredHostEditorTabList()).toEqual([]);
});
it("registers and unregisters a plugin host editor tab", () => {
registerHostEditorTab({
id: FAKE_TAB_ID,
labelKey: "nav.fakePluginItem",
icon: <Puzzle />,
component: FakeTabComponent,
});
expect(getRegisteredHostEditorTab(FAKE_TAB_ID)?.component).toBe(
FakeTabComponent,
);
expect(registeredHostEditorTabList().map((t) => t.id)).toContain(
FAKE_TAB_ID,
);
unregisterHostEditorTab(FAKE_TAB_ID);
expect(getRegisteredHostEditorTab(FAKE_TAB_ID)).toBeUndefined();
});
it("appends a registered tab after the built-in SSH sub-tabs", () => {
registerHostEditorTab({
id: FAKE_TAB_ID,
labelKey: "nav.fakePluginItem",
icon: <Puzzle />,
component: FakeTabComponent,
});
const ids: string[] = makeHostSshSubTabs((key) => key).map((tab) => tab.id);
expect(ids).toContain(FAKE_TAB_ID);
expect(ids.indexOf(FAKE_TAB_ID)).toBe(ids.length - 1);
});
});