diff --git a/src/common/catalog-entities/kubernetes-cluster.ts b/src/common/catalog-entities/kubernetes-cluster.ts index d0760dcdf1..55f9d38feb 100644 --- a/src/common/catalog-entities/kubernetes-cluster.ts +++ b/src/common/catalog-entities/kubernetes-cluster.ts @@ -50,7 +50,7 @@ export class KubernetesCluster implements CatalogEntity { icon: "settings", title: "Settings", onlyVisibleForSource: "local", - onClick: async () => context.navigate(`/cluster/${this.metadata.uid}/settings`) + onClick: async () => context.navigate(`/entity/${this.metadata.uid}/settings`) }, { icon: "delete", diff --git a/src/common/catalog-entity.ts b/src/common/catalog-entity.ts index 19d83d5437..8430381368 100644 --- a/src/common/catalog-entity.ts +++ b/src/common/catalog-entity.ts @@ -52,11 +52,23 @@ export type CatalogEntityContextMenu = { } }; +export type CatalogEntitySettingsMenu = { + group?: string; + title: string; + components: { + View: React.ComponentType + }; +}; + export interface CatalogEntityContextMenuContext { navigate: (url: string) => void; menuItems: CatalogEntityContextMenu[]; } +export interface CatalogEntitySettingsContext { + menuItems: CatalogEntityContextMenu[]; +} + export interface CatalogEntityAddMenuContext { navigate: (url: string) => void; menuItems: CatalogEntityContextMenu[]; @@ -78,4 +90,5 @@ export interface CatalogEntity extends CatalogEntityData { onRun: (context: CatalogEntityActionContext) => Promise; onDetailsOpen: (context: CatalogEntityActionContext) => Promise; onContextMenuOpen: (context: CatalogEntityContextMenuContext) => Promise; + onSettingsOpen?: (context: CatalogEntitySettingsContext) => Promise; } diff --git a/src/common/cluster-store.ts b/src/common/cluster-store.ts index d1f11d8372..f1f10d29c3 100644 --- a/src/common/cluster-store.ts +++ b/src/common/cluster-store.ts @@ -11,7 +11,7 @@ import { dumpConfigYaml } from "./kube-helpers"; import { saveToAppFiles } from "./utils/saveToAppFiles"; import { KubeConfig } from "@kubernetes/client-node"; import { handleRequest, requestMain, subscribeToBroadcast, unsubscribeAllFromBroadcast } from "./ipc"; -import { ResourceType } from "../renderer/components/+cluster-settings/components/cluster-metrics-setting"; +import { ResourceType } from "../renderer/components/cluster-settings/components/cluster-metrics-setting"; export interface ClusterIconUpload { clusterId: string; diff --git a/src/extensions/extension-loader.ts b/src/extensions/extension-loader.ts index d36123b95a..5ae041067f 100644 --- a/src/extensions/extension-loader.ts +++ b/src/extensions/extension-loader.ts @@ -211,8 +211,8 @@ export class ExtensionLoader { this.autoInitExtensions(async (extension: LensRendererExtension) => { const removeItems = [ registries.globalPageRegistry.add(extension.globalPages, extension), - registries.globalPageMenuRegistry.add(extension.globalPageMenus, extension), registries.appPreferenceRegistry.add(extension.appPreferences), + registries.entitySettingRegistry.add(extension.entitySettings), registries.statusBarRegistry.add(extension.statusBarItems), registries.commandRegistry.add(extension.commands), ]; diff --git a/src/extensions/lens-renderer-extension.ts b/src/extensions/lens-renderer-extension.ts index bf1f8cbeb3..fabb876408 100644 --- a/src/extensions/lens-renderer-extension.ts +++ b/src/extensions/lens-renderer-extension.ts @@ -3,6 +3,7 @@ import type { Cluster } from "../main/cluster"; import { LensExtension } from "./lens-extension"; import { getExtensionPageUrl } from "./registries/page-registry"; import { CommandRegistration } from "./registries/command-registry"; +import { EntitySettingRegistration } from "./registries/entity-setting-registry"; export class LensRendererExtension extends LensExtension { globalPages: PageRegistration[] = []; @@ -11,6 +12,7 @@ export class LensRendererExtension extends LensExtension { clusterPageMenus: ClusterPageMenuRegistration[] = []; kubeObjectStatusTexts: KubeObjectStatusRegistration[] = []; appPreferences: AppPreferenceRegistration[] = []; + entitySettings: EntitySettingRegistration[] = []; statusBarItems: StatusBarRegistration[] = []; kubeObjectDetailItems: KubeObjectDetailRegistration[] = []; kubeObjectMenuItems: KubeObjectMenuRegistration[] = []; diff --git a/src/extensions/registries/entity-setting-registry.ts b/src/extensions/registries/entity-setting-registry.ts new file mode 100644 index 0000000000..8dd5ce6f25 --- /dev/null +++ b/src/extensions/registries/entity-setting-registry.ts @@ -0,0 +1,49 @@ +import type React from "react"; +import { CatalogEntity } from "../../common/catalog-entity"; +import { BaseRegistry } from "./base-registry"; + +export interface EntitySettingViewProps { + entity: CatalogEntity; +} + +export interface EntitySettingComponents { + View: React.ComponentType; +} + +export interface EntitySettingRegistration { + title: string; + kind: string; + apiVersions: string[]; + source?: string; + id?: string; + components: EntitySettingComponents; +} + +export interface RegisteredEntitySetting extends EntitySettingRegistration { + id: string; +} + +export class EntitySettingRegistry extends BaseRegistry { + getRegisteredItem(item: EntitySettingRegistration): RegisteredEntitySetting { + return { + id: item.id || item.title.toLowerCase(), + ...item, + }; + } + + getItemsForKind(kind: string, apiVersion: string, source?: string) { + const items = this.getItems().filter((item) => { + return item.kind === kind && item.apiVersions.includes(apiVersion); + }); + + if (source) { + return items.filter((item) => { + return !item.source || item.source === source; + }); + } else { + return items; + } + } +} + +export const entitySettingRegistry = new EntitySettingRegistry(); diff --git a/src/extensions/registries/index.ts b/src/extensions/registries/index.ts index 9f0aba5f0d..419f717477 100644 --- a/src/extensions/registries/index.ts +++ b/src/extensions/registries/index.ts @@ -9,3 +9,4 @@ export * from "./kube-object-detail-registry"; export * from "./kube-object-menu-registry"; export * from "./kube-object-status-registry"; export * from "./command-registry"; +export * from "./entity-setting-registry"; diff --git a/src/extensions/registries/page-menu-registry.ts b/src/extensions/registries/page-menu-registry.ts index 8fe5b68b3b..25796bbda5 100644 --- a/src/extensions/registries/page-menu-registry.ts +++ b/src/extensions/registries/page-menu-registry.ts @@ -57,5 +57,4 @@ export class ClusterPageMenuRegistry extends PageMenuRegistry { - item.enabled = false; - }); - } - - return menuItems; - } - async function navigate(url: string) { logger.info(`[MENU]: navigating to ${url}`); await windowManager.navigate(url); @@ -112,19 +101,6 @@ export function buildMenu(windowManager: WindowManager) { navigate(addClusterURL()); } }, - ...activeClusterOnly([ - { - label: "Cluster Settings", - accelerator: "CmdOrCtrl+Shift+S", - click() { - navigate(clusterSettingsURL({ - params: { - clusterId: windowManager.activeClusterId - } - })); - } - } - ]), ...ignoreOnMac([ { type: "separator" }, { diff --git a/src/renderer/api/catalog-entity-registry.ts b/src/renderer/api/catalog-entity-registry.ts index afed7a88cf..12450023ee 100644 --- a/src/renderer/api/catalog-entity-registry.ts +++ b/src/renderer/api/catalog-entity-registry.ts @@ -44,6 +44,10 @@ export class CatalogEntityRegistry { return this._items; } + getById(id: string) { + return this._items.find((entity) => entity.metadata.uid === id); + } + getItemsForApiKind(apiVersion: string, kind: string): T[] { const items = this._items.filter((item) => item.apiVersion === apiVersion && item.kind === kind); diff --git a/src/renderer/components/+add-cluster/add-cluster.tsx b/src/renderer/components/+add-cluster/add-cluster.tsx index 94980a4d15..11681f76fa 100644 --- a/src/renderer/components/+add-cluster/add-cluster.tsx +++ b/src/renderer/components/+add-cluster/add-cluster.tsx @@ -345,7 +345,7 @@ export class AddCluster extends React.Component { return ( -

Add Clusters

} showOnTop={true}> +

Add Clusters from Kubeconfig

{this.renderInfo()} {this.renderKubeConfigSource()} diff --git a/src/renderer/components/+cluster-settings/cluster-settings.route.ts b/src/renderer/components/+cluster-settings/cluster-settings.route.ts deleted file mode 100644 index 11a373dcdb..0000000000 --- a/src/renderer/components/+cluster-settings/cluster-settings.route.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { IClusterViewRouteParams } from "../cluster-manager/cluster-view.route"; -import type { RouteProps } from "react-router"; -import { buildURL } from "../../../common/utils/buildUrl"; - -export interface IClusterSettingsRouteParams extends IClusterViewRouteParams { -} - -export const clusterSettingsRoute: RouteProps = { - path: `/cluster/:clusterId/settings`, -}; - -export const clusterSettingsURL = buildURL(clusterSettingsRoute.path); diff --git a/src/renderer/components/+cluster-settings/cluster-settings.scss b/src/renderer/components/+cluster-settings/cluster-settings.scss deleted file mode 100644 index dd1cd37f60..0000000000 --- a/src/renderer/components/+cluster-settings/cluster-settings.scss +++ /dev/null @@ -1,51 +0,0 @@ -.ClusterSettings { - $spacing: $padding * 3; - - > .content-wrapper { - --flex-gap: #{$spacing}; - } - - // TODO: move sub-component styles to separate files - .admin-note { - font-size: small; - opacity: 0.5; - margin-left: $margin; - } - - .button-area { - margin-top: $margin * 2; - } - - .file-loader { - margin-top: $margin * 2; - } - - .status-table { - margin: $spacing 0; - - .Table { - border: 1px solid var(--drawerSubtitleBackground); - border-radius: $radius; - - .TableRow { - &:not(:last-of-type) { - border-bottom: 1px solid var(--drawerSubtitleBackground); - } - - .value { - flex-grow: 2; - word-break: break-word; - color: var(--textColorSecondary); - } - - .link { - @include pseudo-link; - } - } - } - } - - .Input, .Select { - margin-top: $padding; - } -} \ No newline at end of file diff --git a/src/renderer/components/+cluster-settings/cluster-settings.tsx b/src/renderer/components/+cluster-settings/cluster-settings.tsx deleted file mode 100644 index 0334f997f7..0000000000 --- a/src/renderer/components/+cluster-settings/cluster-settings.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import "./cluster-settings.scss"; - -import React from "react"; -import { reaction } from "mobx"; -import { RouteComponentProps } from "react-router"; -import { observer, disposeOnUnmount } from "mobx-react"; -import { Status } from "./status"; -import { General } from "./general"; -import { Cluster } from "../../../main/cluster"; -import { IClusterSettingsRouteParams } from "./cluster-settings.route"; -import { clusterStore } from "../../../common/cluster-store"; -import { PageLayout } from "../layout/page-layout"; -import { requestMain } from "../../../common/ipc"; -import { clusterActivateHandler, clusterRefreshHandler } from "../../../common/cluster-ipc"; -import { navigation } from "../../navigation"; - -interface Props extends RouteComponentProps { -} - -@observer -export class ClusterSettings extends React.Component { - get clusterId() { - return this.props.match.params.clusterId; - } - - get cluster(): Cluster { - return clusterStore.getById(this.clusterId); - } - - componentDidMount() { - const { hash } = navigation.location; - - document.getElementById(hash.slice(1))?.scrollIntoView(); - - disposeOnUnmount(this, [ - reaction(() => this.cluster, this.refreshCluster, { - fireImmediately: true, - }), - reaction(() => this.clusterId, clusterId => clusterStore.setActive(clusterId), { - fireImmediately: true, - }) - ]); - } - - refreshCluster = async () => { - if (this.cluster) { - await requestMain(clusterActivateHandler, this.cluster.id); - await requestMain(clusterRefreshHandler, this.cluster.id); - } - }; - - render() { - const cluster = this.cluster; - - if (!cluster) return null; - const header = ( - <> -

{cluster.preferences.clusterName}

- - ); - - return ( - - - - - ); - } -} diff --git a/src/renderer/components/+cluster-settings/general.tsx b/src/renderer/components/+cluster-settings/general.tsx deleted file mode 100644 index ecb0457f1c..0000000000 --- a/src/renderer/components/+cluster-settings/general.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from "react"; -import { Cluster } from "../../../main/cluster"; -import { ClusterNameSetting } from "./components/cluster-name-setting"; -import { ClusterProxySetting } from "./components/cluster-proxy-setting"; -import { ClusterPrometheusSetting } from "./components/cluster-prometheus-setting"; -import { ClusterHomeDirSetting } from "./components/cluster-home-dir-setting"; -import { ClusterAccessibleNamespaces } from "./components/cluster-accessible-namespaces"; -import { ClusterMetricsSetting } from "./components/cluster-metrics-setting"; -import { ShowMetricsSetting } from "./components/show-metrics"; - -interface Props { - cluster: Cluster; -} - -export class General extends React.Component { - render() { - return
-

General

- - - - - - - -
; - } -} diff --git a/src/renderer/components/+cluster-settings/status.tsx b/src/renderer/components/+cluster-settings/status.tsx deleted file mode 100644 index d43cfe5c35..0000000000 --- a/src/renderer/components/+cluster-settings/status.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import React from "react"; -import { Cluster } from "../../../main/cluster"; -import { SubTitle } from "../layout/sub-title"; -import { Table, TableCell, TableRow } from "../table"; -import { autobind } from "../../utils"; -import { shell } from "electron"; - -interface Props { - cluster: Cluster; -} - -export class Status extends React.Component { - - @autobind() - openKubeconfig() { - const { cluster } = this.props; - - shell.showItemInFolder(cluster.kubeConfigPath); - } - - renderStatusRows() { - const { cluster } = this.props; - const rows = [ - ["Online Status", cluster.online ? "online" : `offline (${cluster.failureReason || "unknown reason"})`], - ["Distribution", cluster.metadata.distribution ? String(cluster.metadata.distribution) : "N/A"], - ["Kernel Version", cluster.metadata.version ? String(cluster.metadata.version) : "N/A"], - ["API Address", cluster.apiUrl || "N/A"], - ["Nodes Count", cluster.metadata.nodes ? String(cluster.metadata.nodes) : "N/A"] - ]; - - return ( - - {rows.map(([name, value]) => { - return ( - - {name} - {value} - - ); - })} - - Kubeconfig - {cluster.kubeConfigPath} - -
- ); - } - - render() { - return
-

Status

- -

- Cluster status information including: detected distribution, kernel version, and online status. -

-
- {this.renderStatusRows()} -
-
; - } -} diff --git a/src/renderer/components/+cluster/cluster-overview.tsx b/src/renderer/components/+cluster/cluster-overview.tsx index eda8ac3090..23d3214340 100644 --- a/src/renderer/components/+cluster/cluster-overview.tsx +++ b/src/renderer/components/+cluster/cluster-overview.tsx @@ -13,7 +13,7 @@ import { ClusterIssues } from "./cluster-issues"; import { ClusterMetrics } from "./cluster-metrics"; import { clusterOverviewStore } from "./cluster-overview.store"; import { ClusterPieCharts } from "./cluster-pie-charts"; -import { ResourceType } from "../+cluster-settings/components/cluster-metrics-setting"; +import { ResourceType } from "../cluster-settings/components/cluster-metrics-setting"; @observer export class ClusterOverview extends React.Component { diff --git a/src/renderer/components/+entity-settings/entity-settings.route.ts b/src/renderer/components/+entity-settings/entity-settings.route.ts new file mode 100644 index 0000000000..f140353070 --- /dev/null +++ b/src/renderer/components/+entity-settings/entity-settings.route.ts @@ -0,0 +1,12 @@ +import type { RouteProps } from "react-router"; +import { buildURL } from "../../../common/utils/buildUrl"; + +export interface EntitySettingsRouteParams { + entityId: string; +} + +export const entitySettingsRoute: RouteProps = { + path: `/entity/:entityId/settings`, +}; + +export const entitySettingsURL = buildURL(entitySettingsRoute.path); diff --git a/src/renderer/components/+entity-settings/entity-settings.scss b/src/renderer/components/+entity-settings/entity-settings.scss new file mode 100644 index 0000000000..6c8189cfe6 --- /dev/null +++ b/src/renderer/components/+entity-settings/entity-settings.scss @@ -0,0 +1,23 @@ +.EntitySettings { + $spacing: $padding * 3; + + + // TODO: move sub-component styles to separate files + .admin-note { + font-size: small; + opacity: 0.5; + margin-left: $margin; + } + + .button-area { + margin-top: $margin * 2; + } + + .file-loader { + margin-top: $margin * 2; + } + + .Input, .Select { + margin-top: $padding; + } +} diff --git a/src/renderer/components/+entity-settings/entity-settings.tsx b/src/renderer/components/+entity-settings/entity-settings.tsx new file mode 100644 index 0000000000..ef21e2b3ad --- /dev/null +++ b/src/renderer/components/+entity-settings/entity-settings.tsx @@ -0,0 +1,99 @@ +import "./entity-settings.scss"; + +import React from "react"; +import { observable } from "mobx"; +import { RouteComponentProps } from "react-router"; +import { observer } from "mobx-react"; +import { PageLayout } from "../layout/page-layout"; +import { navigation } from "../../navigation"; +import { Tabs, Tab } from "../tabs"; +import { CatalogEntity } from "../../api/catalog-entity"; +import { catalogEntityRegistry } from "../../api/catalog-entity-registry"; +import { entitySettingRegistry } from "../../../extensions/registries"; +import { EntitySettingsRouteParams } from "./entity-settings.route"; + +interface Props extends RouteComponentProps { +} + +@observer +export class EntitySettings extends React.Component { + @observable activeTab: string; + + get entityId() { + return this.props.match.params.entityId; + } + + get entity(): CatalogEntity { + return catalogEntityRegistry.getById(this.entityId); + } + + get menuItems() { + if (!this.entity) return []; + + return entitySettingRegistry.getItemsForKind(this.entity.kind, this.entity.apiVersion, this.entity.metadata.source); + } + + async componentDidMount() { + const { hash } = navigation.location; + + this.ensureActiveTab(); + + document.getElementById(hash.slice(1))?.scrollIntoView(); + } + + onTabChange = (tabId: string) => { + this.activeTab = tabId; + }; + + renderNavigation() { + return ( + <> +

{this.entity.metadata.name}

+ +
Settings
+ { this.menuItems.map((setting) => ( + + ))} +
+ + ); + } + + ensureActiveTab() { + if (!this.activeTab) { + this.activeTab = this.menuItems[0]?.id; + } + } + + render() { + if (!this.entity) { + console.error("entity not found", this.entityId); + + return null; + } + + this.ensureActiveTab(); + const activeSetting = this.menuItems.find((setting) => setting.id === this.activeTab); + + return ( + +
+

{activeSetting.title}

+
+ +
+
+
+ ); + } +} diff --git a/src/renderer/components/+entity-settings/index.ts b/src/renderer/components/+entity-settings/index.ts new file mode 100644 index 0000000000..44a76065a2 --- /dev/null +++ b/src/renderer/components/+entity-settings/index.ts @@ -0,0 +1,4 @@ +import "../cluster-settings"; + +export * from "./entity-settings.route"; +export * from "./entity-settings"; diff --git a/src/renderer/components/+extensions/extensions.tsx b/src/renderer/components/+extensions/extensions.tsx index c14a6988b6..9b69689f76 100644 --- a/src/renderer/components/+extensions/extensions.tsx +++ b/src/renderer/components/+extensions/extensions.tsx @@ -482,12 +482,11 @@ export class Extensions extends React.Component { } render() { - const topHeader =

Manage Lens Extensions

; const { installPath } = this; return ( - +

Lens Extensions

Add new features and functionality via Lens Extensions. diff --git a/src/renderer/components/+network-ingresses/ingress-details.tsx b/src/renderer/components/+network-ingresses/ingress-details.tsx index bfbbfe6f5a..244738a537 100644 --- a/src/renderer/components/+network-ingresses/ingress-details.tsx +++ b/src/renderer/components/+network-ingresses/ingress-details.tsx @@ -14,7 +14,7 @@ import { IngressCharts } from "./ingress-charts"; import { KubeObjectMeta } from "../kube-object/kube-object-meta"; import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry"; import { getBackendServiceNamePort } from "../../api/endpoints/ingress.api"; -import { ResourceType } from "../+cluster-settings/components/cluster-metrics-setting"; +import { ResourceType } from "../cluster-settings/components/cluster-metrics-setting"; import { clusterStore } from "../../../common/cluster-store"; interface Props extends KubeObjectDetailsProps { diff --git a/src/renderer/components/+nodes/node-details.tsx b/src/renderer/components/+nodes/node-details.tsx index a565411e7e..349bf59051 100644 --- a/src/renderer/components/+nodes/node-details.tsx +++ b/src/renderer/components/+nodes/node-details.tsx @@ -17,7 +17,7 @@ import { PodDetailsList } from "../+workloads-pods/pod-details-list"; import { KubeObjectMeta } from "../kube-object/kube-object-meta"; import { KubeEventDetails } from "../+events/kube-event-details"; import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry"; -import { ResourceType } from "../+cluster-settings/components/cluster-metrics-setting"; +import { ResourceType } from "../cluster-settings/components/cluster-metrics-setting"; import { clusterStore } from "../../../common/cluster-store"; interface Props extends KubeObjectDetailsProps { diff --git a/src/renderer/components/+storage-volume-claims/volume-claim-details.tsx b/src/renderer/components/+storage-volume-claims/volume-claim-details.tsx index 2aebba52b3..99642249ab 100644 --- a/src/renderer/components/+storage-volume-claims/volume-claim-details.tsx +++ b/src/renderer/components/+storage-volume-claims/volume-claim-details.tsx @@ -14,7 +14,7 @@ import { VolumeClaimDiskChart } from "./volume-claim-disk-chart"; import { getDetailsUrl, KubeObjectDetailsProps, KubeObjectMeta } from "../kube-object"; import { PersistentVolumeClaim } from "../../api/endpoints"; import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry"; -import { ResourceType } from "../+cluster-settings/components/cluster-metrics-setting"; +import { ResourceType } from "../cluster-settings/components/cluster-metrics-setting"; import { clusterStore } from "../../../common/cluster-store"; interface Props extends KubeObjectDetailsProps { diff --git a/src/renderer/components/+workloads-daemonsets/daemonset-details.tsx b/src/renderer/components/+workloads-daemonsets/daemonset-details.tsx index 9e613c3eff..be56f5f477 100644 --- a/src/renderer/components/+workloads-daemonsets/daemonset-details.tsx +++ b/src/renderer/components/+workloads-daemonsets/daemonset-details.tsx @@ -18,7 +18,7 @@ import { reaction } from "mobx"; import { PodDetailsList } from "../+workloads-pods/pod-details-list"; import { KubeObjectMeta } from "../kube-object/kube-object-meta"; import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry"; -import { ResourceType } from "../+cluster-settings/components/cluster-metrics-setting"; +import { ResourceType } from "../cluster-settings/components/cluster-metrics-setting"; import { clusterStore } from "../../../common/cluster-store"; interface Props extends KubeObjectDetailsProps { diff --git a/src/renderer/components/+workloads-deployments/deployment-details.tsx b/src/renderer/components/+workloads-deployments/deployment-details.tsx index 7cd2350a17..778fcd9bc2 100644 --- a/src/renderer/components/+workloads-deployments/deployment-details.tsx +++ b/src/renderer/components/+workloads-deployments/deployment-details.tsx @@ -19,7 +19,7 @@ import { reaction } from "mobx"; import { PodDetailsList } from "../+workloads-pods/pod-details-list"; import { KubeObjectMeta } from "../kube-object/kube-object-meta"; import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry"; -import { ResourceType } from "../+cluster-settings/components/cluster-metrics-setting"; +import { ResourceType } from "../cluster-settings/components/cluster-metrics-setting"; import { clusterStore } from "../../../common/cluster-store"; interface Props extends KubeObjectDetailsProps { diff --git a/src/renderer/components/+workloads-pods/pod-details-container.tsx b/src/renderer/components/+workloads-pods/pod-details-container.tsx index 99238aa5ad..d761f7fc1e 100644 --- a/src/renderer/components/+workloads-pods/pod-details-container.tsx +++ b/src/renderer/components/+workloads-pods/pod-details-container.tsx @@ -11,7 +11,7 @@ import { PodContainerPort } from "./pod-container-port"; import { ResourceMetrics } from "../resource-metrics"; import { IMetrics } from "../../api/endpoints/metrics.api"; import { ContainerCharts } from "./container-charts"; -import { ResourceType } from "../+cluster-settings/components/cluster-metrics-setting"; +import { ResourceType } from "../cluster-settings/components/cluster-metrics-setting"; import { clusterStore } from "../../../common/cluster-store"; interface Props { diff --git a/src/renderer/components/+workloads-pods/pod-details.tsx b/src/renderer/components/+workloads-pods/pod-details.tsx index 6736e1e326..f643d599a5 100644 --- a/src/renderer/components/+workloads-pods/pod-details.tsx +++ b/src/renderer/components/+workloads-pods/pod-details.tsx @@ -22,7 +22,7 @@ import { getItemMetrics } from "../../api/endpoints/metrics.api"; import { PodCharts, podMetricTabs } from "./pod-charts"; import { KubeObjectMeta } from "../kube-object/kube-object-meta"; import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry"; -import { ResourceType } from "../+cluster-settings/components/cluster-metrics-setting"; +import { ResourceType } from "../cluster-settings/components/cluster-metrics-setting"; import { clusterStore } from "../../../common/cluster-store"; interface Props extends KubeObjectDetailsProps { diff --git a/src/renderer/components/+workloads-replicasets/replicaset-details.tsx b/src/renderer/components/+workloads-replicasets/replicaset-details.tsx index 4510d0add7..b104340612 100644 --- a/src/renderer/components/+workloads-replicasets/replicaset-details.tsx +++ b/src/renderer/components/+workloads-replicasets/replicaset-details.tsx @@ -17,7 +17,7 @@ import { PodCharts, podMetricTabs } from "../+workloads-pods/pod-charts"; import { PodDetailsList } from "../+workloads-pods/pod-details-list"; import { KubeObjectMeta } from "../kube-object/kube-object-meta"; import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry"; -import { ResourceType } from "../+cluster-settings/components/cluster-metrics-setting"; +import { ResourceType } from "../cluster-settings/components/cluster-metrics-setting"; import { clusterStore } from "../../../common/cluster-store"; interface Props extends KubeObjectDetailsProps { diff --git a/src/renderer/components/+workloads-statefulsets/statefulset-details.tsx b/src/renderer/components/+workloads-statefulsets/statefulset-details.tsx index 97e83807f9..f21f9efc5d 100644 --- a/src/renderer/components/+workloads-statefulsets/statefulset-details.tsx +++ b/src/renderer/components/+workloads-statefulsets/statefulset-details.tsx @@ -18,7 +18,7 @@ import { PodCharts, podMetricTabs } from "../+workloads-pods/pod-charts"; import { PodDetailsList } from "../+workloads-pods/pod-details-list"; import { KubeObjectMeta } from "../kube-object/kube-object-meta"; import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry"; -import { ResourceType } from "../+cluster-settings/components/cluster-metrics-setting"; +import { ResourceType } from "../cluster-settings/components/cluster-metrics-setting"; import { clusterStore } from "../../../common/cluster-store"; interface Props extends KubeObjectDetailsProps { diff --git a/src/renderer/components/cluster-manager/cluster-actions.tsx b/src/renderer/components/cluster-manager/cluster-actions.tsx deleted file mode 100644 index 5015abbcac..0000000000 --- a/src/renderer/components/cluster-manager/cluster-actions.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React from "react"; -import uniqueId from "lodash/uniqueId"; -import { clusterSettingsURL } from "../+cluster-settings"; -import { catalogURL } from "../+catalog"; - -import { clusterStore } from "../../../common/cluster-store"; -import { broadcastMessage, requestMain } from "../../../common/ipc"; -import { clusterDisconnectHandler } from "../../../common/cluster-ipc"; -import { ConfirmDialog } from "../confirm-dialog"; -import { Cluster } from "../../../main/cluster"; -import { Tooltip } from "../../components//tooltip"; -import { IpcRendererNavigationEvents } from "../../navigation/events"; - -const navigate = (route: string) => - broadcastMessage(IpcRendererNavigationEvents.NAVIGATE_IN_APP, route); - -/** - * Creates handlers for high-level actions - * that could be performed on an individual cluster - * @param cluster Cluster - */ -export const ClusterActions = (cluster: Cluster) => ({ - showSettings: () => navigate(clusterSettingsURL({ - params: { clusterId: cluster.id } - })), - disconnect: async () => { - clusterStore.deactivate(cluster.id); - navigate(catalogURL()); - await requestMain(clusterDisconnectHandler, cluster.id); - }, - remove: () => { - const tooltipId = uniqueId("tooltip_target_"); - - return ConfirmDialog.open({ - okButtonProps: { - primary: false, - accent: true, - label: "Remove" - }, - ok: () => { - clusterStore.deactivate(cluster.id); - clusterStore.removeById(cluster.id); - navigate(catalogURL()); - }, - message:

- Are you sure want to remove cluster {cluster.name}? - {cluster.id} -

- }); - } -}); diff --git a/src/renderer/components/cluster-manager/cluster-manager.tsx b/src/renderer/components/cluster-manager/cluster-manager.tsx index 6fb6646d32..7b9a7be0ea 100644 --- a/src/renderer/components/cluster-manager/cluster-manager.tsx +++ b/src/renderer/components/cluster-manager/cluster-manager.tsx @@ -9,7 +9,6 @@ import { Catalog, catalogRoute, catalogURL } from "../+catalog"; import { Preferences, preferencesRoute } from "../+preferences"; import { AddCluster, addClusterRoute } from "../+add-cluster"; import { ClusterView } from "./cluster-view"; -import { ClusterSettings, clusterSettingsRoute } from "../+cluster-settings"; import { clusterViewRoute } from "./cluster-view.route"; import { clusterStore } from "../../../common/cluster-store"; import { hasLoadedView, initView, lensViews, refreshViews } from "./lens-views"; @@ -17,6 +16,7 @@ import { globalPageRegistry } from "../../../extensions/registries/page-registry import { Extensions, extensionsRoute } from "../+extensions"; import { getMatchedClusterId } from "../../navigation"; import { HotbarMenu } from "../hotbar/hotbar-menu"; +import { EntitySettings, entitySettingsRoute } from "../+entity-settings"; @observer export class ClusterManager extends React.Component { @@ -58,7 +58,7 @@ export class ClusterManager extends React.Component { - + {globalPageRegistry.getItems().map(({ url, components: { Page } }) => { return ; })} diff --git a/src/renderer/components/cluster-manager/index.tsx b/src/renderer/components/cluster-manager/index.tsx index 74595583aa..692f1676ef 100644 --- a/src/renderer/components/cluster-manager/index.tsx +++ b/src/renderer/components/cluster-manager/index.tsx @@ -1,2 +1 @@ export * from "./cluster-manager"; -export * from "./cluster-actions"; diff --git a/src/renderer/components/+cluster-settings/cluster-settings.command.ts b/src/renderer/components/cluster-settings/cluster-settings.command.ts similarity index 72% rename from src/renderer/components/+cluster-settings/cluster-settings.command.ts rename to src/renderer/components/cluster-settings/cluster-settings.command.ts index c2e762eca8..061d135d67 100644 --- a/src/renderer/components/+cluster-settings/cluster-settings.command.ts +++ b/src/renderer/components/cluster-settings/cluster-settings.command.ts @@ -1,15 +1,15 @@ import { navigate } from "../../navigation"; import { commandRegistry } from "../../../extensions/registries/command-registry"; -import { clusterSettingsURL } from "./cluster-settings.route"; import { clusterStore } from "../../../common/cluster-store"; +import { entitySettingsURL } from "../+entity-settings"; commandRegistry.add({ id: "cluster.viewCurrentClusterSettings", title: "Cluster: View Settings", scope: "global", - action: () => navigate(clusterSettingsURL({ + action: () => navigate(entitySettingsURL({ params: { - clusterId: clusterStore.active.id + entityId: clusterStore.active.id } })), isActive: (context) => !!context.entity diff --git a/src/renderer/components/cluster-settings/cluster-settings.tsx b/src/renderer/components/cluster-settings/cluster-settings.tsx new file mode 100644 index 0000000000..86297e800e --- /dev/null +++ b/src/renderer/components/cluster-settings/cluster-settings.tsx @@ -0,0 +1,141 @@ +import React from "react"; +import { clusterStore } from "../../../common/cluster-store"; +import { ClusterProxySetting } from "./components/cluster-proxy-setting"; +import { ClusterNameSetting } from "./components/cluster-name-setting"; +import { ClusterHomeDirSetting } from "./components/cluster-home-dir-setting"; +import { ClusterAccessibleNamespaces } from "./components/cluster-accessible-namespaces"; +import { ClusterMetricsSetting } from "./components/cluster-metrics-setting"; +import { ShowMetricsSetting } from "./components/show-metrics"; +import { ClusterPrometheusSetting } from "./components/cluster-prometheus-setting"; +import { ClusterKubeconfig } from "./components/cluster-kubeconfig"; +import { entitySettingRegistry } from "../../../extensions/registries"; +import { CatalogEntity } from "../../api/catalog-entity"; + + +function getClusterForEntity(entity: CatalogEntity) { + const cluster = clusterStore.getById(entity.metadata.uid); + + if (!cluster?.enabled) { + return null; + } + + return cluster; +} + +entitySettingRegistry.add([ + { + apiVersions: ["entity.k8slens.dev/v1alpha1"], + kind: "KubernetesCluster", + source: "local", + title: "General", + components: { + View: (props: { entity: CatalogEntity }) => { + const cluster = getClusterForEntity(props.entity); + + if (!cluster) { + return null; + } + + return ( +
+
+ +
+
+ +
+
+ ); + } + } + }, + { + apiVersions: ["entity.k8slens.dev/v1alpha1"], + kind: "KubernetesCluster", + title: "Proxy", + components: { + View: (props: { entity: CatalogEntity }) => { + const cluster = getClusterForEntity(props.entity); + + if (!cluster) { + return null; + } + + return ( +
+ +
+ ); + } + } + }, + { + apiVersions: ["entity.k8slens.dev/v1alpha1"], + kind: "KubernetesCluster", + title: "Terminal", + components: { + View: (props: { entity: CatalogEntity }) => { + const cluster = getClusterForEntity(props.entity); + + if (!cluster) { + return null; + } + + return ( +
+ +
+ ); + } + } + }, + { + apiVersions: ["entity.k8slens.dev/v1alpha1"], + kind: "KubernetesCluster", + title: "Namespaces", + components: { + View: (props: { entity: CatalogEntity }) => { + const cluster = getClusterForEntity(props.entity); + + if (!cluster) { + return null; + } + + return ( +
+ +
+ ); + } + } + }, + { + apiVersions: ["entity.k8slens.dev/v1alpha1"], + kind: "KubernetesCluster", + source: "local", + title: "Metrics", + components: { + View: (props: { entity: CatalogEntity }) => { + const cluster = getClusterForEntity(props.entity); + + if (!cluster) { + return null; + } + + return ( +
+
+ +
+
+ +
+
+ +
+
+ ); + } + } + } +]); diff --git a/src/renderer/components/+cluster-settings/components/cluster-accessible-namespaces.tsx b/src/renderer/components/cluster-settings/components/cluster-accessible-namespaces.tsx similarity index 83% rename from src/renderer/components/+cluster-settings/components/cluster-accessible-namespaces.tsx rename to src/renderer/components/cluster-settings/components/cluster-accessible-namespaces.tsx index d1ad7dbeef..0fb3e7e08f 100644 --- a/src/renderer/components/+cluster-settings/components/cluster-accessible-namespaces.tsx +++ b/src/renderer/components/cluster-settings/components/cluster-accessible-namespaces.tsx @@ -17,7 +17,6 @@ export class ClusterAccessibleNamespaces extends React.Component { return ( <> -

This setting is useful for manually specifying which namespaces you have access to. This is useful when you do not have permissions to list namespaces.

{ @@ -30,6 +29,9 @@ export class ClusterAccessibleNamespaces extends React.Component { this.props.cluster.accessibleNamespaces = Array.from(this.namespaces); }} /> + + This setting is useful for manually specifying which namespaces you have access to. This is useful when you do not have permissions to list namespaces. + ); } diff --git a/src/renderer/components/+cluster-settings/components/cluster-home-dir-setting.tsx b/src/renderer/components/cluster-settings/components/cluster-home-dir-setting.tsx similarity index 96% rename from src/renderer/components/+cluster-settings/components/cluster-home-dir-setting.tsx rename to src/renderer/components/cluster-settings/components/cluster-home-dir-setting.tsx index 10aabf3ff7..e5dfdad130 100644 --- a/src/renderer/components/+cluster-settings/components/cluster-home-dir-setting.tsx +++ b/src/renderer/components/cluster-settings/components/cluster-home-dir-setting.tsx @@ -33,7 +33,6 @@ export class ClusterHomeDirSetting extends React.Component { return ( <> -

Terminal working directory.

{ + + @autobind() + openKubeconfig() { + const { cluster } = this.props; + + shell.showItemInFolder(cluster.kubeConfigPath); + } + + render() { + return ( + <> + + + + {this.props.cluster.kubeConfigPath} + + + + ); + } +} diff --git a/src/renderer/components/+cluster-settings/components/cluster-metrics-setting.scss b/src/renderer/components/cluster-settings/components/cluster-metrics-setting.scss similarity index 100% rename from src/renderer/components/+cluster-settings/components/cluster-metrics-setting.scss rename to src/renderer/components/cluster-settings/components/cluster-metrics-setting.scss diff --git a/src/renderer/components/+cluster-settings/components/cluster-metrics-setting.tsx b/src/renderer/components/cluster-settings/components/cluster-metrics-setting.tsx similarity index 100% rename from src/renderer/components/+cluster-settings/components/cluster-metrics-setting.tsx rename to src/renderer/components/cluster-settings/components/cluster-metrics-setting.tsx diff --git a/src/renderer/components/+cluster-settings/components/cluster-name-setting.tsx b/src/renderer/components/cluster-settings/components/cluster-name-setting.tsx similarity index 96% rename from src/renderer/components/+cluster-settings/components/cluster-name-setting.tsx rename to src/renderer/components/cluster-settings/components/cluster-name-setting.tsx index 9d953ef9ca..46f80597df 100644 --- a/src/renderer/components/+cluster-settings/components/cluster-name-setting.tsx +++ b/src/renderer/components/cluster-settings/components/cluster-name-setting.tsx @@ -34,7 +34,6 @@ export class ClusterNameSetting extends React.Component { return ( <> -

Define cluster name.

{ render() { return ( <> - +

Use pre-installed Prometheus service for metrics. Please refer to the{" "} guide{" "} for possible configuration changes.

-

Prometheus installation method.

{ placeholder="http://
:" validators={this.proxy ? InputValidators.isUrl : undefined} /> + + HTTP Proxy server. Used for communicating with Kubernetes API. + ); } diff --git a/src/renderer/components/+cluster-settings/components/remove-cluster-button.tsx b/src/renderer/components/cluster-settings/components/remove-cluster-button.tsx similarity index 100% rename from src/renderer/components/+cluster-settings/components/remove-cluster-button.tsx rename to src/renderer/components/cluster-settings/components/remove-cluster-button.tsx diff --git a/src/renderer/components/+cluster-settings/components/show-metrics.tsx b/src/renderer/components/cluster-settings/components/show-metrics.tsx similarity index 100% rename from src/renderer/components/+cluster-settings/components/show-metrics.tsx rename to src/renderer/components/cluster-settings/components/show-metrics.tsx diff --git a/src/renderer/components/+cluster-settings/index.ts b/src/renderer/components/cluster-settings/index.ts similarity index 65% rename from src/renderer/components/+cluster-settings/index.ts rename to src/renderer/components/cluster-settings/index.ts index b83e440d53..9bcc72f7d1 100644 --- a/src/renderer/components/+cluster-settings/index.ts +++ b/src/renderer/components/cluster-settings/index.ts @@ -1,3 +1,2 @@ -export * from "./cluster-settings.route"; export * from "./cluster-settings"; export * from "./cluster-settings.command"; diff --git a/src/renderer/components/layout/page-layout.scss b/src/renderer/components/layout/page-layout.scss index 2c79c48f95..2c6bcaf6aa 100644 --- a/src/renderer/components/layout/page-layout.scss +++ b/src/renderer/components/layout/page-layout.scss @@ -43,6 +43,15 @@ width: 218px; padding: 60px 0 60px 20px; + h2 { + margin-bottom: 10px; + font-size: 18px; + padding: 6px 10px; + overflow-wrap: anywhere; + color: var(--textColorAccent); + font-weight: 600; + } + .Tabs { .header { padding: 6px 10px; diff --git a/src/renderer/components/layout/page-layout.tsx b/src/renderer/components/layout/page-layout.tsx index bbec97acdd..8a9a9e6537 100644 --- a/src/renderer/components/layout/page-layout.tsx +++ b/src/renderer/components/layout/page-layout.tsx @@ -8,8 +8,6 @@ import { Icon } from "../icon"; export interface PageLayoutProps extends React.DOMAttributes { className?: IClassName; - header?: React.ReactNode; - headerClass?: IClassName; contentClass?: IClassName; provideBackButtonNavigation?: boolean; contentGaps?: boolean; @@ -57,7 +55,7 @@ export class PageLayout extends React.Component { render() { const { - contentClass, headerClass, provideBackButtonNavigation, + contentClass, provideBackButtonNavigation, contentGaps, showOnTop, navigation, children, ...elemProps } = this.props; const className = cssNames("PageLayout", { showOnTop, showNavigation: navigation }, this.props.className); diff --git a/src/renderer/ipc/index.tsx b/src/renderer/ipc/index.tsx index ccbeef4797..119f13a0dc 100644 --- a/src/renderer/ipc/index.tsx +++ b/src/renderer/ipc/index.tsx @@ -7,7 +7,7 @@ import { isMac } from "../../common/vars"; import { invalidKubeconfigHandler } from "./invalid-kubeconfig-handler"; import { clusterStore } from "../../common/cluster-store"; import { navigate } from "../navigation"; -import { clusterSettingsURL } from "../components/+cluster-settings"; +import { entitySettingsURL } from "../components/+entity-settings"; function sendToBackchannel(backchannel: string, notificationId: string, data: BackchannelArg): void { notificationsStore.remove(notificationId); @@ -79,7 +79,7 @@ function ListNamespacesForbiddenHandler(event: IpcRendererEvent, ...[clusterId]:

Cluster {clusterStore.active.name} does not have permissions to list namespaces. Please add the namespaces you have access to.

diff --git a/src/renderer/protocol-handler/app-handlers.ts b/src/renderer/protocol-handler/app-handlers.ts index b4d2232eb0..c71467fe8a 100644 --- a/src/renderer/protocol-handler/app-handlers.ts +++ b/src/renderer/protocol-handler/app-handlers.ts @@ -1,5 +1,4 @@ import { addClusterURL } from "../components/+add-cluster"; -import { clusterSettingsURL } from "../components/+cluster-settings"; import { extensionsURL } from "../components/+extensions"; import { catalogURL } from "../components/+catalog"; import { preferencesURL } from "../components/+preferences"; @@ -7,6 +6,8 @@ import { clusterViewURL } from "../components/cluster-manager/cluster-view.route import { LensProtocolRouterRenderer } from "./router"; import { navigate } from "../navigation/helpers"; import { clusterStore } from "../../common/cluster-store"; +import { entitySettingsURL } from "../components/+entity-settings"; +import { catalogEntityRegistry } from "../api/catalog-entity-registry"; export function bindProtocolAddRouteHandlers() { LensProtocolRouterRenderer @@ -23,6 +24,19 @@ export function bindProtocolAddRouteHandlers() { .addInternalHandler("/cluster", () => { navigate(addClusterURL()); }) + .addInternalHandler("/entity/:entityId/settings", ({ pathname: { entityId } }) => { + const entity = catalogEntityRegistry.getById(entityId); + + if (entity) { + navigate(entitySettingsURL({ params: { entityId } })); + } else { + console.log("[APP-HANDLER]: catalog entity with given ID does not exist", { entityId }); + } + }) + .addInternalHandler("/extensions", () => { + navigate(extensionsURL()); + }) + // Handlers below are deprecated and only kept for backward compat purposes .addInternalHandler("/cluster/:clusterId", ({ pathname: { clusterId } }) => { const cluster = clusterStore.getById(clusterId); @@ -36,12 +50,9 @@ export function bindProtocolAddRouteHandlers() { const cluster = clusterStore.getById(clusterId); if (cluster) { - navigate(clusterSettingsURL({ params: { clusterId } })); + navigate(entitySettingsURL({ params: { entityId: clusterId } })); } else { console.log("[APP-HANDLER]: cluster with given ID does not exist", { clusterId }); } - }) - .addInternalHandler("/extensions", () => { - navigate(extensionsURL()); }); }