1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Merge branch 'extensions-api' into enable-extensions-on-main

This commit is contained in:
Jari Kolehmainen 2020-10-06 16:52:15 +03:00
commit f97fbdf9ed
13 changed files with 88 additions and 28 deletions

View File

@ -0,0 +1,24 @@
import { DynamicPageType, LensRendererExtension, PageStore } from "@lens/ui-extensions";
import { examplePage, ExtensionIcon } from "./page"
export default class ExampleExtension extends LensRendererExtension {
onActivate() {
console.log('EXAMPLE EXTENSION RENDERER: ACTIVATED', this.getMeta());
}
registerPages(pageStore: PageStore) {
this.registerPage(pageStore, {
type: DynamicPageType.CLUSTER,
path: "/extension-example",
title: "Example Extension",
components: {
Page: examplePage(this),
MenuIcon: ExtensionIcon,
}
})
}
onDeactivate() {
console.log('EXAMPLE EXTENSION RENDERER: DEACTIVATED', this.getMeta());
}
}

View File

@ -1,10 +1,14 @@
import { LensExtension } from "@lens/extensions"; import { LensMainExtension } from "@lens/extensions";
export default class ExampleExtensionMain extends LensExtension { export default class ExampleExtensionMain extends LensMainExtension {
onActivate() { onActivate() {
console.log('EXAMPLE EXTENSION MAIN: ACTIVATED', this.getMeta()); console.log('EXAMPLE EXTENSION MAIN: ACTIVATED', this.getMeta());
} }
onEvent(evt: any) {
//
}
onDeactivate() { onDeactivate() {
console.log('EXAMPLE EXTENSION MAIN: DEACTIVATED', this.getMeta()); console.log('EXAMPLE EXTENSION MAIN: DEACTIVATED', this.getMeta());
} }

View File

@ -10,7 +10,7 @@ export default class ExampleExtension extends LensRendererExtension {
this.registerPage(pageStore, { this.registerPage(pageStore, {
type: DynamicPageType.CLUSTER, type: DynamicPageType.CLUSTER,
path: "/extension-example", path: "/extension-example",
menuTitle: "Example Extension", title: "Example Extension",
components: { components: {
Page: examplePage(this), Page: examplePage(this),
MenuIcon: ExtensionIcon, MenuIcon: ExtensionIcon,

View File

@ -0,0 +1,15 @@
import React from "react";
import { cssNames } from "../renderer/utils";
import { TabLayout } from "../renderer/components/layout/tab-layout";
import { PageRegistration } from "./page-store"
export class DynamicPage extends React.Component<{ page: PageRegistration }> {
render() {
const { className, components: { Page }, subPages = [] } = this.props.page;
return (
<TabLayout className={cssNames("ExtensionPage", className)} tabs={subPages}>
<Page/>
</TabLayout>
)
}
}

View File

@ -1,5 +1,5 @@
// Lens-extensions api developer's kit // Lens-extensions api developer's kit
export type { LensExtensionRuntimeEnv, PageStore } from "./lens-runtime"; export type { LensExtensionRuntimeEnv, PageStore } from "./lens-renderer-runtime";
// APIs // APIs
export * from "./lens-renderer-extension" export * from "./lens-renderer-extension"

View File

@ -1,4 +1,4 @@
import type { PageStore } from "./lens-runtime" import type { PageStore } from "./lens-renderer-runtime"
import type { PageRegistration } from "./page-store" import type { PageRegistration } from "./page-store"
import { LensExtension } from "./lens-extension" import { LensExtension } from "./lens-extension"

View File

@ -0,0 +1,21 @@
// Lens extension runtime params available to renderer extensions after activation
import logger from "../main/logger";
import { navigate } from "../renderer/navigation";
import { PageRegistration } from "./page-store";
export interface PageStore {
register(params: PageRegistration): () => void
}
export interface LensExtensionRuntimeEnv {
logger: typeof logger;
navigate: typeof navigate;
}
export function getLensRuntime(): LensExtensionRuntimeEnv {
return {
logger,
navigate
}
}

View File

@ -1,11 +1,6 @@
// Lens extension runtime params available to extensions after activation // Lens extension runtime params available to extensions after activation
import logger from "../main/logger"; import logger from "../main/logger";
import { PageRegistration } from "./page-store";
export interface PageStore {
register(params: PageRegistration): () => void
}
export interface LensExtensionRuntimeEnv { export interface LensExtensionRuntimeEnv {
logger: typeof logger; logger: typeof logger;
@ -13,6 +8,6 @@ export interface LensExtensionRuntimeEnv {
export function getLensRuntime(): LensExtensionRuntimeEnv { export function getLensRuntime(): LensExtensionRuntimeEnv {
return { return {
logger, logger
} }
} }

View File

@ -2,18 +2,24 @@
import { computed, observable } from "mobx"; import { computed, observable } from "mobx";
import React from "react"; import React from "react";
import type { IconProps } from "../renderer/components/icon"; import { RouteProps } from "react-router";
import { IconProps } from "../renderer/components/icon";
import { IClassName } from "../renderer/utils";
import { TabRoute } from "../renderer/components/layout/tab-layout";
export enum DynamicPageType { export enum DynamicPageType {
GLOBAL = "lens-scope", GLOBAL = "lens-scope",
CLUSTER = "cluster-view-scope", CLUSTER = "cluster-view-scope",
} }
export interface PageRegistration { export interface PageRegistration extends RouteProps {
className?: IClassName;
url?: string; // initial url to be used for building menus and tabs, otherwise "path" applied by default
path: string; // route-path path: string; // route-path
menuTitle: string; title: React.ReactNode; // used in sidebar's & tabs-layout
type: DynamicPageType; type: DynamicPageType;
components: PageComponents; components: PageComponents;
subPages?: (PageRegistration & TabRoute)[];
} }
export interface PageComponents { export interface PageComponents {

View File

@ -37,6 +37,7 @@ import logger from "../../main/logger";
import { clusterIpc } from "../../common/cluster-ipc"; import { clusterIpc } from "../../common/cluster-ipc";
import { webFrame } from "electron"; import { webFrame } from "electron";
import { pageStore } from "../../extensions/page-store"; import { pageStore } from "../../extensions/page-store";
import { DynamicPage } from "../../extensions/dynamic-page";
import { extensionLoader } from "../../extensions/extension-loader"; import { extensionLoader } from "../../extensions/extension-loader";
import { getLensRuntime } from "../../extensions/lens-runtime"; import { getLensRuntime } from "../../extensions/lens-runtime";
@ -77,8 +78,8 @@ export class App extends React.Component {
<Route component={CustomResources} {...crdRoute}/> <Route component={CustomResources} {...crdRoute}/>
<Route component={UserManagement} {...usersManagementRoute}/> <Route component={UserManagement} {...usersManagementRoute}/>
<Route component={Apps} {...appsRoute}/> <Route component={Apps} {...appsRoute}/>
{pageStore.clusterPages.map(({ path, components: { Page } }) => { {pageStore.clusterPages.map(page => {
return <Route key={path} path={path} component={Page}/> return <Route {...page} key={page.path} render={() => <DynamicPage page={page}/>}/>
})} })}
<Redirect exact from="/" to={this.startURL}/> <Redirect exact from="/" to={this.startURL}/>
<Route component={NotFound}/> <Route component={NotFound}/>

View File

@ -22,7 +22,7 @@ import { ConfirmDialog } from "../confirm-dialog";
import { clusterIpc } from "../../../common/cluster-ipc"; import { clusterIpc } from "../../../common/cluster-ipc";
import { clusterViewURL } from "./cluster-view.route"; import { clusterViewURL } from "./cluster-view.route";
import { DragDropContext, Droppable, Draggable, DropResult, DroppableProvided, DraggableProvided } from "react-beautiful-dnd"; import { DragDropContext, Droppable, Draggable, DropResult, DroppableProvided, DraggableProvided } from "react-beautiful-dnd";
import { dynamicPages } from "../../../extensions/register-page"; import { pageStore } from "../../../extensions/page-store";
interface Props { interface Props {
className?: IClassName; className?: IClassName;
@ -156,7 +156,7 @@ export class ClustersMenu extends React.Component<Props> {
)} )}
</div> </div>
<div className="dynamic-pages"> <div className="dynamic-pages">
{dynamicPages.globalPages.map(({ path, components: { MenuIcon } }) => { {pageStore.globalPages.map(({ path, components: { MenuIcon } }) => {
return <MenuIcon key={path} onClick={() => navigate(path)}/> return <MenuIcon key={path} onClick={() => navigate(path)}/>
})} })}
</div> </div>

View File

@ -28,7 +28,7 @@ import { CrdList, crdResourcesRoute, crdRoute, crdURL } from "../+custom-resourc
import { CustomResources } from "../+custom-resources/custom-resources"; import { CustomResources } from "../+custom-resources/custom-resources";
import { navigation } from "../../navigation"; import { navigation } from "../../navigation";
import { isAllowedResource } from "../../../common/rbac" import { isAllowedResource } from "../../../common/rbac"
import { dynamicPages } from "../../../extensions/register-page"; import { pageStore } from "../../../extensions/page-store";
const SidebarContext = React.createContext<SidebarContextValue>({ pinned: false }); const SidebarContext = React.createContext<SidebarContextValue>({ pinned: false });
type SidebarContextValue = { type SidebarContextValue = {
@ -184,14 +184,14 @@ export class Sidebar extends React.Component<Props> {
> >
{this.renderCustomResources()} {this.renderCustomResources()}
</SidebarNavItem> </SidebarNavItem>
{dynamicPages.clusterPages.map(({ path, menuTitle, components: { MenuIcon } }) => { {pageStore.clusterPages.map(({ path, title, components: { MenuIcon } }) => {
return ( return (
<SidebarNavItem <SidebarNavItem
key={path} key={path}
id={`extension-${path}`} id={`extension-${path}`}
url={path} url={path}
routePath={path} routePath={path}
text={menuTitle} text={title}
icon={<MenuIcon/>} icon={<MenuIcon/>}
/> />
) )

View File

@ -1,5 +1,4 @@
import "./tab-layout.scss"; import "./tab-layout.scss";
import React, { ReactNode } from "react"; import React, { ReactNode } from "react";
import { matchPath, RouteProps } from "react-router-dom"; import { matchPath, RouteProps } from "react-router-dom";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
@ -7,7 +6,6 @@ import { cssNames } from "../../utils";
import { Tab, Tabs } from "../tabs"; import { Tab, Tabs } from "../tabs";
import { ErrorBoundary } from "../error-boundary"; import { ErrorBoundary } from "../error-boundary";
import { navigate, navigation } from "../../navigation"; import { navigate, navigation } from "../../navigation";
import { getHostedCluster } from "../../../common/cluster-store";
export interface TabRoute extends RouteProps { export interface TabRoute extends RouteProps {
title: React.ReactNode; title: React.ReactNode;
@ -23,17 +21,13 @@ interface Props {
export const TabLayout = observer(({ className, contentClass, tabs, children }: Props) => { export const TabLayout = observer(({ className, contentClass, tabs, children }: Props) => {
const routePath = navigation.location.pathname; const routePath = navigation.location.pathname;
const cluster = getHostedCluster();
if (!cluster) {
return null; // fix: skip render when removing active (visible) cluster
}
return ( return (
<div className={cssNames("TabLayout", className)}> <div className={cssNames("TabLayout", className)}>
{tabs && ( {tabs && (
<Tabs center onChange={(url) => navigate(url)}> <Tabs center onChange={(url) => navigate(url)}>
{tabs.map(({ title, path, url, ...routeProps }) => { {tabs.map(({ title, path, url, ...routeProps }) => {
const isActive = !!matchPath(routePath, { path, ...routeProps }); const isActive = !!matchPath(routePath, { path, ...routeProps });
return <Tab key={url} label={title} value={url} active={isActive} />; return <Tab key={url} label={title} value={url} active={isActive}/>;
})} })}
</Tabs> </Tabs>
)} )}