mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
PageRegistration refactoring -- part 2
Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
parent
ab097c410c
commit
9b55a5fd0a
@ -57,31 +57,30 @@ export class ExtensionLoader {
|
|||||||
loadOnMain() {
|
loadOnMain() {
|
||||||
logger.info('[EXTENSIONS-LOADER]: load on main')
|
logger.info('[EXTENSIONS-LOADER]: load on main')
|
||||||
this.autoInitExtensions((extension: LensMainExtension) => [
|
this.autoInitExtensions((extension: LensMainExtension) => [
|
||||||
registries.menuRegistry.add(...extension.appMenus)
|
registries.menuRegistry.add(extension, extension.appMenus)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadOnClusterManagerRenderer() {
|
loadOnClusterManagerRenderer() {
|
||||||
logger.info('[EXTENSIONS-LOADER]: load on main renderer (cluster manager)')
|
logger.info('[EXTENSIONS-LOADER]: load on main renderer (cluster manager)')
|
||||||
this.autoInitExtensions((extension: LensRendererExtension) => [
|
this.autoInitExtensions((extension: LensRendererExtension) => [
|
||||||
registries.globalPageRegistry.add(...extension.globalPages),
|
registries.globalPageRegistry.add(extension, extension.globalPages),
|
||||||
registries.globalPageMenuRegistry.add(...extension.globalPageMenus),
|
registries.globalPageMenuRegistry.add(extension, extension.globalPageMenus),
|
||||||
registries.appPreferenceRegistry.add(...extension.appPreferences),
|
registries.appPreferenceRegistry.add(extension, extension.appPreferences),
|
||||||
registries.clusterFeatureRegistry.add(...extension.clusterFeatures),
|
registries.clusterFeatureRegistry.add(extension, extension.clusterFeatures),
|
||||||
registries.statusBarRegistry.add(...extension.statusBarItems),
|
registries.statusBarRegistry.add(extension, extension.statusBarItems),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadOnClusterRenderer() {
|
loadOnClusterRenderer() {
|
||||||
logger.info('[EXTENSIONS-LOADER]: load on cluster renderer (dashboard)')
|
logger.info('[EXTENSIONS-LOADER]: load on cluster renderer (dashboard)')
|
||||||
this.autoInitExtensions((extension: LensRendererExtension) => [
|
this.autoInitExtensions((extension: LensRendererExtension) => [
|
||||||
registries.clusterPageRegistry.add(...extension.clusterPages),
|
registries.clusterPageRegistry.add(extension, extension.clusterPages),
|
||||||
registries.clusterPageMenuRegistry.add(...extension.clusterPageMenus),
|
registries.clusterPageMenuRegistry.add(extension, extension.clusterPageMenus),
|
||||||
registries.kubeObjectMenuRegistry.add(...extension.kubeObjectMenuItems),
|
registries.kubeObjectMenuRegistry.add(extension, extension.kubeObjectMenuItems),
|
||||||
registries.kubeObjectDetailRegistry.add(...extension.kubeObjectDetailItems),
|
registries.kubeObjectDetailRegistry.add(extension, extension.kubeObjectDetailItems),
|
||||||
registries.kubeObjectStatusRegistry.add(...extension.kubeObjectStatusTexts)
|
registries.kubeObjectStatusRegistry.add(extension, extension.kubeObjectStatusTexts)
|
||||||
])
|
])
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected autoInitExtensions(register: (ext: LensExtension) => Function[]) {
|
protected autoInitExtensions(register: (ext: LensExtension) => Function[]) {
|
||||||
|
|||||||
@ -4,4 +4,5 @@ export type { KubeObjectDetailRegistration, KubeObjectDetailComponents } from ".
|
|||||||
export type { KubeObjectMenuRegistration, KubeObjectMenuComponents } from "../registries/kube-object-menu-registry"
|
export type { KubeObjectMenuRegistration, KubeObjectMenuComponents } from "../registries/kube-object-menu-registry"
|
||||||
export type { KubeObjectStatusRegistration } from "../registries/kube-object-status-registry"
|
export type { KubeObjectStatusRegistration } from "../registries/kube-object-status-registry"
|
||||||
export type { PageRegistration, PageComponents } from "../registries/page-registry"
|
export type { PageRegistration, PageComponents } from "../registries/page-registry"
|
||||||
|
export type { PageMenuRegistration, PageMenuComponents } from "../registries/page-menu-registry"
|
||||||
export type { StatusBarRegistration } from "../registries/status-bar-registry"
|
export type { StatusBarRegistration } from "../registries/status-bar-registry"
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import type { InstalledExtension } from "./extension-manager";
|
import type { InstalledExtension } from "./extension-manager";
|
||||||
import { action, observable, reaction } from "mobx";
|
import { action, observable, reaction } from "mobx";
|
||||||
|
import { compile } from "path-to-regexp"
|
||||||
import logger from "../main/logger";
|
import logger from "../main/logger";
|
||||||
|
|
||||||
export type LensExtensionId = string; // path to manifest (package.json)
|
export type LensExtensionId = string; // path to manifest (package.json)
|
||||||
@ -14,6 +15,7 @@ export interface LensExtensionManifest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class LensExtension {
|
export class LensExtension {
|
||||||
|
readonly routePrefix = "/extensions/:name"
|
||||||
readonly manifest: LensExtensionManifest;
|
readonly manifest: LensExtensionManifest;
|
||||||
readonly manifestPath: string;
|
readonly manifestPath: string;
|
||||||
readonly isBundled: boolean;
|
readonly isBundled: boolean;
|
||||||
@ -42,6 +44,14 @@ export class LensExtension {
|
|||||||
return this.manifest.description
|
return this.manifest.description
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPageUrl(baseUrl: string) {
|
||||||
|
return compile(this.routePrefix)({ name: this.name }) + "/" + baseUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
getPageRoute(baseRoute: string) {
|
||||||
|
return this.routePrefix + "/" + baseRoute;
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
async enable() {
|
async enable() {
|
||||||
if (this.isEnabled) return;
|
if (this.isEnabled) return;
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
import type {
|
import type {
|
||||||
AppPreferenceRegistration, ClusterFeatureRegistration,
|
AppPreferenceRegistration, ClusterFeatureRegistration,
|
||||||
KubeObjectMenuRegistration, KubeObjectDetailRegistration, StatusBarRegistration, KubeObjectStatusRegistration,
|
KubeObjectMenuRegistration, KubeObjectDetailRegistration, StatusBarRegistration, KubeObjectStatusRegistration,
|
||||||
PageRegistration, PageMenuRegistration, PageRegistrationCluster, PageMenuRegistrationCluster,
|
PageRegistration, PageMenuRegistration,
|
||||||
} from "./registries"
|
} from "./registries"
|
||||||
import { observable } from "mobx";
|
import { observable } from "mobx";
|
||||||
import { LensExtension } from "./lens-extension"
|
import { LensExtension } from "./lens-extension"
|
||||||
|
|
||||||
export class LensRendererExtension extends LensExtension {
|
export class LensRendererExtension extends LensExtension {
|
||||||
@observable.shallow globalPages: PageRegistration[] = []
|
@observable.shallow globalPages: PageRegistration[] = []
|
||||||
@observable.shallow clusterPages: PageRegistrationCluster[] = []
|
@observable.shallow clusterPages: PageRegistration[] = []
|
||||||
@observable.shallow globalPageMenus: PageMenuRegistration[] = []
|
@observable.shallow globalPageMenus: PageMenuRegistration[] = []
|
||||||
@observable.shallow clusterPageMenus: PageMenuRegistrationCluster[] = []
|
@observable.shallow clusterPageMenus: PageMenuRegistration[] = []
|
||||||
@observable.shallow kubeObjectStatusTexts: KubeObjectStatusRegistration[] = []
|
@observable.shallow kubeObjectStatusTexts: KubeObjectStatusRegistration[] = []
|
||||||
@observable.shallow appPreferences: AppPreferenceRegistration[] = []
|
@observable.shallow appPreferences: AppPreferenceRegistration[] = []
|
||||||
@observable.shallow clusterFeatures: ClusterFeatureRegistration[] = []
|
@observable.shallow clusterFeatures: ClusterFeatureRegistration[] = []
|
||||||
|
|||||||
@ -1,23 +1,37 @@
|
|||||||
// Base class for extensions-api registries
|
// Base class for extensions-api registries
|
||||||
import { action, observable } from "mobx";
|
import { action, observable } from "mobx";
|
||||||
|
import { LensExtension } from "../lens-extension";
|
||||||
|
|
||||||
export class BaseRegistry<T = any> {
|
export class BaseRegistry<T extends object = any> {
|
||||||
protected items = observable<T>([], { deep: false });
|
private items = observable.map<LensExtension, T[]>([], { deep: false });
|
||||||
|
|
||||||
getItems(): T[] {
|
getItems(): (T & { extension?: LensExtension })[] {
|
||||||
return this.items.toJS();
|
return Array.from(this.items).map(([ext, items]) => {
|
||||||
|
return items.map(item => ({
|
||||||
|
...item,
|
||||||
|
extension: ext,
|
||||||
|
}))
|
||||||
|
}).flat()
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
add(...items: T[]) {
|
add(ext: LensExtension | null, items: T[], merge = true) {
|
||||||
this.items.push(...items);
|
if (merge && this.items.has(ext)) {
|
||||||
return () => this.remove(...items);
|
const newItems = new Set(this.items.get(ext));
|
||||||
|
items.forEach(item => newItems.add(item))
|
||||||
|
this.items.set(ext, [...newItems]);
|
||||||
|
} else {
|
||||||
|
this.items.set(ext, items);
|
||||||
|
}
|
||||||
|
return () => this.remove(ext, items)
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
remove(...items: T[]) {
|
remove(ext: LensExtension | null, items: T[]) {
|
||||||
items.forEach(item => {
|
const storedItems = this.items.get(ext);
|
||||||
this.items.remove(item); // works because of {deep: false};
|
if (storedItems) {
|
||||||
})
|
const newItems = storedItems.filter(item => !items.includes(item)); // works because of {deep: false};
|
||||||
|
this.items.set(ext, newItems);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,7 @@ export interface KubeObjectDetailRegistration {
|
|||||||
|
|
||||||
export class KubeObjectDetailRegistry extends BaseRegistry<KubeObjectDetailRegistration> {
|
export class KubeObjectDetailRegistry extends BaseRegistry<KubeObjectDetailRegistration> {
|
||||||
getItemsForKind(kind: string, apiVersion: string) {
|
getItemsForKind(kind: string, apiVersion: string) {
|
||||||
return this.items.filter((item) => {
|
return this.getItems().filter((item) => {
|
||||||
return item.kind === kind && item.apiVersions.includes(apiVersion)
|
return item.kind === kind && item.apiVersions.includes(apiVersion)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,7 @@ export interface KubeObjectMenuRegistration {
|
|||||||
|
|
||||||
export class KubeObjectMenuRegistry extends BaseRegistry<KubeObjectMenuRegistration> {
|
export class KubeObjectMenuRegistry extends BaseRegistry<KubeObjectMenuRegistration> {
|
||||||
getItemsForKind(kind: string, apiVersion: string) {
|
getItemsForKind(kind: string, apiVersion: string) {
|
||||||
return this.items.filter((item) => {
|
return this.getItems().filter((item) => {
|
||||||
return item.kind === kind && item.apiVersions.includes(apiVersion)
|
return item.kind === kind && item.apiVersions.includes(apiVersion)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@ export interface KubeObjectStatusRegistration {
|
|||||||
|
|
||||||
export class KubeObjectStatusRegistry extends BaseRegistry<KubeObjectStatusRegistration> {
|
export class KubeObjectStatusRegistry extends BaseRegistry<KubeObjectStatusRegistration> {
|
||||||
getItemsForKind(kind: string, apiVersion: string) {
|
getItemsForKind(kind: string, apiVersion: string) {
|
||||||
return this.items.filter((item) => {
|
return this.getItems().filter((item) => {
|
||||||
return item.kind === kind && item.apiVersions.includes(apiVersion)
|
return item.kind === kind && item.apiVersions.includes(apiVersion)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,14 +3,12 @@
|
|||||||
import type React from "react";
|
import type React from "react";
|
||||||
import type { IconProps } from "../../renderer/components/icon";
|
import type { IconProps } from "../../renderer/components/icon";
|
||||||
import { BaseRegistry } from "./base-registry";
|
import { BaseRegistry } from "./base-registry";
|
||||||
|
import { matchPath } from "react-router";
|
||||||
|
|
||||||
export interface PageMenuRegistration {
|
export interface PageMenuRegistration {
|
||||||
url: string;
|
url: string;
|
||||||
title: React.ReactNode;
|
title: React.ReactNode;
|
||||||
components: PageMenuComponents;
|
components: PageMenuComponents;
|
||||||
}
|
|
||||||
|
|
||||||
export interface PageMenuRegistrationCluster extends PageMenuRegistration {
|
|
||||||
subMenus?: Omit<PageMenuRegistration, "components" | "subMenus">[];
|
subMenus?: Omit<PageMenuRegistration, "components" | "subMenus">[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -18,8 +16,21 @@ export interface PageMenuComponents {
|
|||||||
Icon: React.ComponentType<IconProps>;
|
Icon: React.ComponentType<IconProps>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PageMenuRegistry<T extends PageMenuRegistration> extends BaseRegistry<T> {
|
export class PageMenuRegistry extends BaseRegistry<PageMenuRegistration> {
|
||||||
|
getByMatchingRoute(routePath: string | string[], exact?: boolean) {
|
||||||
|
return this.getItems().find(item => !!matchPath(item.url, {
|
||||||
|
path: routePath,
|
||||||
|
exact,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
getItems() {
|
||||||
|
return super.getItems().map(item => {
|
||||||
|
item.url = item.extension.getPageUrl(item.url)
|
||||||
|
return item
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const globalPageMenuRegistry = new PageMenuRegistry<PageMenuRegistration>();
|
export const globalPageMenuRegistry = new PageMenuRegistry();
|
||||||
export const clusterPageMenuRegistry = new PageMenuRegistry<PageMenuRegistrationCluster>();
|
export const clusterPageMenuRegistry = new PageMenuRegistry();
|
||||||
|
|||||||
@ -1,29 +1,34 @@
|
|||||||
// Extensions-api -> Custom page registration
|
// Extensions-api -> Custom page registration
|
||||||
|
|
||||||
import type React from "react";
|
import React from "react";
|
||||||
|
import { matchPath } from "react-router";
|
||||||
import { BaseRegistry } from "./base-registry";
|
import { BaseRegistry } from "./base-registry";
|
||||||
|
|
||||||
export interface PageRegistration {
|
export interface PageRegistration {
|
||||||
routePath: string; // react-router's path, e.g. "/page/:id"
|
routePath: string; // react-router's path, e.g. "/page/:id"
|
||||||
exact?: boolean; // route matching flag, see: https://reactrouter.com/web/api/NavLink/exact-bool
|
exact?: boolean; // route matching flag, see: https://reactrouter.com/web/api/NavLink/exact-bool
|
||||||
components: PageComponents;
|
components: PageComponents;
|
||||||
}
|
subPages?: Omit<PageRegistration, "subPages">[];
|
||||||
|
|
||||||
export interface PageRegistrationCluster extends PageRegistration {
|
|
||||||
subPages?: Omit<PageRegistration, "subPages">;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PageComponents {
|
export interface PageComponents {
|
||||||
Page: React.ComponentType<any>;
|
Page: React.ComponentType<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PageRegistry<T extends PageRegistration> extends BaseRegistry<T> {
|
export class PageRegistry extends BaseRegistry<PageRegistration> {
|
||||||
protected routePrefixPath = "/extensions/:name" // todo: figure out how to provide inside extension
|
getByMatchingUrl(baseUrl: string) {
|
||||||
|
return this.getItems().find(({ routePath: path, exact }) => {
|
||||||
|
return !!matchPath(baseUrl, { path, exact });
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
getItems() {
|
getItems() {
|
||||||
return super.getItems();
|
return super.getItems().map(item => {
|
||||||
|
item.routePath = item.extension.getPageRoute(item.routePath)
|
||||||
|
return item
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const globalPageRegistry = new PageRegistry<PageRegistration>();
|
export const globalPageRegistry = new PageRegistry();
|
||||||
export const clusterPageRegistry = new PageRegistry<PageRegistrationCluster>();
|
export const clusterPageRegistry = new PageRegistry();
|
||||||
|
|||||||
@ -128,10 +128,12 @@ export class HpaDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeObjectDetailRegistry.add({
|
kubeObjectDetailRegistry.add(null, [
|
||||||
kind: "HorizontalPodAutoscaler",
|
{
|
||||||
apiVersions: ["autoscaling/v1"],
|
kind: "HorizontalPodAutoscaler",
|
||||||
components: {
|
apiVersions: ["autoscaling/v1"],
|
||||||
Details: (props) => <HpaDetails {...props} />
|
components: {
|
||||||
|
Details: (props) => <HpaDetails {...props} />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
])
|
||||||
|
|||||||
@ -94,10 +94,12 @@ export class ConfigMapDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeObjectDetailRegistry.add({
|
kubeObjectDetailRegistry.add(null, [
|
||||||
kind: "ConfigMap",
|
{
|
||||||
apiVersions: ["v1"],
|
kind: "ConfigMap",
|
||||||
components: {
|
apiVersions: ["v1"],
|
||||||
Details: (props) => <ConfigMapDetails {...props} />
|
components: {
|
||||||
|
Details: (props) => <ConfigMapDetails {...props} />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
])
|
||||||
|
|||||||
@ -54,10 +54,12 @@ export class PodDisruptionBudgetDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeObjectDetailRegistry.add({
|
kubeObjectDetailRegistry.add(null, [
|
||||||
kind: "PodDisruptionBudget",
|
{
|
||||||
apiVersions: ["policy/v1beta1"],
|
kind: "PodDisruptionBudget",
|
||||||
components: {
|
apiVersions: ["policy/v1beta1"],
|
||||||
Details: (props) => <PodDisruptionBudgetDetails {...props} />
|
components: {
|
||||||
|
Details: (props) => <PodDisruptionBudgetDetails {...props} />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
])
|
||||||
|
|||||||
@ -16,8 +16,6 @@ import { ReplicaSetDetails } from "../+workloads-replicasets";
|
|||||||
interface Props extends KubeObjectDetailsProps<ResourceQuota> {
|
interface Props extends KubeObjectDetailsProps<ResourceQuota> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const onlyNumbers = /$[0-9]*^/g;
|
|
||||||
|
|
||||||
function transformUnit(name: string, value: string): number {
|
function transformUnit(name: string, value: string): number {
|
||||||
if (name.includes("memory") || name.includes("storage")) {
|
if (name.includes("memory") || name.includes("storage")) {
|
||||||
return unitsToBytes(value)
|
return unitsToBytes(value)
|
||||||
@ -98,10 +96,12 @@ export class ResourceQuotaDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeObjectDetailRegistry.add({
|
kubeObjectDetailRegistry.add(null, [
|
||||||
kind: "ResourceQuota",
|
{
|
||||||
apiVersions: ["v1"],
|
kind: "ResourceQuota",
|
||||||
components: {
|
apiVersions: ["v1"],
|
||||||
Details: (props) => <ReplicaSetDetails {...props} />
|
components: {
|
||||||
|
Details: (props) => <ReplicaSetDetails {...props} />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
])
|
||||||
|
|||||||
@ -113,10 +113,12 @@ export class SecretDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeObjectDetailRegistry.add({
|
kubeObjectDetailRegistry.add(null, [
|
||||||
kind: "Secret",
|
{
|
||||||
apiVersions: ["v1"],
|
kind: "Secret",
|
||||||
components: {
|
apiVersions: ["v1"],
|
||||||
Details: (props) => <SecretDetails {...props} />
|
components: {
|
||||||
|
Details: (props) => <SecretDetails {...props} />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
])
|
||||||
|
|||||||
@ -134,10 +134,12 @@ export class CRDDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeObjectDetailRegistry.add({
|
kubeObjectDetailRegistry.add(null, [
|
||||||
kind: "CustomResourceDefinition",
|
{
|
||||||
apiVersions: ["apiextensions.k8s.io/v1", "apiextensions.k8s.io/v1beta1"],
|
kind: "CustomResourceDefinition",
|
||||||
components: {
|
apiVersions: ["apiextensions.k8s.io/v1", "apiextensions.k8s.io/v1beta1"],
|
||||||
Details: (props) => <CRDDetails {...props} />
|
components: {
|
||||||
|
Details: (props) => <CRDDetails {...props} />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
])
|
||||||
|
|||||||
@ -74,10 +74,12 @@ export class EventDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeObjectDetailRegistry.add({
|
kubeObjectDetailRegistry.add(null, [
|
||||||
kind: "Event",
|
{
|
||||||
apiVersions: ["v1"],
|
kind: "Event",
|
||||||
components: {
|
apiVersions: ["v1"],
|
||||||
Details: (props) => <EventDetails {...props}/>
|
components: {
|
||||||
|
Details: (props) => <EventDetails {...props}/>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
])
|
||||||
|
|||||||
@ -56,10 +56,12 @@ export class NamespaceDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeObjectDetailRegistry.add({
|
kubeObjectDetailRegistry.add(null, [
|
||||||
kind: "Namespace",
|
{
|
||||||
apiVersions: ["v1"],
|
kind: "Namespace",
|
||||||
components: {
|
apiVersions: ["v1"],
|
||||||
Details: (props) => <NamespaceDetails {...props} />
|
components: {
|
||||||
|
Details: (props) => <NamespaceDetails {...props} />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
])
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import { DrawerTitle } from "../drawer";
|
|||||||
import { KubeEventDetails } from "../+events/kube-event-details";
|
import { KubeEventDetails } from "../+events/kube-event-details";
|
||||||
import { KubeObjectDetailsProps } from "../kube-object";
|
import { KubeObjectDetailsProps } from "../kube-object";
|
||||||
import { Endpoint } from "../../api/endpoints";
|
import { Endpoint } from "../../api/endpoints";
|
||||||
import { _i18n } from "../../i18n";
|
|
||||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||||
import { EndpointSubsetList } from "./endpoint-subset-list";
|
import { EndpointSubsetList } from "./endpoint-subset-list";
|
||||||
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||||
@ -38,10 +37,12 @@ export class EndpointDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeObjectDetailRegistry.add({
|
kubeObjectDetailRegistry.add(null, [
|
||||||
kind: "Endpoints",
|
{
|
||||||
apiVersions: ["v1"],
|
kind: "Endpoints",
|
||||||
components: {
|
apiVersions: ["v1"],
|
||||||
Details: (props) => <EndpointDetails {...props} />
|
components: {
|
||||||
|
Details: (props) => <EndpointDetails {...props} />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
])
|
||||||
|
|||||||
@ -134,10 +134,12 @@ export class IngressDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeObjectDetailRegistry.add({
|
kubeObjectDetailRegistry.add(null, [
|
||||||
kind: "Ingress",
|
{
|
||||||
apiVersions: ["extensions/v1beta1"],
|
kind: "Ingress",
|
||||||
components: {
|
apiVersions: ["extensions/v1beta1"],
|
||||||
Details: (props) => <IngressDetails {...props} />
|
components: {
|
||||||
|
Details: (props) => <IngressDetails {...props} />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
])
|
||||||
|
|||||||
@ -144,10 +144,12 @@ export class NetworkPolicyDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeObjectDetailRegistry.add({
|
kubeObjectDetailRegistry.add(null, [
|
||||||
kind: "NetworkPolicy",
|
{
|
||||||
apiVersions: ["networking.k8s.io/v1"],
|
kind: "NetworkPolicy",
|
||||||
components: {
|
apiVersions: ["networking.k8s.io/v1"],
|
||||||
Details: (props) => <NetworkPolicyDetails {...props} />
|
components: {
|
||||||
|
Details: (props) => <NetworkPolicyDetails {...props} />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
])
|
||||||
|
|||||||
@ -85,10 +85,12 @@ export class ServiceDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeObjectDetailRegistry.add({
|
kubeObjectDetailRegistry.add(null, [
|
||||||
kind: "Service",
|
{
|
||||||
apiVersions: ["v1"],
|
kind: "Service",
|
||||||
components: {
|
apiVersions: ["v1"],
|
||||||
Details: (props) => <ServiceDetails {...props} />
|
components: {
|
||||||
|
Details: (props) => <ServiceDetails {...props} />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
])
|
||||||
|
|||||||
@ -155,10 +155,12 @@ export class NodeDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeObjectDetailRegistry.add({
|
kubeObjectDetailRegistry.add(null, [
|
||||||
kind: "Node",
|
{
|
||||||
apiVersions: ["v1"],
|
kind: "Node",
|
||||||
components: {
|
apiVersions: ["v1"],
|
||||||
Details: (props) => <NodeDetails {...props} />
|
components: {
|
||||||
|
Details: (props) => <NodeDetails {...props} />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
])
|
||||||
|
|||||||
@ -209,10 +209,12 @@ export class PodSecurityPolicyDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeObjectDetailRegistry.add({
|
kubeObjectDetailRegistry.add(null, [
|
||||||
kind: "PodSecurityPolicy",
|
{
|
||||||
apiVersions: ["policy/v1beta1"],
|
kind: "PodSecurityPolicy",
|
||||||
components: {
|
apiVersions: ["policy/v1beta1"],
|
||||||
Details: (props) => <PodSecurityPolicyDetails {...props}/>
|
components: {
|
||||||
|
Details: (props) => <PodSecurityPolicyDetails {...props}/>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
])
|
||||||
|
|||||||
@ -62,10 +62,12 @@ export class StorageClassDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeObjectDetailRegistry.add({
|
kubeObjectDetailRegistry.add(null, [
|
||||||
kind: "StorageClass",
|
{
|
||||||
apiVersions: ["storage.k8s.io/v1"],
|
kind: "StorageClass",
|
||||||
components: {
|
apiVersions: ["storage.k8s.io/v1"],
|
||||||
Details: (props) => <StorageClassDetails {...props} />
|
components: {
|
||||||
|
Details: (props) => <StorageClassDetails {...props} />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
])
|
||||||
|
|||||||
@ -95,10 +95,12 @@ export class PersistentVolumeClaimDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeObjectDetailRegistry.add({
|
kubeObjectDetailRegistry.add(null, [
|
||||||
kind: "PersistentVolumeClaim",
|
{
|
||||||
apiVersions: ["v1"],
|
kind: "PersistentVolumeClaim",
|
||||||
components: {
|
apiVersions: ["v1"],
|
||||||
Details: (props) => <PersistentVolumeClaimDetails {...props} />
|
components: {
|
||||||
|
Details: (props) => <PersistentVolumeClaimDetails {...props} />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
])
|
||||||
|
|||||||
@ -103,10 +103,12 @@ export class PersistentVolumeDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeObjectDetailRegistry.add({
|
kubeObjectDetailRegistry.add(null, [
|
||||||
kind: "PersistentVolume",
|
{
|
||||||
apiVersions: ["v1"],
|
kind: "PersistentVolume",
|
||||||
components: {
|
apiVersions: ["v1"],
|
||||||
Details: (props) => <PersistentVolumeDetails {...props} />
|
components: {
|
||||||
|
Details: (props) => <PersistentVolumeDetails {...props} />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
])
|
||||||
|
|||||||
@ -125,17 +125,19 @@ export class RoleBindingDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeObjectDetailRegistry.add({
|
kubeObjectDetailRegistry.add(null, [
|
||||||
kind: "RoleBinding",
|
{
|
||||||
apiVersions: ["rbac.authorization.k8s.io/v1"],
|
kind: "RoleBinding",
|
||||||
components: {
|
apiVersions: ["rbac.authorization.k8s.io/v1"],
|
||||||
Details: (props) => <RoleBindingDetails {...props} />
|
components: {
|
||||||
|
Details: (props) => <RoleBindingDetails {...props} />
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "ClusterRoleBinding",
|
||||||
|
apiVersions: ["rbac.authorization.k8s.io/v1"],
|
||||||
|
components: {
|
||||||
|
Details: (props) => <RoleBindingDetails {...props} />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
])
|
||||||
kubeObjectDetailRegistry.add({
|
|
||||||
kind: "ClusterRoleBinding",
|
|
||||||
apiVersions: ["rbac.authorization.k8s.io/v1"],
|
|
||||||
components: {
|
|
||||||
Details: (props) => <RoleBindingDetails {...props} />
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|||||||
@ -66,18 +66,19 @@ export class RoleDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeObjectDetailRegistry.add({
|
kubeObjectDetailRegistry.add(null, [
|
||||||
kind: "Role",
|
{
|
||||||
apiVersions: ["rbac.authorization.k8s.io/v1"],
|
kind: "Role",
|
||||||
components: {
|
apiVersions: ["rbac.authorization.k8s.io/v1"],
|
||||||
Details: (props) => <RoleDetails {...props}/>
|
components: {
|
||||||
|
Details: (props) => <RoleDetails {...props}/>
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "ClusterRole",
|
||||||
|
apiVersions: ["rbac.authorization.k8s.io/v1"],
|
||||||
|
components: {
|
||||||
|
Details: (props) => <RoleDetails {...props}/>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
])
|
||||||
|
|
||||||
kubeObjectDetailRegistry.add({
|
|
||||||
kind: "ClusterRole",
|
|
||||||
apiVersions: ["rbac.authorization.k8s.io/v1"],
|
|
||||||
components: {
|
|
||||||
Details: (props) => <RoleDetails {...props}/>
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@ -132,10 +132,12 @@ export class ServiceAccountsDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeObjectDetailRegistry.add({
|
kubeObjectDetailRegistry.add(null, [
|
||||||
kind: "ServiceAccount",
|
{
|
||||||
apiVersions: ["v1"],
|
kind: "ServiceAccount",
|
||||||
components: {
|
apiVersions: ["v1"],
|
||||||
Details: (props) => <ServiceAccountsDetails {...props} />
|
components: {
|
||||||
|
Details: (props) => <ServiceAccountsDetails {...props} />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
])
|
||||||
|
|||||||
@ -77,10 +77,12 @@ function ServiceAccountMenu(props: KubeObjectMenuProps<ServiceAccount>) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeObjectMenuRegistry.add({
|
kubeObjectMenuRegistry.add(null, [
|
||||||
kind: "ServiceAccount",
|
{
|
||||||
apiVersions: ["v1"],
|
kind: "ServiceAccount",
|
||||||
components: {
|
apiVersions: ["v1"],
|
||||||
MenuItem: ServiceAccountMenu
|
components: {
|
||||||
|
MenuItem: ServiceAccountMenu
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
])
|
||||||
|
|||||||
@ -87,10 +87,12 @@ export class CronJobDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeObjectDetailRegistry.add({
|
kubeObjectDetailRegistry.add(null, [
|
||||||
kind: "CronJob",
|
{
|
||||||
apiVersions: ["batch/v1"],
|
kind: "CronJob",
|
||||||
components: {
|
apiVersions: ["batch/v1"],
|
||||||
Details: (props) => <CronJobDetails {...props} />
|
components: {
|
||||||
|
Details: (props) => <CronJobDetails {...props} />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
])
|
||||||
|
|||||||
@ -88,10 +88,12 @@ export function CronJobMenu(props: KubeObjectMenuProps<CronJob>) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeObjectMenuRegistry.add({
|
kubeObjectMenuRegistry.add(null, [
|
||||||
kind: "CronJob",
|
{
|
||||||
apiVersions: ["batch/v1beta1"],
|
kind: "CronJob",
|
||||||
components: {
|
apiVersions: ["batch/v1beta1"],
|
||||||
MenuItem: CronJobMenu
|
components: {
|
||||||
|
MenuItem: CronJobMenu
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
])
|
||||||
|
|||||||
@ -97,10 +97,12 @@ export class DaemonSetDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeObjectDetailRegistry.add({
|
kubeObjectDetailRegistry.add(null, [
|
||||||
kind: "DaemonSet",
|
{
|
||||||
apiVersions: ["apps/v1"],
|
kind: "DaemonSet",
|
||||||
components: {
|
apiVersions: ["apps/v1"],
|
||||||
Details: (props: any) => <DaemonSetDetails {...props} />
|
components: {
|
||||||
|
Details: (props: any) => <DaemonSetDetails {...props} />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
])
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { disposeOnUnmount, observer } from "mobx-react";
|
|||||||
import { t, Trans } from "@lingui/macro";
|
import { t, Trans } from "@lingui/macro";
|
||||||
import { DrawerItem } from "../drawer";
|
import { DrawerItem } from "../drawer";
|
||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
import { Deployment, deploymentApi } from "../../api/endpoints";
|
import { Deployment } from "../../api/endpoints";
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames } from "../../utils";
|
||||||
import { PodDetailsTolerations } from "../+workloads-pods/pod-details-tolerations";
|
import { PodDetailsTolerations } from "../+workloads-pods/pod-details-tolerations";
|
||||||
import { PodDetailsAffinities } from "../+workloads-pods/pod-details-affinities";
|
import { PodDetailsAffinities } from "../+workloads-pods/pod-details-affinities";
|
||||||
@ -122,10 +122,12 @@ export class DeploymentDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeObjectDetailRegistry.add({
|
kubeObjectDetailRegistry.add(null, [
|
||||||
kind: "Deployment",
|
{
|
||||||
apiVersions: ["apps/v1"],
|
kind: "Deployment",
|
||||||
components: {
|
apiVersions: ["apps/v1"],
|
||||||
Details: (props: any) => <DeploymentDetails {...props} />
|
components: {
|
||||||
|
Details: (props: any) => <DeploymentDetails {...props} />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
])
|
||||||
|
|||||||
@ -129,10 +129,12 @@ export function DeploymentMenu(props: KubeObjectMenuProps<Deployment>) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeObjectMenuRegistry.add({
|
kubeObjectMenuRegistry.add(null, [
|
||||||
kind: "Deployment",
|
{
|
||||||
apiVersions: ["apps/v1"],
|
kind: "Deployment",
|
||||||
components: {
|
apiVersions: ["apps/v1"],
|
||||||
MenuItem: DeploymentMenu
|
components: {
|
||||||
|
MenuItem: DeploymentMenu
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
])
|
||||||
|
|||||||
@ -107,10 +107,12 @@ export class JobDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeObjectDetailRegistry.add({
|
kubeObjectDetailRegistry.add(null, [
|
||||||
kind: "Job",
|
{
|
||||||
apiVersions: ["batch/v1"],
|
kind: "Job",
|
||||||
components: {
|
apiVersions: ["batch/v1"],
|
||||||
Details: (props: any) => <JobDetails {...props}/>
|
components: {
|
||||||
|
Details: (props: any) => <JobDetails {...props}/>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
])
|
||||||
|
|||||||
@ -221,10 +221,12 @@ export class PodDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeObjectDetailRegistry.add({
|
kubeObjectDetailRegistry.add(null, [
|
||||||
kind: "Pod",
|
{
|
||||||
apiVersions: ["v1"],
|
kind: "Pod",
|
||||||
components: {
|
apiVersions: ["v1"],
|
||||||
Details: (props: any) => <PodDetails {...props} />
|
components: {
|
||||||
|
Details: (props: any) => <PodDetails {...props} />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
])
|
||||||
|
|||||||
@ -97,10 +97,12 @@ export class ReplicaSetDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeObjectDetailRegistry.add({
|
kubeObjectDetailRegistry.add(null, [
|
||||||
kind: "ReplicaSet",
|
{
|
||||||
apiVersions: ["apps/v1"],
|
kind: "ReplicaSet",
|
||||||
components: {
|
apiVersions: ["apps/v1"],
|
||||||
Details: (props: any) => <ReplicaSetDetails {...props} />
|
components: {
|
||||||
|
Details: (props: any) => <ReplicaSetDetails {...props} />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
])
|
||||||
|
|||||||
@ -96,10 +96,12 @@ export class StatefulSetDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
kubeObjectDetailRegistry.add({
|
kubeObjectDetailRegistry.add(null, [
|
||||||
kind: "StatefulSet",
|
{
|
||||||
apiVersions: ["apps/v1"],
|
kind: "StatefulSet",
|
||||||
components: {
|
apiVersions: ["apps/v1"],
|
||||||
Details: (props: any) => <StatefulSetDetails {...props} />
|
components: {
|
||||||
|
Details: (props: any) => <StatefulSetDetails {...props} />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
])
|
||||||
|
|||||||
@ -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 { clusterPageRegistry } from "../../extensions/registries/page-registry";
|
import { clusterPageRegistry } from "../../extensions/registries/page-registry";
|
||||||
|
import { clusterPageMenuRegistry } from "../../extensions/registries";
|
||||||
import { extensionLoader } from "../../extensions/extension-loader";
|
import { extensionLoader } from "../../extensions/extension-loader";
|
||||||
import { appEventBus } from "../../common/event-bus";
|
import { appEventBus } from "../../common/event-bus";
|
||||||
import whatInput from 'what-input';
|
import whatInput from 'what-input';
|
||||||
@ -72,6 +73,34 @@ export class App extends React.Component {
|
|||||||
return workloadsURL();
|
return workloadsURL();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderExtensionRoutes() {
|
||||||
|
return clusterPageRegistry.getItems().map(({ components: { Page }, exact, routePath, subPages }) => {
|
||||||
|
const Component = () => {
|
||||||
|
if (subPages) {
|
||||||
|
const tabs: TabLayoutRoute[] = subPages.map(({ exact, routePath, components: { Page } }) => {
|
||||||
|
const matchingUrl = clusterPageMenuRegistry.getByMatchingRoute(routePath, exact)
|
||||||
|
if (!matchingUrl) return;
|
||||||
|
return {
|
||||||
|
routePath, exact,
|
||||||
|
component: Page,
|
||||||
|
url: matchingUrl.url,
|
||||||
|
title: matchingUrl.title,
|
||||||
|
}
|
||||||
|
}).filter(Boolean);
|
||||||
|
if (tabs.length > 0) {
|
||||||
|
return (
|
||||||
|
<Page>
|
||||||
|
<TabLayout tabs={tabs}/>
|
||||||
|
</Page>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return <Page/>
|
||||||
|
};
|
||||||
|
return <Route key={routePath} path={routePath} exact={exact} component={Component}/>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<I18nProvider i18n={_i18n}>
|
<I18nProvider i18n={_i18n}>
|
||||||
@ -90,15 +119,7 @@ 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}/>
|
||||||
{clusterPageRegistry.getItems().map(({ components: { Page }, subPages = [], exact, routePath }) => {
|
{this.renderExtensionRoutes()}
|
||||||
// return (
|
|
||||||
// <Route key={routePath} path={routePath} exact={exact} render={() => (
|
|
||||||
// <TabLayout tabs={subPages}>
|
|
||||||
// <Page/>
|
|
||||||
// </TabLayout>
|
|
||||||
// )}/>
|
|
||||||
// )
|
|
||||||
})}
|
|
||||||
<Redirect exact from="/" to={this.startURL}/>
|
<Redirect exact from="/" to={this.startURL}/>
|
||||||
<Route component={NotFound}/>
|
<Route component={NotFound}/>
|
||||||
</Switch>
|
</Switch>
|
||||||
|
|||||||
@ -23,7 +23,7 @@ import { Tooltip } from "../tooltip";
|
|||||||
import { ConfirmDialog } from "../confirm-dialog";
|
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 { globalPageMenuRegistry } from "../../../extensions/registries";
|
import { globalPageMenuRegistry, globalPageRegistry } from "../../../extensions/registries";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: IClassName;
|
className?: IClassName;
|
||||||
@ -150,8 +150,10 @@ export class ClustersMenu extends React.Component<Props> {
|
|||||||
</div>
|
</div>
|
||||||
<div className="extensions">
|
<div className="extensions">
|
||||||
{globalPageMenuRegistry.getItems().map(({ title, url, components: { Icon } }) => {
|
{globalPageMenuRegistry.getItems().map(({ title, url, components: { Icon } }) => {
|
||||||
const routePath = "" // todo: find matching route in page-registry + exact
|
const registeredPage = globalPageRegistry.getByMatchingUrl(url);
|
||||||
const isActive = !!matchPath(navigation.location.pathname, { path: routePath/*, exact: false*/ });
|
if (!registeredPage) return;
|
||||||
|
const { routePath, exact } = registeredPage;
|
||||||
|
const isActive = !!matchPath(navigation.location.pathname, { path: routePath, exact });
|
||||||
return (
|
return (
|
||||||
<Icon
|
<Icon
|
||||||
key={routePath}
|
key={routePath}
|
||||||
|
|||||||
@ -4,17 +4,17 @@ import "./sidebar.scss";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { computed, observable, reaction } from "mobx";
|
import { computed, observable, reaction } from "mobx";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { matchPath, NavLink } from "react-router-dom";
|
import { NavLink } from "react-router-dom";
|
||||||
import { Trans } from "@lingui/macro";
|
import { Trans } from "@lingui/macro";
|
||||||
import { createStorage, cssNames } from "../../utils";
|
import { createStorage, cssNames } from "../../utils";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { workloadsRoute, workloadsURL } from "../+workloads/workloads.route";
|
import { workloadsRoute, workloadsURL } from "../+workloads/workloads.route";
|
||||||
import { namespacesURL } from "../+namespaces/namespaces.route";
|
import { namespacesRoute, namespacesURL } from "../+namespaces/namespaces.route";
|
||||||
import { nodesURL } from "../+nodes/nodes.route";
|
import { nodesRoute, nodesURL } from "../+nodes/nodes.route";
|
||||||
import { usersManagementRoute, usersManagementURL } from "../+user-management/user-management.route";
|
import { usersManagementRoute, usersManagementURL } from "../+user-management/user-management.route";
|
||||||
import { networkRoute, networkURL } from "../+network/network.route";
|
import { networkRoute, networkURL } from "../+network/network.route";
|
||||||
import { storageRoute, storageURL } from "../+storage/storage.route";
|
import { storageRoute, storageURL } from "../+storage/storage.route";
|
||||||
import { clusterURL } from "../+cluster";
|
import { clusterRoute, clusterURL } from "../+cluster";
|
||||||
import { Config, configRoute, configURL } from "../+config";
|
import { Config, configRoute, configURL } from "../+config";
|
||||||
import { eventRoute, eventsURL } from "../+events";
|
import { eventRoute, eventsURL } from "../+events";
|
||||||
import { Apps, appsRoute, appsURL } from "../+apps";
|
import { Apps, appsRoute, appsURL } from "../+apps";
|
||||||
@ -26,10 +26,10 @@ import { Network } from "../+network";
|
|||||||
import { crdStore } from "../+custom-resources/crd.store";
|
import { crdStore } from "../+custom-resources/crd.store";
|
||||||
import { CrdList, crdResourcesRoute, crdRoute, crdURL } from "../+custom-resources";
|
import { CrdList, crdResourcesRoute, crdRoute, crdURL } from "../+custom-resources";
|
||||||
import { CustomResources } from "../+custom-resources/custom-resources";
|
import { CustomResources } from "../+custom-resources/custom-resources";
|
||||||
import { navigation } from "../../navigation";
|
import { isActiveRoute } from "../../navigation";
|
||||||
import { isAllowedResource } from "../../../common/rbac"
|
import { isAllowedResource } from "../../../common/rbac"
|
||||||
import { Spinner } from "../spinner";
|
import { Spinner } from "../spinner";
|
||||||
import { clusterPageMenuRegistry } from "../../../extensions/registries";
|
import { clusterPageMenuRegistry, clusterPageRegistry } from "../../../extensions/registries";
|
||||||
|
|
||||||
const SidebarContext = React.createContext<SidebarContextValue>({ pinned: false });
|
const SidebarContext = React.createContext<SidebarContextValue>({ pinned: false });
|
||||||
type SidebarContextValue = {
|
type SidebarContextValue = {
|
||||||
@ -52,7 +52,7 @@ export class Sidebar extends React.Component<Props> {
|
|||||||
|
|
||||||
renderCustomResources() {
|
renderCustomResources() {
|
||||||
if (crdStore.isLoading) {
|
if (crdStore.isLoading) {
|
||||||
return <Spinner centerHorizontal />
|
return <Spinner centerHorizontal/>
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.entries(crdStore.groups).map(([group, crds]) => {
|
return Object.entries(crdStore.groups).map(([group, crds]) => {
|
||||||
@ -84,7 +84,7 @@ export class Sidebar extends React.Component<Props> {
|
|||||||
<div className={cssNames("Sidebar flex column", className, { pinned: isPinned })}>
|
<div className={cssNames("Sidebar flex column", className, { pinned: isPinned })}>
|
||||||
<div className="header flex align-center">
|
<div className="header flex align-center">
|
||||||
<NavLink exact to="/" className="box grow">
|
<NavLink exact to="/" className="box grow">
|
||||||
<Icon svg="logo-lens" className="logo-icon" />
|
<Icon svg="logo-lens" className="logo-icon"/>
|
||||||
<div className="logo-text">Lens</div>
|
<div className="logo-text">Lens</div>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
<Icon
|
<Icon
|
||||||
@ -97,93 +97,100 @@ export class Sidebar extends React.Component<Props> {
|
|||||||
</div>
|
</div>
|
||||||
<div className="sidebar-nav flex column box grow-fixed">
|
<div className="sidebar-nav flex column box grow-fixed">
|
||||||
<SidebarNavItem
|
<SidebarNavItem
|
||||||
|
isActive={isActiveRoute(clusterRoute)}
|
||||||
isHidden={!isAllowedResource("nodes")}
|
isHidden={!isAllowedResource("nodes")}
|
||||||
url={clusterURL()}
|
url={clusterURL()}
|
||||||
text={<Trans>Cluster</Trans>}
|
text={<Trans>Cluster</Trans>}
|
||||||
icon={<Icon svg="kube" />}
|
icon={<Icon svg="kube"/>}
|
||||||
/>
|
/>
|
||||||
<SidebarNavItem
|
<SidebarNavItem
|
||||||
|
isActive={isActiveRoute(nodesRoute)}
|
||||||
isHidden={!isAllowedResource("nodes")}
|
isHidden={!isAllowedResource("nodes")}
|
||||||
url={nodesURL()}
|
url={nodesURL()}
|
||||||
text={<Trans>Nodes</Trans>}
|
text={<Trans>Nodes</Trans>}
|
||||||
icon={<Icon svg="nodes" />}
|
icon={<Icon svg="nodes"/>}
|
||||||
/>
|
/>
|
||||||
<SidebarNavItem
|
<SidebarNavItem
|
||||||
|
isActive={isActiveRoute(workloadsRoute)}
|
||||||
isHidden={Workloads.tabRoutes.length == 0}
|
isHidden={Workloads.tabRoutes.length == 0}
|
||||||
url={workloadsURL({ query })}
|
url={workloadsURL({ query })}
|
||||||
routePath={workloadsRoute.path}
|
|
||||||
subMenus={Workloads.tabRoutes}
|
subMenus={Workloads.tabRoutes}
|
||||||
text={<Trans>Workloads</Trans>}
|
text={<Trans>Workloads</Trans>}
|
||||||
icon={<Icon svg="workloads" />}
|
icon={<Icon svg="workloads"/>}
|
||||||
/>
|
/>
|
||||||
<SidebarNavItem
|
<SidebarNavItem
|
||||||
|
isActive={isActiveRoute(configRoute)}
|
||||||
isHidden={Config.tabRoutes.length == 0}
|
isHidden={Config.tabRoutes.length == 0}
|
||||||
url={configURL({ query })}
|
url={configURL({ query })}
|
||||||
routePath={configRoute.path}
|
|
||||||
subMenus={Config.tabRoutes}
|
subMenus={Config.tabRoutes}
|
||||||
text={<Trans>Configuration</Trans>}
|
text={<Trans>Configuration</Trans>}
|
||||||
icon={<Icon material="list" />}
|
icon={<Icon material="list"/>}
|
||||||
/>
|
/>
|
||||||
<SidebarNavItem
|
<SidebarNavItem
|
||||||
|
isActive={isActiveRoute(networkRoute)}
|
||||||
isHidden={Network.tabRoutes.length == 0}
|
isHidden={Network.tabRoutes.length == 0}
|
||||||
url={networkURL({ query })}
|
url={networkURL({ query })}
|
||||||
routePath={networkRoute.path}
|
|
||||||
subMenus={Network.tabRoutes}
|
subMenus={Network.tabRoutes}
|
||||||
text={<Trans>Network</Trans>}
|
text={<Trans>Network</Trans>}
|
||||||
icon={<Icon material="device_hub" />}
|
icon={<Icon material="device_hub"/>}
|
||||||
/>
|
/>
|
||||||
<SidebarNavItem
|
<SidebarNavItem
|
||||||
|
isActive={isActiveRoute(storageRoute)}
|
||||||
isHidden={Storage.tabRoutes.length == 0}
|
isHidden={Storage.tabRoutes.length == 0}
|
||||||
url={storageURL({ query })}
|
url={storageURL({ query })}
|
||||||
routePath={storageRoute.path}
|
|
||||||
subMenus={Storage.tabRoutes}
|
subMenus={Storage.tabRoutes}
|
||||||
icon={<Icon svg="storage" />}
|
icon={<Icon svg="storage"/>}
|
||||||
text={<Trans>Storage</Trans>}
|
text={<Trans>Storage</Trans>}
|
||||||
/>
|
/>
|
||||||
<SidebarNavItem
|
<SidebarNavItem
|
||||||
|
isActive={isActiveRoute(namespacesRoute)}
|
||||||
isHidden={!isAllowedResource("namespaces")}
|
isHidden={!isAllowedResource("namespaces")}
|
||||||
url={namespacesURL()}
|
url={namespacesURL()}
|
||||||
icon={<Icon material="layers" />}
|
icon={<Icon material="layers"/>}
|
||||||
text={<Trans>Namespaces</Trans>}
|
text={<Trans>Namespaces</Trans>}
|
||||||
/>
|
/>
|
||||||
<SidebarNavItem
|
<SidebarNavItem
|
||||||
|
isActive={isActiveRoute(eventRoute)}
|
||||||
isHidden={!isAllowedResource("events")}
|
isHidden={!isAllowedResource("events")}
|
||||||
url={eventsURL({ query })}
|
url={eventsURL({ query })}
|
||||||
routePath={eventRoute.path}
|
icon={<Icon material="access_time"/>}
|
||||||
icon={<Icon material="access_time" />}
|
|
||||||
text={<Trans>Events</Trans>}
|
text={<Trans>Events</Trans>}
|
||||||
/>
|
/>
|
||||||
<SidebarNavItem
|
<SidebarNavItem
|
||||||
|
isActive={isActiveRoute(appsRoute)}
|
||||||
url={appsURL({ query })}
|
url={appsURL({ query })}
|
||||||
subMenus={Apps.tabRoutes}
|
subMenus={Apps.tabRoutes}
|
||||||
routePath={appsRoute.path}
|
icon={<Icon material="apps"/>}
|
||||||
icon={<Icon material="apps" />}
|
|
||||||
text={<Trans>Apps</Trans>}
|
text={<Trans>Apps</Trans>}
|
||||||
/>
|
/>
|
||||||
<SidebarNavItem
|
<SidebarNavItem
|
||||||
|
isActive={isActiveRoute(usersManagementRoute)}
|
||||||
url={usersManagementURL({ query })}
|
url={usersManagementURL({ query })}
|
||||||
routePath={usersManagementRoute.path}
|
|
||||||
subMenus={UserManagement.tabRoutes}
|
subMenus={UserManagement.tabRoutes}
|
||||||
icon={<Icon material="security" />}
|
icon={<Icon material="security"/>}
|
||||||
text={<Trans>Access Control</Trans>}
|
text={<Trans>Access Control</Trans>}
|
||||||
/>
|
/>
|
||||||
<SidebarNavItem
|
<SidebarNavItem
|
||||||
|
isActive={isActiveRoute(crdRoute)}
|
||||||
isHidden={!isAllowedResource("customresourcedefinitions")}
|
isHidden={!isAllowedResource("customresourcedefinitions")}
|
||||||
url={crdURL()}
|
url={crdURL()}
|
||||||
subMenus={CustomResources.tabRoutes}
|
subMenus={CustomResources.tabRoutes}
|
||||||
routePath={crdRoute.path}
|
icon={<Icon material="extension"/>}
|
||||||
icon={<Icon material="extension" />}
|
|
||||||
text={<Trans>Custom Resources</Trans>}
|
text={<Trans>Custom Resources</Trans>}
|
||||||
>
|
>
|
||||||
{this.renderCustomResources()}
|
{this.renderCustomResources()}
|
||||||
</SidebarNavItem>
|
</SidebarNavItem>
|
||||||
{clusterPageMenuRegistry.getItems().map(({ title, url, components: { Icon } }) => {
|
{clusterPageMenuRegistry.getItems().map(({ title, url, components: { Icon } }) => {
|
||||||
const routePath = "" // todo: find in page-registry
|
const registeredPage = clusterPageRegistry.getByMatchingUrl(url);
|
||||||
|
if (!registeredPage) return;
|
||||||
|
const { routePath, exact } = registeredPage;
|
||||||
return (
|
return (
|
||||||
<SidebarNavItem
|
<SidebarNavItem
|
||||||
key={url} url={url}
|
key={url}
|
||||||
routePath={routePath}
|
url={url}
|
||||||
text={title} icon={<Icon />}
|
text={title}
|
||||||
|
icon={<Icon/>}
|
||||||
|
isActive={isActiveRoute({ path: routePath, exact })}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@ -200,7 +207,7 @@ interface SidebarNavItemProps {
|
|||||||
className?: string;
|
className?: string;
|
||||||
icon?: React.ReactNode;
|
icon?: React.ReactNode;
|
||||||
isHidden?: boolean;
|
isHidden?: boolean;
|
||||||
routePath?: string | string[];
|
isActive?: boolean;
|
||||||
subMenus?: TabLayoutRoute[];
|
subMenus?: TabLayoutRoute[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,27 +232,19 @@ class SidebarNavItem extends React.Component<SidebarNavItemProps> {
|
|||||||
navItemState.set(this.itemId, !this.isExpanded);
|
navItemState.set(this.itemId, !this.isExpanded);
|
||||||
};
|
};
|
||||||
|
|
||||||
isActive = () => {
|
|
||||||
const { url, routePath = url } = this.props;
|
|
||||||
return !!matchPath(navigation.location.pathname, {
|
|
||||||
path: routePath
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { isHidden, subMenus = [], icon, text, url, children, className } = this.props;
|
const { isHidden, isActive, subMenus = [], icon, text, url, children, className } = this.props;
|
||||||
if (isHidden) {
|
if (isHidden) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const extendedView = (subMenus.length > 0 || children) && this.context.pinned;
|
const extendedView = (subMenus.length > 0 || children) && this.context.pinned;
|
||||||
if (extendedView) {
|
if (extendedView) {
|
||||||
const isActive = this.isActive();
|
|
||||||
return (
|
return (
|
||||||
<div className={cssNames("SidebarNavItem", className)}>
|
<div className={cssNames("SidebarNavItem", className)}>
|
||||||
<div className={cssNames("nav-item", { active: isActive })} onClick={this.toggleSubMenu}>
|
<div className={cssNames("nav-item", { active: isActive })} onClick={this.toggleSubMenu}>
|
||||||
{icon}
|
{icon}
|
||||||
<span className="link-text">{text}</span>
|
<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>
|
</div>
|
||||||
<ul className={cssNames("sub-menu", { active: isActive })}>
|
<ul className={cssNames("sub-menu", { active: isActive })}>
|
||||||
{subMenus.map(({ title, url }) => (
|
{subMenus.map(({ title, url }) => (
|
||||||
@ -263,7 +262,7 @@ class SidebarNavItem extends React.Component<SidebarNavItemProps> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<NavLink className={cssNames("SidebarNavItem", className)} to={url} isActive={this.isActive}>
|
<NavLink className={cssNames("SidebarNavItem", className)} to={url} isActive={() => isActive}>
|
||||||
{icon}
|
{icon}
|
||||||
<span className="link-text">{text}</span>
|
<span className="link-text">{text}</span>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
// Navigation helpers
|
// Navigation helpers
|
||||||
|
|
||||||
import { ipcRenderer } from "electron";
|
import { ipcRenderer } from "electron";
|
||||||
import { matchPath } from "react-router";
|
import { matchPath, RouteProps } from "react-router";
|
||||||
import { reaction } from "mobx";
|
import { reaction } from "mobx";
|
||||||
import { createObservableHistory } from "mobx-observable-history";
|
import { createObservableHistory } from "mobx-observable-history";
|
||||||
import { createBrowserHistory, createMemoryHistory, LocationDescriptor } from "history";
|
import { createBrowserHistory, createMemoryHistory, LocationDescriptor } from "history";
|
||||||
@ -19,6 +19,10 @@ export function navigate(location: LocationDescriptor) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isActiveRoute(route: string | string[] | RouteProps): boolean {
|
||||||
|
return !!matchPath(navigation.location.pathname, route);
|
||||||
|
}
|
||||||
|
|
||||||
// common params for all pages
|
// common params for all pages
|
||||||
export interface IQueryParams {
|
export interface IQueryParams {
|
||||||
namespaces?: string[]; // selected context namespaces
|
namespaces?: string[]; // selected context namespaces
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user