Skip to content

Commit 4917c91

Browse files
authored
Merge pull request #22 from mononen/home-cards
Homepage Card setup
2 parents 6b1fe31 + c177160 commit 4917c91

16 files changed

Lines changed: 826 additions & 11 deletions

File tree

backend/pkg/plugin/resource/controller.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,11 @@ func (c *controller) StartConnection(pluginID, connectionID string) (types.Conne
405405
}
406406

407407
if conn.Status == types.ConnectionStatusConnected && conn.Connection != nil {
408+
// Stamp the refresh time so the frontend TTL check reflects the real state.
409+
conn.Connection.LastRefresh = time.Now()
410+
if conn.Connection.ExpiryTime == 0 {
411+
conn.Connection.ExpiryTime = types.ConnectionDefaultExpiryTime
412+
}
408413
c.connsMu.Lock()
409414
c.connections[pluginID] = mergeConnections(c.connections[pluginID], []types.Connection{*conn.Connection})
410415
c.connsMu.Unlock()
@@ -435,6 +440,8 @@ func (c *controller) StopConnection(pluginID, connectionID string) (types.Connec
435440
return types.Connection{}, err
436441
}
437442

443+
// Clear LastRefresh so the frontend TTL check shows disconnected.
444+
conn.LastRefresh = time.Time{}
438445
c.connsMu.Lock()
439446
c.connections[pluginID] = mergeConnections(c.connections[pluginID], []types.Connection{conn})
440447
c.connsMu.Unlock()

packages/omniviewdev-runtime/src/types/extensions.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export type RegisterOpts<Props> = {
6868
/**
6969
* The component to render
7070
*/
71-
component: React.Component<Props>;
71+
component: React.ComponentType<Props>;
7272

7373
/**
7474
* Additional optional metadata for the component
@@ -118,6 +118,17 @@ export type CreateExtensionPointOptions<ComponentProps> = ExtensionPointSettings
118118
matcher?: (context: ExtensionRenderContext, item: Registration<ComponentProps>) => boolean;
119119
};
120120

121+
/**
122+
* A registration entry that a plugin exports to register a component into an extension point.
123+
* The host app processes these at plugin load time.
124+
*/
125+
export type ExtensionRegistration<Props = any> = {
126+
/** The ID of the extension point to register into */
127+
extensionPointId: string;
128+
/** The registration options for the component */
129+
registration: RegisterOpts<Props>;
130+
};
131+
121132
/**
122133
* A registry to hold an extension points registered extensions, providing type safe access to the components.
123134
*
@@ -272,7 +283,7 @@ export class ExtensionPointStore<ComponentProps> {
272283
/**
273284
* Provide the components that match the given context.
274285
*/
275-
provide(context: ExtensionRenderContext): Array<React.Component<ComponentProps>> {
286+
provide(context: ExtensionRenderContext): Array<React.ComponentType<ComponentProps>> {
276287
if (this._settings.disabled) {
277288
return [];
278289
}
@@ -293,7 +304,7 @@ export class ExtensionPointStore<ComponentProps> {
293304
if (ids) {
294305
let components = ids.map((id) => this._extensions.get(id))
295306
.filter((v): v is Registration<ComponentProps> => !!v)
296-
.map((v) => v.component);
307+
.map((v) => v.component as React.ComponentType<ComponentProps>);
297308

298309
if (this._settings.order) {
299310
components = this.reorderComponents(components, ids, this._settings.order);
@@ -308,7 +319,7 @@ export class ExtensionPointStore<ComponentProps> {
308319
// if the order is set, reorder the components
309320
let components = ids.map((id) => this._extensions.get(id))
310321
.filter((v): v is Registration<ComponentProps> => !!v)
311-
.map((v) => v.component);
322+
.map((v) => v.component as React.ComponentType<ComponentProps>);
312323

313324
// Reorder components if order is set
314325
if (this._settings.order) {
@@ -318,9 +329,9 @@ export class ExtensionPointStore<ComponentProps> {
318329
return components;
319330
}
320331

321-
reorderComponents(components: Array<React.Component<ComponentProps>>, ids: string[], order: string[]): Array<React.Component<ComponentProps>> {
332+
reorderComponents(components: Array<React.ComponentType<ComponentProps>>, ids: string[], order: string[]): Array<React.ComponentType<ComponentProps>> {
322333
const idToComponentMap = new Map(ids.map((id, index) => [id, components[index]]));
323-
return order.map((id) => idToComponentMap.get(id)).filter((component): component is React.Component<ComponentProps> => !!component);
334+
return order.map((id) => idToComponentMap.get(id)).filter((component): component is React.ComponentType<ComponentProps> => !!component);
324335
}
325336

326337
/**
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import type React from 'react';
2+
3+
/**
4+
* Types for the omniview/home/card extension point.
5+
* Plugins that contribute homepage cards import these types.
6+
*/
7+
8+
export type HomepageCardSection = 'recent' | 'favorites' | 'folders' | (string & {});
9+
10+
export type HomepageCardConfig = {
11+
sections: HomepageCardSection[];
12+
maxItems?: number; // per section, default 5
13+
};
14+
15+
export type HomepageCardProps = {
16+
pluginID: string;
17+
config: HomepageCardConfig;
18+
};
19+
20+
export type HomepageCardMeta = {
21+
label: string;
22+
description?: string;
23+
defaultConfig: HomepageCardConfig;
24+
defaultWidth?: 'small' | 'medium' | 'large';
25+
/** Optional icon component rendered in the card header instead of the text label. */
26+
icon?: React.ComponentType<{ size?: number; color?: string }>;
27+
};

packages/omniviewdev-runtime/src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from './app'
22
export * from './plugin'
33
export * from './extensions'
44
export * from './watch'
5+
export * from './homepage'

plugins/kubernetes

Submodule kubernetes updated 110 files

ui/features/extensions/builtin.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import sidebar from './sidebar/extensions';
22
import logviewer from './logviewer/extensions';
33
import dashboard from './dashboard/extensions';
4+
import homepage from './homepage/extensions';
45

5-
export default [...sidebar, ...logviewer, ...dashboard]
6+
export default [...sidebar, ...logviewer, ...dashboard, ...homepage]
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { type CreateExtensionPointOptions } from '@omniviewdev/runtime';
2+
import { type HomepageCardProps } from './types';
3+
4+
const card: CreateExtensionPointOptions<HomepageCardProps> = {
5+
owner: 'core',
6+
id: 'omniview/home/card',
7+
name: 'Homepage Card',
8+
description: 'Card displayed on the application home page.',
9+
mode: 'multiple',
10+
};
11+
12+
export default [card];
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Re-export from the runtime package so the UI and plugins share the same types.
2+
export type {
3+
HomepageCardSection,
4+
HomepageCardConfig,
5+
HomepageCardProps,
6+
HomepageCardMeta,
7+
} from '@omniviewdev/runtime';

ui/features/plugins/api/loader.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react';
22
import builtInPlugins from './builtins';
3-
import { PluginWindow, DrawerContext, DrawerFactory } from '@omniviewdev/runtime';
3+
import { PluginWindow, DrawerContext, DrawerFactory, type ExtensionRegistration } from '@omniviewdev/runtime';
44
import { EXTENSION_REGISTRY } from '../../extensions/store';
55
import { registerPlugin, registerPluginSidebars, registerPluginDrawers } from '../PluginManager';
66
import { SystemJS } from './systemjs';
@@ -165,6 +165,23 @@ export async function loadAndRegisterPlugin(
165165
registerPluginDrawers(pluginID, drawers);
166166
}
167167

168+
// Register extension point contributions if the plugin exports them
169+
const { extensionRegistrations } = exports as { extensionRegistrations?: ExtensionRegistration[] };
170+
if (extensionRegistrations) {
171+
for (const { extensionPointId, registration } of extensionRegistrations) {
172+
const ep = EXTENSION_REGISTRY.getExtensionPoint(extensionPointId);
173+
if (ep) {
174+
try {
175+
ep.register(registration);
176+
} catch (e) {
177+
console.warn(`[loader] Failed to register extension "${registration.id}" into "${extensionPointId}":`, e);
178+
}
179+
} else {
180+
console.warn(`[loader] Extension point "${extensionPointId}" not found — skipping "${registration.id}"`);
181+
}
182+
}
183+
}
184+
168185
console.debug(`[loader] loadAndRegisterPlugin "${pluginID}" complete — emitting recalc_routes`, { plugin: pluginID });
169186
EventsEmit('core/window/recalc_routes');
170187
}

ui/features/router/routes.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import InstalledPlugins from '../../pages/plugins/InstalledPlugins';
1313
import Settings from '../../pages/settings';
1414

1515
// Home
16-
import Welcome from '../../pages/welcome';
16+
import Home from '../../pages/home';
1717

1818
// Trivy
1919
import TrivyLayout from '../../pages/trivy';
@@ -33,7 +33,7 @@ export const coreRoutes: RouteObject[] = [
3333
{
3434
path: '/',
3535
index: true,
36-
Component: Welcome,
36+
Component: Home,
3737
},
3838
{
3939
path: 'plugins',

0 commit comments

Comments
 (0)