support-page extension -- part 2

Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
Roman committed 2020-10-20 17:19:18 +03:00
1 parent 822d9b4974
commit ef66f67b39
38 files changed
+281 -168

No files matched your search

-5
View File
@@ -21,11 +21,6 @@ export const rendererDir = path.join(contextDir, "src/renderer");
export const htmlTemplate = path.resolve(rendererDir, "template.html");
export const sassCommonVars = path.resolve(rendererDir, "components/vars.scss");
// Extensions
export const extensionsLibName = `${appName}-extensions.api`
export const extensionsRendererLibName = `${appName}-extensions-renderer.api`
export const extensionsDir = path.join(contextDir, "src/extensions");
// Special runtime paths
defineGlobal("__static", {
get() {
+5 -2
View File
@@ -1,14 +1,17 @@
// Lens-extensions api developer's kit
export type { LensExtensionRuntimeEnv } from "./lens-runtime";
export type { LensExtensionMainRuntimeEnv } from "./lens-runtime";
export * from "./lens-main-extension"
// APIs
import * as EventBus from "./main-api/event-bus"
import * as Store from "./main-api/stores"
import * as Util from "./main-api/utils"
import * as Registry from "./main-api/registries"
export {
EventBus,
Store,
Util
Util,
Registry,
}
+13 -8
View File
@@ -1,13 +1,17 @@
import type { ExtensionId, LensExtension, ExtensionManifest, ExtensionModel } from "./lens-extension"
import type { LensMainExtension } from "./lens-main-extension"
import type { LensRendererExtension } from "./lens-renderer-extension"
import { broadcastIpc } from "../common/ipc"
import type { LensExtensionRuntimeEnv } from "./lens-runtime"
import type { LensExtensionMainRuntimeEnv } from "./lens-runtime"
import type { LensExtensionRendererRuntimeEnv } from "./lens-renderer-runtime"
import path from "path"
import { broadcastIpc } from "../common/ipc"
import { observable, reaction, toJS, } from "mobx"
import logger from "../main/logger"
import { app, remote, ipcRenderer } from "electron"
import { pageRegistry } from "./page-registry";
import { appPreferenceRegistry } from "./app-preference-registry"
import { menuRegistry } from "./menu-registry";
import { statusBarRegistry } from "./status-bar-registry";
export interface InstalledExtension extends ExtensionModel {
manifestPath: string;
@@ -35,14 +39,14 @@ export class ExtensionLoader {
}
}
loadOnClusterRenderer(getLensRuntimeEnv: () => LensExtensionRuntimeEnv) {
loadOnClusterRenderer(getLensRuntimeEnv: () => LensExtensionRendererRuntimeEnv) {
logger.info('[EXTENSIONS-LOADER]: load on cluster renderer')
this.autoloadExtensions(getLensRuntimeEnv, (instance: LensRendererExtension) => {
instance.registerPages(pageRegistry)
})
}
loadOnMainRenderer(getLensRuntimeEnv: () => LensExtensionRuntimeEnv) {
loadOnMainRenderer(getLensRuntimeEnv: () => LensExtensionRendererRuntimeEnv) {
logger.info('[EXTENSIONS-LOADER]: load on main renderer')
this.autoloadExtensions(getLensRuntimeEnv, (instance: LensRendererExtension) => {
instance.registerPages(pageRegistry)
@@ -50,14 +54,15 @@ export class ExtensionLoader {
})
}
loadOnMain(getLensRuntimeEnv: () => LensExtensionRuntimeEnv) {
loadOnMain(getLensRuntimeEnv: () => LensExtensionMainRuntimeEnv) {
logger.info('[EXTENSIONS-LOADER]: load on main')
this.autoloadExtensions(getLensRuntimeEnv, (instance: LensExtension) => {
// todo
this.autoloadExtensions(getLensRuntimeEnv, (instance: LensMainExtension) => {
instance.registerAppMenus(menuRegistry);
instance.registerStatusBarIcon(statusBarRegistry);
})
}
protected autoloadExtensions(getLensRuntimeEnv: () => LensExtensionRuntimeEnv, callback: (instance: LensExtension) => void) {
protected autoloadExtensions(getLensRuntimeEnv: () => object, callback: (instance: LensExtension) => void) {
return reaction(() => this.extensions.toJS(), (installedExtensions) => {
for(const [id, ext] of installedExtensions) {
let instance = this.instances.get(ext.manifestPath)
+4 -2
View File
@@ -1,5 +1,5 @@
// Lens-extensions api developer's kit
export type { LensExtensionRuntimeEnv } from "./lens-renderer-runtime"
export type { LensExtensionRendererRuntimeEnv } from "./lens-renderer-runtime"
// APIs
export * from "./lens-extension"
@@ -10,11 +10,13 @@ import * as EventBus from "./main-api/event-bus"
import * as K8sApi from "./renderer-api/k8s-api"
import * as Registry from "./renderer-api/registries"
import * as Util from "./main-api/utils"
import * as CommonVars from "../common/vars";
export {
Component,
EventBus,
K8sApi,
Registry,
Util
Util,
CommonVars,
}
+2 -3
View File
@@ -1,4 +1,3 @@
import type { LensExtensionRuntimeEnv } from "./lens-runtime";
import { readJsonSync } from "fs-extra";
import { action, observable, toJS } from "mobx";
import logger from "../main/logger";
@@ -34,7 +33,7 @@ export class LensExtension implements ExtensionModel {
@observable manifest: ExtensionManifest;
@observable manifestPath: string;
@observable isEnabled = false;
@observable.ref runtime: LensExtensionRuntimeEnv;
@observable.ref runtime: any;
constructor(model: ExtensionModel, manifest: ExtensionManifest) {
this.importModel(model, manifest);
@@ -56,7 +55,7 @@ export class LensExtension implements ExtensionModel {
// mock
}
async enable(runtime: LensExtensionRuntimeEnv) {
async enable(runtime: any) {
this.isEnabled = true;
this.runtime = runtime;
logger.info(`[EXTENSION]: enabled ${this.name}@${this.version}`);
+8 -2
View File
@@ -1,11 +1,17 @@
import { LensExtension } from "./lens-extension"
import type { MenuRegistry } from "./menu-registry";
import type { StatusBarRegistry } from "./status-bar-registry";
export class LensMainExtension extends LensExtension {
async registerAppMenus() {
registerAppMenus(registry: MenuRegistry) {
//
}
async registerPrometheusProviders(registry: any) {
registerStatusBarIcon(registry: StatusBarRegistry) {
//
}
registerPrometheusProviders(registry: any) {
//
}
}
+2 -2
View File
@@ -3,12 +3,12 @@
import logger from "../main/logger";
import { navigate } from "../renderer/navigation";
export interface LensExtensionRuntimeEnv {
export interface LensExtensionRendererRuntimeEnv {
logger: typeof logger;
navigate: typeof navigate;
}
export function getLensRuntime(): LensExtensionRuntimeEnv {
export function getLensRuntimeRenderer(): LensExtensionRendererRuntimeEnv {
return {
logger,
navigate
+2 -2
View File
@@ -2,11 +2,11 @@
import logger from "../main/logger";
export interface LensExtensionRuntimeEnv {
export interface LensExtensionMainRuntimeEnv {
logger: typeof logger;
}
export function getLensRuntime(): LensExtensionRuntimeEnv {
export function getLensRuntime(): LensExtensionMainRuntimeEnv {
return {
logger
}
+1
View File
@@ -0,0 +1 @@
export { MenuRegistry, MenuRegistration } from "../menu-registry";
+16
View File
@@ -0,0 +1,16 @@
// Extensions API -> Global menu customize
import { observable } from "mobx";
export interface MenuRegistration {
data?: any;
}
export class MenuRegistry {
items = observable<MenuRegistration>([], { deep: false });
add(item: MenuRegistration) {
}
}
export const menuRegistry = new MenuRegistry();
+8 -9
View File
@@ -7,38 +7,37 @@ import { IconProps } from "../renderer/components/icon";
import { IClassName } from "../renderer/utils";
import { TabRoute } from "../renderer/components/layout/tab-layout";
export enum DynamicPageType {
export enum PageRegistryType {
GLOBAL = "lens-scope",
CLUSTER = "cluster-view-scope",
}
export interface PageRegistration extends RouteProps {
type: PageRegistryType;
components: PageComponents;
className?: IClassName;
url?: string; // initial url to be used for building menus and tabs, otherwise "path" applied by default
path: string; // route-path
title: React.ReactNode; // used in sidebar's & tabs-layout
type: DynamicPageType;
components: PageComponents;
title?: React.ReactNode; // used in sidebar's & tabs-layout if provided
subPages?: (PageRegistration & TabRoute)[];
}
export interface PageComponents {
Page: React.ComponentType<any>;
MenuIcon: React.ComponentType<IconProps>;
MenuIcon?: React.ComponentType<IconProps>;
}
export class PageRegistry {
protected pages = observable.array<PageRegistration>([], { deep: false });
@computed get globalPages() {
return this.pages.filter(page => page.type === DynamicPageType.GLOBAL);
return this.pages.filter(page => page.type === PageRegistryType.GLOBAL);
}
@computed get clusterPages() {
return this.pages.filter(page => page.type === DynamicPageType.CLUSTER);
return this.pages.filter(page => page.type === PageRegistryType.CLUSTER);
}
// todo: verify paths to avoid collision with existing pages
// fixme: validate route paths to avoid collisions
add(params: PageRegistration) {
this.pages.push(params);
return () => {
+2 -5
View File
@@ -1,12 +1,9 @@
// TODO: add more common re-usable UI components + refactor interfaces (Props -> ComponentProps)
export * from "../../renderer/components/icon"
export * from "../../renderer/components/checkbox"
export * from "../../renderer/components/tooltip"
export * from "../../renderer/components/button"
export * from "../../renderer/components/tabs"
export * from "../../renderer/components/badge"
export { KubeObjectMeta } from "../../renderer/components/kube-object/kube-object-meta";
export { MenuItem, SubMenu } from "../../renderer/components/menu";
export { StatusBrick } from "../../renderer/components/status-brick";
export { terminalStore, createTerminalTab } from "../../renderer/components/dock/terminal.store";
export { createPodLogsTab } from "../../renderer/components/dock/pod-logs.store";
export * from "../../renderer/components/layout/page-layout"
+2 -2
View File
@@ -1,2 +1,2 @@
export { DynamicPageType, PageRegistry } from "../page-registry"
export { AppPreferenceRegistry } from "../app-preference-registry"
export { PageRegistryType, PageRegistry, PageRegistration, PageComponents } from "../page-registry"
export { AppPreferenceRegistry, AppPreferenceComponents, AppPreferenceRegistration } from "../app-preference-registry"
+15
View File
@@ -0,0 +1,15 @@
// Extensions API -> Status bar customizations
import { observable } from "mobx";
export interface StatusBarRegistration {
}
export class StatusBarRegistry {
items = observable<StatusBarRegistration>([], { deep: false });
add(item: StatusBarRegistration) {
}
}
export const statusBarRegistry = new StatusBarRegistry();
@@ -1,2 +0,0 @@
export * from "./support.route"
export * from "./support"
@@ -1,8 +0,0 @@
import { RouteProps } from "react-router";
import { buildURL } from "../../navigation";
export const supportRoute: RouteProps = {
path: "/support"
}
export const supportURL = buildURL(supportRoute.path)
@@ -1,3 +0,0 @@
// TODO: figure out how to consume extra styles from extensions
.Support {
}
@@ -1,28 +0,0 @@
import "./support.scss"
import React from "react"
import { observer } from "mobx-react"
import { Trans } from "@lingui/macro"
import { issuesTrackerUrl, slackUrl } from "../../../common/vars";
import { PageLayout } from "../layout/page-layout";
@observer
export class Support extends React.Component {
render() {
return (
<PageLayout showOnTop className="Support" header={<h2>Support</h2>}>
<h2><Trans>Community Slack Channel</Trans></h2>
<p>
<Trans>Ask a question, see what's being discussed, join the conversation <a className="supportLink" href={slackUrl} target="_blank">here</a></Trans>{" "}
</p>
<h2><Trans>Report an Issue</Trans></h2>
<p>
<Trans>Review existing issues or open a new one <a className="supportLink" href={issuesTrackerUrl} target="_blank">here</a></Trans>
</p>
{/*<h2><Trans>Commercial Support</Trans></h2>*/}
</PageLayout>
);
}
}
+4 -4
View File
@@ -38,8 +38,8 @@ import { webFrame } from "electron";
import { pageRegistry } from "../../extensions/page-registry";
import { DynamicPage } from "../../extensions/dynamic-page";
import { extensionLoader } from "../../extensions/extension-loader";
import { getLensRuntime } from "../../extensions/lens-runtime";
import { appEventBus } from "../../common/event-bus"
import { getLensRuntimeRenderer } from "../../extensions/lens-renderer-runtime";
import { appEventBus } from "../../common/event-bus"
@observer
export class App extends React.Component {
@@ -51,7 +51,7 @@ export class App extends React.Component {
await clusterIpc.setFrameId.invokeFromRenderer(clusterId, frameId);
await getHostedCluster().whenReady; // cluster.activate() is done at this point
extensionLoader.loadOnClusterRenderer(getLensRuntime)
extensionLoader.loadOnClusterRenderer(getLensRuntimeRenderer)
appEventBus.emit({name: "cluster", action: "open", params: {
clusterId: clusterId
}})
@@ -83,7 +83,7 @@ export class App extends React.Component {
<Route component={UserManagement} {...usersManagementRoute}/>
<Route component={Apps} {...appsRoute}/>
{pageRegistry.clusterPages.map(page => {
return <Route {...page} key={page.path} render={() => <DynamicPage page={page}/>}/>
return <Route {...page} key={String(page.path)} render={() => <DynamicPage page={page}/>}/>
})}
<Redirect exact from="/" to={this.startURL}/>
<Route component={NotFound}/>
@@ -5,7 +5,6 @@ import { observer } from "mobx-react";
import { Icon } from "../icon";
import { WorkspaceMenu } from "../+workspaces/workspace-menu";
import { workspaceStore } from "../../../common/workspace-store";
import { supportURL } from "../+support/support.route";
import { navigate } from "../../navigation";
@observer
@@ -24,7 +23,7 @@ export class BottomBar extends React.Component {
material="support"
tooltip="Support"
className="support-icon box right"
onClick={() => navigate(supportURL())}
onClick={() => navigate("/support")}
/>
</div>
)
@@ -7,7 +7,6 @@ import { ClustersMenu } from "./clusters-menu";
import { BottomBar } from "./bottom-bar";
import { LandingPage, landingRoute, landingURL } from "../+landing-page";
import { Preferences, preferencesRoute } from "../+preferences";
import { Support, supportRoute } from "../+support";
import { Workspaces, workspacesRoute } from "../+workspaces";
import { AddCluster, addClusterRoute } from "../+add-cluster";
import { ClusterView } from "./cluster-view";
@@ -60,13 +59,12 @@ export class ClusterManager extends React.Component {
<Switch>
<Route component={LandingPage} {...landingRoute} />
<Route component={Preferences} {...preferencesRoute} />
<Route component={Support} {...supportRoute} />
<Route component={Workspaces} {...workspacesRoute} />
<Route component={AddCluster} {...addClusterRoute} />
<Route component={ClusterView} {...clusterViewRoute} />
<Route component={ClusterSettings} {...clusterSettingsRoute} />
{pageRegistry.globalPages.map(({ path, components: { Page } }) => {
return <Route key={path} path={path} component={Page}/>
{pageRegistry.globalPages.map(({ path, url = String(path), components: { Page } }) => {
return <Route key={url} path={path} component={Page}/>
})}
<Redirect exact to={this.startUrl} />
</Switch>
@@ -67,7 +67,7 @@
}
}
> .dynamic-pages {
> .extensions {
&:not(:empty) {
padding-top: $spacing;
}
@@ -11,7 +11,7 @@ import { ClusterId, clusterStore } from "../../../common/cluster-store";
import { workspaceStore } from "../../../common/workspace-store";
import { ClusterIcon } from "../cluster-icon";
import { Icon } from "../icon";
import { cssNames, IClassName, autobind } from "../../utils";
import { autobind, cssNames, IClassName } from "../../utils";
import { Badge } from "../badge";
import { navigate } from "../../navigation";
import { addClusterURL } from "../+add-cluster";
@@ -21,7 +21,7 @@ import { Tooltip } from "../tooltip";
import { ConfirmDialog } from "../confirm-dialog";
import { clusterIpc } from "../../../common/cluster-ipc";
import { clusterViewURL } from "./cluster-view.route";
import { DragDropContext, Droppable, Draggable, DropResult, DroppableProvided, DraggableProvided } from "react-beautiful-dnd";
import { DragDropContext, Draggable, DraggableProvided, Droppable, DroppableProvided, DropResult } from "react-beautiful-dnd";
import { pageRegistry } from "../../../extensions/page-registry";
interface Props {
@@ -155,9 +155,10 @@ export class ClustersMenu extends React.Component<Props> {
<Badge className="counter" label={newContexts.size} tooltip={<Trans>new</Trans>} />
)}
</div>
<div className="dynamic-pages">
{pageRegistry.globalPages.map(({ path, components: { MenuIcon } }) => {
return <MenuIcon key={path} onClick={() => navigate(path)}/>
<div className="extensions">
{pageRegistry.globalPages.map(({ path, url = String(path), components: { MenuIcon } }) => {
if (!MenuIcon) return;
return <MenuIcon key={url} onClick={() => navigate(url)}/>
})}
</div>
</div>
+17 -17
View File
@@ -80,7 +80,7 @@ export class Sidebar extends React.Component<Props> {
<div className={cssNames("Sidebar flex column", className, { pinned: isPinned })}>
<div className="header flex align-center">
<NavLink exact to="/" className="box grow">
<Icon svg="logo-full" className="logo-icon" />
<Icon svg="logo-full" className="logo-icon"/>
<div className="logo-text">Lens</div>
</NavLink>
<Icon
@@ -97,14 +97,14 @@ export class Sidebar extends React.Component<Props> {
isHidden={!isAllowedResource("nodes")}
url={clusterURL()}
text={<Trans>Cluster</Trans>}
icon={<Icon svg="kube" />}
icon={<Icon svg="kube"/>}
/>
<SidebarNavItem
id="nodes"
isHidden={!isAllowedResource("nodes")}
url={nodesURL()}
text={<Trans>Nodes</Trans>}
icon={<Icon svg="nodes" />}
icon={<Icon svg="nodes"/>}
/>
<SidebarNavItem
id="workloads"
@@ -113,7 +113,7 @@ export class Sidebar extends React.Component<Props> {
routePath={workloadsRoute.path}
subMenus={Workloads.tabRoutes}
text={<Trans>Workloads</Trans>}
icon={<Icon svg="workloads" />}
icon={<Icon svg="workloads"/>}
/>
<SidebarNavItem
id="config"
@@ -122,7 +122,7 @@ export class Sidebar extends React.Component<Props> {
routePath={configRoute.path}
subMenus={Config.tabRoutes}
text={<Trans>Configuration</Trans>}
icon={<Icon material="list" />}
icon={<Icon material="list"/>}
/>
<SidebarNavItem
id="networks"
@@ -131,7 +131,7 @@ export class Sidebar extends React.Component<Props> {
routePath={networkRoute.path}
subMenus={Network.tabRoutes}
text={<Trans>Network</Trans>}
icon={<Icon material="device_hub" />}
icon={<Icon material="device_hub"/>}
/>
<SidebarNavItem
id="storage"
@@ -139,14 +139,14 @@ export class Sidebar extends React.Component<Props> {
url={storageURL({ query })}
routePath={storageRoute.path}
subMenus={Storage.tabRoutes}
icon={<Icon svg="storage" />}
icon={<Icon svg="storage"/>}
text={<Trans>Storage</Trans>}
/>
<SidebarNavItem
id="namespaces"
isHidden={!isAllowedResource("namespaces")}
url={namespacesURL()}
icon={<Icon material="layers" />}
icon={<Icon material="layers"/>}
text={<Trans>Namespaces</Trans>}
/>
<SidebarNavItem
@@ -154,7 +154,7 @@ export class Sidebar extends React.Component<Props> {
isHidden={!isAllowedResource("events")}
url={eventsURL({ query })}
routePath={eventRoute.path}
icon={<Icon material="access_time" />}
icon={<Icon material="access_time"/>}
text={<Trans>Events</Trans>}
/>
<SidebarNavItem
@@ -162,7 +162,7 @@ export class Sidebar extends React.Component<Props> {
url={appsURL({ query })}
subMenus={Apps.tabRoutes}
routePath={appsRoute.path}
icon={<Icon material="apps" />}
icon={<Icon material="apps"/>}
text={<Trans>Apps</Trans>}
/>
<SidebarNavItem
@@ -170,7 +170,7 @@ export class Sidebar extends React.Component<Props> {
url={usersManagementURL({ query })}
routePath={usersManagementRoute.path}
subMenus={UserManagement.tabRoutes}
icon={<Icon material="security" />}
icon={<Icon material="security"/>}
text={<Trans>Access Control</Trans>}
/>
<SidebarNavItem
@@ -179,17 +179,17 @@ export class Sidebar extends React.Component<Props> {
url={crdURL()}
subMenus={CustomResources.tabRoutes}
routePath={crdRoute.path}
icon={<Icon material="extension" />}
icon={<Icon material="extension"/>}
text={<Trans>Custom Resources</Trans>}
>
{this.renderCustomResources()}
</SidebarNavItem>
{pageRegistry.clusterPages.map(({ path, title, components: { MenuIcon } }) => {
{pageRegistry.clusterPages.map(({ path, title, url = String(path), components: { MenuIcon } }) => {
if (!MenuIcon) return;
return (
<SidebarNavItem
key={path}
id={`extension-${path}`}
url={path}
key={url} id={`sidebar_item_${url}`}
url={url}
routePath={path}
text={title}
icon={<MenuIcon/>}
@@ -255,7 +255,7 @@ class SidebarNavItem extends React.Component<SidebarNavItemProps> {
<div className={cssNames("nav-item", { active: isActive })} onClick={this.toggleSubMenu}>
{icon}
<span className="link-text">{text}</span>
<Icon className="expand-icon" material={this.isExpanded ? "keyboard_arrow_up" : "keyboard_arrow_down"} />
<Icon className="expand-icon" material={this.isExpanded ? "keyboard_arrow_up" : "keyboard_arrow_down"}/>
</div>
<ul className={cssNames("sub-menu", { active: isActive })}>
{subMenus.map(({ title, url }) => (
+2 -2
View File
@@ -12,12 +12,12 @@ import { WhatsNew, whatsNewRoute } from "./components/+whats-new";
import { Notifications } from "./components/notifications";
import { ConfirmDialog } from "./components/confirm-dialog";
import { extensionLoader } from "../extensions/extension-loader";
import { getLensRuntime } from "../extensions/lens-runtime";
import { getLensRuntimeRenderer } from "../extensions/lens-renderer-runtime";
@observer
export class LensApp extends React.Component {
static async init() {
extensionLoader.loadOnMainRenderer(getLensRuntime)
extensionLoader.loadOnMainRenderer(getLensRuntimeRenderer)
}
render() {