diff --git a/packages/plugin-sdk/src/testing.ts b/packages/plugin-sdk/src/testing.ts index 67b041562..04f606147 100644 --- a/packages/plugin-sdk/src/testing.ts +++ b/packages/plugin-sdk/src/testing.ts @@ -2104,8 +2104,6 @@ export interface RenderedPluginApp { hostProtocols: () => string[]; hostEditorSections: () => string[]; dashboardCards: () => string[]; - /** Ids this plugin added to an extension point. */ - extensions: (pointId: string) => string[]; settingsComponents: () => string[]; slot: (slotId: string) => string[]; actions: () => string[]; @@ -2135,13 +2133,6 @@ export interface RenderedPluginApp { renderTab: (type: string, props?: Record) => HTMLElement; renderPanel: (id: string, props?: Record) => HTMLElement; renderDashboardCard: (id: string) => HTMLElement; - /** Renders one component of a contributed extension, "view" by default. */ - renderExtension: ( - pointId: string, - id: string, - component?: string, - props?: Record, - ) => HTMLElement; renderHostEditorSection: ( id: string, props?: Record, diff --git a/plugins/alerts/locales/en.json b/plugins/alerts/locales/en.json index 9d7514f0d..fe8ebdadd 100644 --- a/plugins/alerts/locales/en.json +++ b/plugins/alerts/locales/en.json @@ -160,13 +160,5 @@ "save": "Save", "saving": "Saving...", "delete": "Delete" - }, - "widget": { - "name": "Alert Feed", - "description": "Your latest alerts, with a quick way to mark them read", - "maxItems": "Max items", - "showRead": "Show read alerts", - "allClear": "All clear", - "loading": "Loading..." } } diff --git a/plugins/alerts/manifest.json b/plugins/alerts/manifest.json index c96c97764..696e6cb9a 100644 --- a/plugins/alerts/manifest.json +++ b/plugins/alerts/manifest.json @@ -141,8 +141,7 @@ ] } ] - }, - "uses": ["homepage.widgets"] + } }, "locales": "locales", "platforms": ["linux", "win32", "darwin"] diff --git a/plugins/alerts/src/frontend/AlertFeedWidget.tsx b/plugins/alerts/src/frontend/AlertFeedWidget.tsx deleted file mode 100644 index f56ba881a..000000000 --- a/plugins/alerts/src/frontend/AlertFeedWidget.tsx +++ /dev/null @@ -1,197 +0,0 @@ -import { useEffect, useState } from "react"; -import { - AlertCircle, - AlertTriangle, - Bell, - Check, - CheckCircle, - Info, -} from "lucide-react"; -import { useTranslation } from "@termix/plugin-sdk/frontend"; -import { Input } from "@termix/plugin-sdk/ui"; -import type { AlertItem } from "../types"; -import type { AlertsApi } from "./api"; -import { timeAgo } from "./format"; -import { useAlertsVersion, type AlertsStore } from "./store"; - -export interface AlertFeedConfig { - maxItems: number; - showRead: boolean; - /** 2.8 name for showRead, still found in saved homepages. */ - showAcknowledged?: boolean; -} - -export const DEFAULT_ALERT_FEED_CONFIG: AlertFeedConfig = { - maxItems: 10, - showRead: false, -}; - -function SeverityIcon({ severity }: { severity: AlertItem["severity"] }) { - if (severity === "critical") - return ; - if (severity === "warning") - return ; - if (severity === "success") - return ; - return ; -} - -export function showsRead(config: Partial): boolean { - return config.showRead ?? config.showAcknowledged ?? false; -} - -export function createAlertFeedWidget(api: AlertsApi, store: AlertsStore) { - function AlertFeedWidget({ - widget, - config, - }: { - widget: { title?: string | null }; - config: AlertFeedConfig; - }) { - const { t, language } = useTranslation(); - const version = useAlertsVersion(store); - const maxItems = Math.max(1, config.maxItems || 10); - const showRead = showsRead(config); - const [items, setItems] = useState(null); - - useEffect(() => { - let cancelled = false; - api - .items({ unread: showRead ? undefined : true, limit: maxItems }) - .then((result) => { - if (!cancelled) setItems(result.items.slice(0, maxItems)); - }) - .catch(() => { - if (!cancelled) setItems([]); - }); - return () => { - cancelled = true; - }; - }, [maxItems, showRead, version]); - - const markRead = async (id: number) => { - try { - await api.setRead([id]); - setItems((prev) => - (prev ?? []).flatMap((item) => - item.id !== id - ? [item] - : showRead - ? [{ ...item, readAt: new Date().toISOString() }] - : [], - ), - ); - } catch { - // the next reload shows the real state - } - }; - - if (items === null) { - return ( -
- {t("widget.loading")} -
- ); - } - - return ( -
-
- - {widget.title || t("widget.name")} -
- {items.length === 0 ? ( -
- - - {t("widget.allClear")} - -
- ) : ( -
- {items.map((item) => ( -
- -
- - {item.title} - - - {timeAgo(item.createdAt, language)} - -
- {!item.readAt && ( - - )} -
- ))} -
- )} -
- ); - } - return AlertFeedWidget; -} - -export function AlertFeedEditForm({ - config, - onChange, -}: { - config: AlertFeedConfig; - onChange: (config: AlertFeedConfig) => void; -}) { - const { t } = useTranslation(); - return ( -
-
- - - onChange({ - ...config, - maxItems: Math.min(50, Math.max(1, Number(e.target.value))), - }) - } - className="h-8 text-xs" - /> -
- -
- ); -} - -/** The widget's name in the homepage add menu, in this plugin's locale. */ -export function AlertFeedLabel({ part }: { part: "name" | "description" }) { - const { t } = useTranslation(); - return <>{t(part === "name" ? "widget.name" : "widget.description")}; -} diff --git a/plugins/alerts/src/frontend/index.tsx b/plugins/alerts/src/frontend/index.tsx index 9b447d9e4..d649fdb19 100644 --- a/plugins/alerts/src/frontend/index.tsx +++ b/plugins/alerts/src/frontend/index.tsx @@ -10,16 +10,6 @@ import { AlertsView } from "./AlertsView"; import { createSectionRequests } from "./sections"; import { InboxView } from "./InboxView"; import { createAlertsStore, useUnreadCount } from "./store"; -import { createAlertsApi } from "./api"; -import { - AlertFeedEditForm, - AlertFeedLabel, - DEFAULT_ALERT_FEED_CONFIG, - createAlertFeedWidget, -} from "./AlertFeedWidget"; - -// The homepage grid step, in pixels. -const GRID_SIZE = 30; const VIEW_ID = "alerts"; @@ -81,22 +71,6 @@ export function activate(app: TermixApp): void { component: Toaster as ComponentType>, }); - app.registerExtension("homepage.widgets", { - id: "alert_feed", - name: "Alert Feed", - description: "Your latest alerts", - category: "monitoring", - icon: , - defaultConfig: DEFAULT_ALERT_FEED_CONFIG, - defaultSize: { w: GRID_SIZE * 10, h: GRID_SIZE * 10 }, - minSize: { w: GRID_SIZE * 6, h: GRID_SIZE * 5 }, - components: { - view: createAlertFeedWidget(createAlertsApi(app.api), store) as never, - editForm: AlertFeedEditForm as never, - label: AlertFeedLabel as never, - }, - }); - app.registerAction("alerts.open", () => openTab()); app.registerAction("alerts.openChannels", () => { sections.request("channels"); diff --git a/plugins/alerts/tests/frontend/activate.test.tsx b/plugins/alerts/tests/frontend/activate.test.tsx index d6fd726b9..897af3fb1 100644 --- a/plugins/alerts/tests/frontend/activate.test.tsx +++ b/plugins/alerts/tests/frontend/activate.test.tsx @@ -148,50 +148,3 @@ describe("the alerts panel", () => { ); }); }); - -describe("the alert feed homepage widget", () => { - it("registers under the 2.8 widget id", async () => { - rendered = await renderWithApp(plugin, { manifest, locales }); - expect(rendered.registered.extensions("homepage.widgets")).toEqual([ - "alert_feed", - ]); - }); - - it("shows unread alerts and marks one read", async () => { - const { api, calls } = stubApi(); - rendered = await renderWithApp(plugin, { manifest, locales, api }); - const view = rendered.renderExtension( - "homepage.widgets", - "alert_feed", - "view", - { - widget: { title: null }, - config: { maxItems: 5, showRead: false }, - }, - ); - await waitFor(() => - expect(view.textContent).toContain("Certificate renewal failed"), - ); - expect(calls).toContainEqual(["GET", "/items?unread=true&limit=5"]); - fireEvent.click(view.querySelector('[title="Mark as read"]')!); - await waitFor(() => - expect(calls).toContainEqual([ - "POST", - "/items/read", - { ids: [2], read: true }, - ]), - ); - }); - - it("reads the 2.8 showAcknowledged option", async () => { - const { api, calls } = stubApi(); - rendered = await renderWithApp(plugin, { manifest, locales, api }); - rendered.renderExtension("homepage.widgets", "alert_feed", "view", { - widget: {}, - config: { maxItems: 3, showAcknowledged: true }, - }); - await waitFor(() => - expect(calls).toContainEqual(["GET", "/items?limit=3"]), - ); - }); -}); diff --git a/plugins/homepage/locales/en.json b/plugins/homepage/locales/en.json index 760b3a445..272eaa126 100644 --- a/plugins/homepage/locales/en.json +++ b/plugins/homepage/locales/en.json @@ -18,7 +18,6 @@ "homepage": { "title": "Homepage", "addWidget": "Add Widget", - "noWidgetsInCategory": "No widgets in this category", "editWidget": "Edit Widget", "zoomIn": "Zoom In", "zoomOut": "Zoom Out", @@ -169,7 +168,8 @@ "noServices": "No services set up", "configureLocation": "Set a location in the widget settings", "bannerTextPlaceholder": "Banner text", - "serviceNamePlaceholder": "My service" + "serviceNamePlaceholder": "My service", + "noWidgetsInCategory": "No widgets in this category" }, "permissions": { "use": { diff --git a/plugins/homepage/src/frontend/dialogs/AddWidgetMenu.tsx b/plugins/homepage/src/frontend/dialogs/AddWidgetMenu.tsx index 53e1d8773..20c027b4a 100644 --- a/plugins/homepage/src/frontend/dialogs/AddWidgetMenu.tsx +++ b/plugins/homepage/src/frontend/dialogs/AddWidgetMenu.tsx @@ -136,19 +136,9 @@ export function AddWidgetMenu({ state, onAdd, onClose }: AddWidgetMenuProps) { > {type.icon}
- - {type.labelComponent ? ( - - ) : ( - type.name - )} - + {type.name} - {type.labelComponent ? ( - - ) : ( - type.description - )} + {type.description}
diff --git a/plugins/homepage/src/frontend/types.ts b/plugins/homepage/src/frontend/types.ts index 77cc6dd43..bcf97fd37 100644 --- a/plugins/homepage/src/frontend/types.ts +++ b/plugins/homepage/src/frontend/types.ts @@ -365,8 +365,6 @@ export interface WidgetTypeDefinition> { minSize: { w: number; h: number }; component: React.ComponentType>; editFormComponent?: React.ComponentType>; - /** Renders the translated name or description in the add menu. */ - labelComponent?: React.ComponentType<{ part: "name" | "description" }>; } export interface DragState { diff --git a/plugins/homepage/src/frontend/widgets/WidgetRegistry.ts b/plugins/homepage/src/frontend/widgets/WidgetRegistry.ts index 92f5f3e73..033b263f0 100644 --- a/plugins/homepage/src/frontend/widgets/WidgetRegistry.ts +++ b/plugins/homepage/src/frontend/widgets/WidgetRegistry.ts @@ -49,7 +49,6 @@ function toDefinition( ...(rest as unknown as WidgetTypeDefinition), component: view, editFormComponent: components?.editForm, - labelComponent: components?.label, }; } diff --git a/src/ui/plugin-host/testing-host.tsx b/src/ui/plugin-host/testing-host.tsx index c7f997c85..1ac73791f 100644 --- a/src/ui/plugin-host/testing-host.tsx +++ b/src/ui/plugin-host/testing-host.tsx @@ -71,7 +71,6 @@ import { } from "./shell-bridge"; import { PluginViewPlaceholder } from "./PluginViewPlaceholder"; import { resolveLayoutTabTarget } from "@/shell/shell-layout"; -import { getExtension, listExtensions } from "./extension-registry"; import type { WorkspaceTabSnapshot } from "@/types/ui-types"; function recordingShell(calls: ShellCall[]): TabShellCallbacks { @@ -237,8 +236,6 @@ export async function renderPlugin( mine(hostEditorSectionList()).map((section) => section.id), dashboardCards: () => mine(registeredDashboardCardList()).map((card) => card.id), - extensions: (pointId) => - mine(listExtensions(pointId)).map((extension) => extension.id), settingsComponents: () => listSettingsComponents() .filter((key) => key.startsWith(`${pluginId}:`)) @@ -321,16 +318,6 @@ export async function renderPlugin( ); }, - renderExtension(pointId, id, component = "view", props = {}) { - const extension = getExtension(pointId, id); - const Component = - extension?.pluginId === pluginId - ? extension.components?.[component] - : undefined; - if (!Component) missing(`an extension "${component}" component`, id); - return wrap(); - }, - renderDashboardCard(id) { const card = getRegisteredDashboardCard(id) ?? missing("a card", id); const Component = card.component;