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

Replace KubeObjectDetailRegistry with injectable version

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2022-01-05 15:38:05 -05:00
parent a5e89b79d6
commit 6e0fdc2e57
19 changed files with 664 additions and 592 deletions

View File

@ -32,6 +32,12 @@ module.exports = {
react: {
version: packageJson.devDependencies.react || "detect",
},
// the package eslint-import-resolver-typescript is required for this line which fixes errors when using .d.ts files
"import/resolver": {
"typescript": {
"alwaysTryTypes": true,
},
},
},
overrides: [
{

View File

@ -19,4 +19,18 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
export { KubeObjectDetailRegistry } from "../../extensions/registries/kube-object-detail-registry";
module.exports = {
languages: {
register: jest.fn(),
setMonarchTokensProvider: jest.fn(),
registerCompletionItemProvider: jest.fn(),
},
editor: {
defineTheme: jest.fn(),
getModel: jest.fn(),
createModel: jest.fn(),
},
Uri: {
file: jest.fn(),
},
};

View File

@ -62,7 +62,8 @@
},
"moduleNameMapper": {
"\\.(css|scss)$": "<rootDir>/__mocks__/styleMock.ts",
"\\.(svg)$": "<rootDir>/__mocks__/imageMock.ts"
"\\.(svg)$": "<rootDir>/__mocks__/imageMock.ts",
"^monaco-editor": "<rootDir>/node_modules/monaco-editor"
},
"modulePathIgnorePatterns": [
"<rootDir>/dist",
@ -340,6 +341,7 @@
"esbuild": "^0.13.15",
"esbuild-loader": "^2.16.0",
"eslint": "^7.32.0",
"eslint-import-resolver-typescript": "^2.5.0",
"eslint-plugin-header": "^3.1.1",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-react": "^7.27.1",

View File

@ -20,7 +20,7 @@
*/
export type { AppPreferenceRegistration, AppPreferenceComponents } from "../registries/app-preference-registry";
export type { KubeObjectDetailRegistration, KubeObjectDetailComponents } from "../registries/kube-object-detail-registry";
export type { KubeObjectDetailRegistration, KubeObjectDetailComponents } from "../../renderer/components/kube-object-details/kube-details-items/kube-detail-items";
export type { KubeObjectMenuRegistration, KubeObjectMenuComponents } from "../registries/kube-object-menu-registry";
export type { KubeObjectStatusRegistration } from "../registries/kube-object-status-registry";
export type { PageRegistration, RegisteredPage, PageParams, PageComponentProps, PageComponents, PageTarget } from "../registries/page-registry";

View File

@ -289,7 +289,6 @@ export class ExtensionLoader {
registries.ClusterPageRegistry.getInstance().add(extension.clusterPages, extension),
registries.ClusterPageMenuRegistry.getInstance().add(extension.clusterPageMenus, extension),
registries.KubeObjectMenuRegistry.getInstance().add(extension.kubeObjectMenuItems),
registries.KubeObjectDetailRegistry.getInstance().add(extension.kubeObjectDetailItems),
registries.KubeObjectStatusRegistry.getInstance().add(extension.kubeObjectStatusTexts),
registries.WorkloadsOverviewDetailRegistry.getInstance().add(extension.kubeWorkloadsOverviewItems),
];

View File

@ -31,6 +31,7 @@ import type { KubernetesCluster } from "../common/catalog-entities";
import type { WelcomeMenuRegistration } from "../renderer/components/+welcome/welcome-menu-items/welcome-menu-registration";
import type { WelcomeBannerRegistration } from "../renderer/components/+welcome/welcome-banner-items/welcome-banner-registration";
import type { CommandRegistration } from "../renderer/components/command-palette/registered-commands/commands";
import type { KubeObjectDetailRegistration } from "../renderer/components/kube-object-details/kube-details-items/kube-detail-items";
export class LensRendererExtension extends LensExtension {
globalPages: registries.PageRegistration[] = [];
@ -40,7 +41,7 @@ export class LensRendererExtension extends LensExtension {
appPreferences: registries.AppPreferenceRegistration[] = [];
entitySettings: registries.EntitySettingRegistration[] = [];
statusBarItems: registries.StatusBarRegistration[] = [];
kubeObjectDetailItems: registries.KubeObjectDetailRegistration[] = [];
kubeObjectDetailItems: KubeObjectDetailRegistration[] = [];
kubeObjectMenuItems: registries.KubeObjectMenuRegistration[] = [];
kubeWorkloadsOverviewItems: registries.WorkloadsOverviewDetailRegistration[] = [];
commands: CommandRegistration[] = [];

View File

@ -25,7 +25,6 @@ export * from "./page-registry";
export * from "./page-menu-registry";
export * from "./app-preference-registry";
export * from "./status-bar-registry";
export * from "./kube-object-detail-registry";
export * from "./kube-object-menu-registry";
export * from "./kube-object-status-registry";
export * from "./entity-setting-registry";

View File

@ -109,9 +109,6 @@ export async function bootstrap(comp: () => Promise<AppComponent>, di: Dependenc
logger.info(`${logPrefix} initializing KubeObjectMenuRegistry`);
initializers.initKubeObjectMenuRegistry();
logger.info(`${logPrefix} initializing KubeObjectDetailRegistry`);
initializers.initKubeObjectDetailRegistry();
logger.info(`${logPrefix} initializing WorkloadsOverviewDetailRegistry`);
initializers.initWorkloadsOverviewDetailRegistry();

View File

@ -88,10 +88,8 @@ export class CRDDetails extends React.Component<Props> {
</DrawerItem>
<DrawerItem name="Conditions" className="conditions" labelsOnly>
{
crd.getConditions().map(condition => {
const { type, message, lastTransitionTime, status } = condition;
return (
crd.getConditions()
.map(({ type, message, lastTransitionTime, status }) => (
<Badge
key={type}
label={type}
@ -104,8 +102,7 @@ export class CRDDetails extends React.Component<Props> {
</>
)}
/>
);
})
))
}
</DrawerItem>
<DrawerTitle title="Names"/>
@ -133,19 +130,15 @@ export class CRDDetails extends React.Component<Props> {
<TableCell className="json-path">JSON Path</TableCell>
</TableHead>
{
printerColumns.map((column, index) => {
const { name, type, jsonPath } = column;
return (
<TableRow key={index}>
<TableCell className="name">{name}</TableCell>
<TableCell className="type">{type}</TableCell>
<TableCell className="json-path">
<Badge label={jsonPath}/>
</TableCell>
</TableRow>
);
})
printerColumns.map(({ name, type, jsonPath }, index) => (
<TableRow key={index}>
<TableCell className="name">{name}</TableCell>
<TableCell className="type">{type}</TableCell>
<TableCell className="json-path">
<Badge label={jsonPath}/>
</TableCell>
</TableRow>
))
}
</Table>
</>

View File

@ -0,0 +1,446 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import React from "react";
import type { KubeObjectDetailsProps } from "..";
import { type HpaDetailsProps, HpaDetails } from "../../+config-autoscalers";
import { LimitRangeDetails } from "../../+config-limit-ranges";
import { ConfigMapDetails } from "../../+config-maps";
import { PodDisruptionBudgetDetails } from "../../+config-pod-disruption-budgets";
import { ResourceQuotaDetails } from "../../+config-resource-quotas";
import { SecretDetails } from "../../+config-secrets";
import { CRDDetails } from "../../+custom-resources";
import { EventDetails } from "../../+events";
import { KubeEventDetails } from "../../+events/kube-event-details";
import { NamespaceDetails } from "../../+namespaces";
import { EndpointDetails } from "../../+network-endpoints";
import { IngressDetails } from "../../+network-ingresses";
import { NetworkPolicyDetails } from "../../+network-policies";
import { ServiceDetails } from "../../+network-services";
import { NodeDetails } from "../../+nodes";
import { PodSecurityPolicyDetails } from "../../+pod-security-policies";
import { StorageClassDetails } from "../../+storage-classes";
import { PersistentVolumeClaimDetails } from "../../+storage-volume-claims";
import { PersistentVolumeDetails } from "../../+storage-volumes";
import { ClusterRoleBindingDetails } from "../../+user-management/+cluster-role-bindings";
import { ClusterRoleDetails } from "../../+user-management/+cluster-roles";
import { RoleBindingDetails } from "../../+user-management/+role-bindings";
import { RoleDetails } from "../../+user-management/+roles";
import { ServiceAccountsDetails } from "../../+user-management/+service-accounts";
import { CronJobDetails } from "../../+workloads-cronjobs";
import { DaemonSetDetails } from "../../+workloads-daemonsets";
import { DeploymentDetails } from "../../+workloads-deployments";
import { JobDetails } from "../../+workloads-jobs";
import { PodDetails } from "../../+workloads-pods";
import { ReplicaSetDetails } from "../../+workloads-replicasets";
import { StatefulSetDetails } from "../../+workloads-statefulsets";
import type { KubeObjectDetailRegistration } from "./kube-detail-items";
export const internalItems: Required<KubeObjectDetailRegistration>[] = [
{
kind: "HorizontalPodAutoscaler",
apiVersions: ["autoscaling/v2beta1"],
components: {
// Note: this line is left in the long form as a validation that this usecase is valid
Details: (props: HpaDetailsProps) => <HpaDetails {...props}/>,
},
},
{
kind: "HorizontalPodAutoscaler",
apiVersions: ["autoscaling/v2beta1"],
priority: 5,
components: {
// Note: this line is left in the long form as a validation that this usecase is valid
Details: (props: KubeObjectDetailsProps) => <KubeEventDetails {...props}/>,
},
},
{
kind: "LimitRange",
apiVersions: ["v1"],
components: {
Details: LimitRangeDetails,
},
},
{
kind: "ConfigMap",
apiVersions: ["v1"],
components: {
Details: ConfigMapDetails,
},
},
{
kind: "ConfigMap",
apiVersions: ["v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "PodDisruptionBudget",
apiVersions: ["policy/v1beta1"],
components: {
Details: PodDisruptionBudgetDetails,
},
},
{
kind: "ResourceQuota",
apiVersions: ["v1"],
components: {
Details: ResourceQuotaDetails,
},
},
{
kind: "Secret",
apiVersions: ["v1"],
components: {
Details: SecretDetails,
},
},
{
kind: "CustomResourceDefinition",
apiVersions: ["apiextensions.k8s.io/v1", "apiextensions.k8s.io/v1beta1"],
components: {
Details: CRDDetails,
},
},
{
kind: "Event",
apiVersions: ["v1"],
components: {
Details: EventDetails,
},
},
{
kind: "Namespace",
apiVersions: ["v1"],
components: {
Details: NamespaceDetails,
},
},
{
kind: "Endpoints",
apiVersions: ["v1"],
components: {
Details: EndpointDetails,
},
},
{
kind: "Endpoints",
apiVersions: ["v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "Ingress",
apiVersions: ["networking.k8s.io/v1", "extensions/v1beta1"],
components: {
Details: IngressDetails,
},
},
{
kind: "Ingress",
apiVersions: ["networking.k8s.io/v1", "extensions/v1beta1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "NetworkPolicy",
apiVersions: ["networking.k8s.io/v1"],
components: {
Details: NetworkPolicyDetails,
},
},
{
kind: "NetworkPolicy",
apiVersions: ["networking.k8s.io/v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "Service",
apiVersions: ["v1"],
components: {
Details: ServiceDetails,
},
},
{
kind: "Service",
apiVersions: ["v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "Node",
apiVersions: ["v1"],
components: {
Details: NodeDetails,
},
},
{
kind: "Node",
apiVersions: ["v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "PodSecurityPolicy",
apiVersions: ["policy/v1beta1"],
components: {
Details: PodSecurityPolicyDetails,
},
},
{
kind: "StorageClass",
apiVersions: ["storage.k8s.io/v1"],
components: {
Details: StorageClassDetails,
},
},
{
kind: "StorageClass",
apiVersions: ["storage.k8s.io/v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "PersistentVolumeClaim",
apiVersions: ["v1"],
components: {
Details: PersistentVolumeClaimDetails,
},
},
{
kind: "PersistentVolumeClaim",
apiVersions: ["v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "PersistentVolume",
apiVersions: ["v1"],
components: {
Details: PersistentVolumeDetails,
},
},
{
kind: "PersistentVolume",
apiVersions: ["v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "Role",
apiVersions: ["rbac.authorization.k8s.io/v1"],
components: {
Details: RoleDetails,
},
},
{
kind: "Role",
apiVersions: ["rbac.authorization.k8s.io/v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "ClusterRole",
apiVersions: ["rbac.authorization.k8s.io/v1"],
components: {
Details: ClusterRoleDetails,
},
},
{
kind: "ClusterRole",
apiVersions: ["rbac.authorization.k8s.io/v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "RoleBinding",
apiVersions: ["rbac.authorization.k8s.io/v1"],
components: {
Details: RoleBindingDetails,
},
},
{
kind: "RoleBinding",
apiVersions: ["rbac.authorization.k8s.io/v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "ClusterRoleBinding",
apiVersions: ["rbac.authorization.k8s.io/v1"],
components: {
Details: ClusterRoleBindingDetails,
},
},
{
kind: "ClusterRoleBinding",
apiVersions: ["rbac.authorization.k8s.io/v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "ServiceAccount",
apiVersions: ["v1"],
components: {
Details: ServiceAccountsDetails,
},
},
{
kind: "ServiceAccount",
apiVersions: ["v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "CronJob",
apiVersions: ["batch/v1beta1"],
components: {
Details: CronJobDetails,
},
},
{
kind: "CronJob",
apiVersions: ["batch/v1beta1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "DaemonSet",
apiVersions: ["apps/v1"],
components: {
Details: DaemonSetDetails,
},
},
{
kind: "DaemonSet",
apiVersions: ["apps/v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "Deployment",
apiVersions: ["apps/v1"],
components: {
Details: DeploymentDetails,
},
},
{
kind: "Deployment",
apiVersions: ["apps/v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "Job",
apiVersions: ["batch/v1"],
components: {
Details: JobDetails,
},
},
{
kind: "Job",
apiVersions: ["batch/v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "Pod",
apiVersions: ["v1"],
components: {
Details: PodDetails,
},
},
{
kind: "Pod",
apiVersions: ["v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "ReplicaSet",
apiVersions: ["apps/v1"],
components: {
Details: ReplicaSetDetails,
},
},
{
kind: "ReplicaSet",
apiVersions: ["apps/v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "StatefulSet",
apiVersions: ["apps/v1"],
components: {
Details: StatefulSetDetails,
},
},
{
kind: "StatefulSet",
apiVersions: ["apps/v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
].map(({ priority = 50, ...item }) => ({ priority, ...item }));

View File

@ -19,28 +19,19 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import type React from "react";
import type { KubeObjectDetailsProps } from "../renderer-api/components";
import type { KubeObject } from "../renderer-api/k8s-api";
import { BaseRegistry } from "./base-registry";
/**
* The components for a details item
*/
export interface KubeObjectDetailComponents<T extends KubeObject = KubeObject> {
Details: React.ComponentType<KubeObjectDetailsProps<T>>;
}
/**
* The registration type for extensions
*/
export interface KubeObjectDetailRegistration {
kind: string;
apiVersions: string[];
components: KubeObjectDetailComponents<KubeObject>;
priority?: number;
}
export class KubeObjectDetailRegistry extends BaseRegistry<KubeObjectDetailRegistration> {
getItemsForKind(kind: string, apiVersion: string) {
const items = this.getItems().filter((item) => {
return item.kind === kind && item.apiVersions.includes(apiVersion);
});
return items.sort((a, b) => (b.priority ?? 50) - (a.priority ?? 50));
}
}

View File

@ -0,0 +1,61 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import { orderBy } from "lodash";
import { computed } from "mobx";
import rendererExtensionsInjectable from "../../../../extensions/renderer-extensions.injectable";
import { internalItems } from "./internal-items";
import type { KubeObjectDetailComponents } from "./kube-detail-items";
type Kind = string;
type ApiVersion = string;
const kubeDetailItemsInjectable = getInjectable({
instantiate: di => computed(() => {
const res = new Map<Kind, Map<ApiVersion, KubeObjectDetailComponents[]>>();
const extensionItems = di.inject(rendererExtensionsInjectable).get()
.flatMap(ext => ext.kubeObjectDetailItems)
.map(({ priority = 50, ...item }) => ({ priority, ...item }));
const items = orderBy([...internalItems, ...extensionItems], "priority", "desc");
for (const item of items) {
if (!res.has(item.kind)) {
res.set(item.kind, new Map());
}
const byVersions = res.get(item.kind);
for (const apiVersion of item.apiVersions) {
if (!byVersions.has(apiVersion)) {
byVersions.set(apiVersion, []);
}
byVersions.get(apiVersion).push(item.components);
}
}
return res;
}),
lifecycle: lifecycleEnum.singleton,
});
export default kubeDetailItemsInjectable;

View File

@ -21,19 +21,21 @@
import "./kube-object-details.scss";
import React from "react";
import { disposeOnUnmount, observer } from "mobx-react";
import { computed, observable, reaction, makeObservable } from "mobx";
import React, { ReactNode, useEffect, useState } from "react";
import { observer } from "mobx-react";
import { reaction, IComputedValue } from "mobx";
import { Drawer } from "../drawer";
import type { KubeObject } from "../../../common/k8s-api/kube-object";
import { Spinner } from "../spinner";
import { apiManager } from "../../../common/k8s-api/api-manager";
import { crdStore } from "../+custom-resources/crd.store";
import { KubeObjectMenu } from "../kube-object-menu";
import { KubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
import { CrdResourceDetails } from "../+custom-resources";
import { KubeObjectMeta } from "../kube-object-meta";
import { hideDetails, kubeDetailsUrlParam } from "../kube-detail-params";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { KubeObjectDetailComponents } from "./kube-details-items/kube-detail-items";
import kubeDetailItemsInjectable from "./kube-details-items/kube-details.injectable";
export interface KubeObjectDetailsProps<T extends KubeObject = KubeObject> {
@ -41,115 +43,116 @@ export interface KubeObjectDetailsProps<T extends KubeObject = KubeObject> {
object: T;
}
@observer
export class KubeObjectDetails extends React.Component {
@observable isLoading = false;
@observable.ref loadingError: React.ReactNode;
interface Dependencies {
kubeDetailItems: IComputedValue<Map<string, Map<string, KubeObjectDetailComponents<KubeObject>[]>>>;
}
constructor(props: {}) {
super(props);
makeObservable(this);
function getKubeObjectByPath(path: string): KubeObject | undefined {
try {
return apiManager
.getStore(path)
?.getByPath(path);
} catch (error) {
console.error(`[KUBE-OBJECT-DETAILS]: failed to get store or object: ${error}`, { path });
return undefined;
}
}
@computed get path() {
return kubeDetailsUrlParam.get();
}
const NonInjectedKubeObjectDetails = observer(({ kubeDetailItems }: Dependencies) => {
const [loading, setLoading] = useState(false);
const [loadingError, setLoadingError] = useState<ReactNode>("");
@computed get object() {
try {
return apiManager
.getStore(this.path)
?.getByPath(this.path);
} catch (error) {
console.error(`[KUBE-OBJECT-DETAILS]: failed to get store or object: ${error}`, { path: this.path });
useEffect(() => reaction(
() => [
kubeDetailsUrlParam.get(),
getKubeObjectByPath(kubeDetailsUrlParam.get()), // resource might be updated via watch-event or from already opened details
crdStore.items.length, // crd stores initialized after loading
] as const, async ([path, kubeObject]) => {
setLoadingError("");
return undefined;
}
}
if (!kubeObject) {
const store = apiManager.getStore(path);
@disposeOnUnmount
loader = reaction(() => [
this.path,
this.object, // resource might be updated via watch-event or from already opened details
crdStore.items.length, // crd stores initialized after loading
], async () => {
this.loadingError = "";
const { path, object } = this;
if (store) {
setLoading(true);
if (!object) {
const store = apiManager.getStore(path);
if (store) {
this.isLoading = true;
try {
await store.loadFromPath(path);
} catch (err) {
this.loadingError = <>Resource loading has failed: <b>{err.toString()}</b></>;
} finally {
this.isLoading = false;
try {
await store.loadFromPath(path);
} catch (err) {
setLoadingError(<>Resource loading has failed: <b>{err.toString()}</b></>);
} finally {
setLoading(false);
}
}
}
}
});
},
), []);
render() {
const { object, isLoading, loadingError } = this;
const isOpen = !!(object || isLoading || loadingError);
const detailsPath = kubeDetailsUrlParam.get();
const kubeObject = getKubeObjectByPath(detailsPath);
if (!object) {
return (
<Drawer
className="KubeObjectDetails flex column"
open={isOpen}
title=""
toolbar={<KubeObjectMenu object={object} toolbar={true} />}
onClose={hideDetails}
>
{isLoading && <Spinner center />}
{loadingError && <div className="box center">{loadingError}</div>}
</Drawer>
);
}
const { kind, getName } = object;
const title = `${kind}: ${getName()}`;
const details = KubeObjectDetailRegistry
.getInstance()
.getItemsForKind(object.kind, object.apiVersion)
.map((item, index) => (
<item.components.Details object={object} key={`object-details-${index}`} />
));
if (details.length === 0) {
const crd = crdStore.getByObject(object);
/**
* This is a fallback so that if a custom resource object doesn't have
* any defined details we should try and display at least some details
*/
if (crd) {
details.push(<CrdResourceDetails key={object.getId()} object={object} crd={crd} />);
}
}
if (details.length === 0) {
// if we still don't have any details to show, just show the standard object metadata
details.push(<KubeObjectMeta key={object.getId()} object={object} />);
}
const isOpen = !!(kubeObject || loading || loadingError);
if (!kubeObject) {
return (
<Drawer
className="KubeObjectDetails flex column"
open={isOpen}
title={title}
toolbar={<KubeObjectMenu object={object} toolbar={true}/>}
title=""
toolbar={<KubeObjectMenu object={kubeObject} toolbar={true} />}
onClose={hideDetails}
>
{isLoading && <Spinner center/>}
{loading && <Spinner center />}
{loadingError && <div className="box center">{loadingError}</div>}
{details}
</Drawer>
);
}
}
const { kind, getName } = kubeObject;
const title = `${kind}: ${getName()}`;
const details = kubeDetailItems.get()
.get(kubeObject.kind)
.get(kubeObject.apiVersion)
.map(({ Details }, index) => (
<Details object={kubeObject} key={`object-details-${index}`} />
));
if (details.length === 0) {
const crd = crdStore.getByObject(kubeObject);
/**
* This is a fallback so that if a custom resource object doesn't have
* any defined details we should try and display at least some details
*/
if (crd) {
details.push(<CrdResourceDetails key={kubeObject.getId()} object={kubeObject} crd={crd} />);
}
}
if (details.length === 0) {
// if we still don't have any details to show, just show the standard object metadata
details.push(<KubeObjectMeta key={kubeObject.getId()} object={kubeObject} />);
}
return (
<Drawer
className="KubeObjectDetails flex column"
open={isOpen}
title={title}
toolbar={<KubeObjectMenu object={kubeObject} toolbar={true}/>}
onClose={hideDetails}
>
{loading && <Spinner center/>}
{loadingError && <div className="box center">{loadingError}</div>}
{details}
</Drawer>
);
});
export const KubeObjectDetails = withInjectables<Dependencies>(NonInjectedKubeObjectDetails, {
getProps: (di, props) => ({
kubeDetailItems: di.inject(kubeDetailItemsInjectable),
...props,
}),
});

View File

@ -28,6 +28,7 @@ import { broadcastMessage } from "../../../../common/ipc";
import * as vars from "../../../../common/vars";
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
import { DiRender, renderFor } from "../../test-utils/renderFor";
import type { ConfigurableDependencyInjectionContainer } from "@ogre-tools/injectable";
const mockConfig = vars as { isWindows: boolean; isLinux: boolean };
@ -65,10 +66,10 @@ jest.mock("@electron/remote", () => {
describe("<TopBar/> in Windows and Linux", () => {
let render: DiRender;
let di: ConfigurableDependencyInjectionContainer;
beforeEach(() => {
const di = getDiForUnitTesting();
di = getDiForUnitTesting();
render = renderFor(di);
});

View File

@ -90,7 +90,6 @@ describe("<TopBar/>", () => {
beforeEach(() => {
di = getDiForUnitTesting();
render = renderFor(di);
});

View File

@ -23,7 +23,6 @@ export * from "./catalog-entity-detail-registry";
export * from "./catalog";
export * from "./entity-settings-registry";
export * from "./ipc";
export * from "./kube-object-detail-registry";
export * from "./kube-object-menu-registry";
export * from "./registries";
export * from "./workloads-overview-detail-registry";

View File

@ -1,449 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import React from "react";
import { KubeObjectDetailRegistry } from "../api/kube-object-detail-registry";
import { HpaDetails, HpaDetailsProps } from "../components/+config-autoscalers";
import { LimitRangeDetails } from "../components/+config-limit-ranges";
import { ConfigMapDetails } from "../components/+config-maps";
import { PodDisruptionBudgetDetails } from "../components/+config-pod-disruption-budgets";
import { ResourceQuotaDetails } from "../components/+config-resource-quotas";
import { SecretDetails } from "../components/+config-secrets";
import { CRDDetails } from "../components/+custom-resources";
import { EventDetails } from "../components/+events";
import { KubeEventDetails } from "../components/+events/kube-event-details";
import { NamespaceDetails } from "../components/+namespaces";
import { EndpointDetails } from "../components/+network-endpoints";
import { IngressDetails } from "../components/+network-ingresses";
import { NetworkPolicyDetails } from "../components/+network-policies";
import { ServiceDetails } from "../components/+network-services";
import { NodeDetails } from "../components/+nodes";
import { PodSecurityPolicyDetails } from "../components/+pod-security-policies";
import { StorageClassDetails } from "../components/+storage-classes";
import { PersistentVolumeClaimDetails } from "../components/+storage-volume-claims";
import { PersistentVolumeDetails } from "../components/+storage-volumes";
import { ClusterRoleDetails } from "../components/+user-management/+cluster-roles";
import { ClusterRoleBindingDetails } from "../components/+user-management/+cluster-role-bindings";
import { RoleDetails } from "../components/+user-management/+roles";
import { RoleBindingDetails } from "../components/+user-management/+role-bindings";
import { ServiceAccountsDetails } from "../components/+user-management/+service-accounts";
import { CronJobDetails } from "../components/+workloads-cronjobs";
import { DaemonSetDetails } from "../components/+workloads-daemonsets";
import { DeploymentDetails } from "../components/+workloads-deployments";
import { JobDetails } from "../components/+workloads-jobs";
import { PodDetails } from "../components/+workloads-pods";
import { ReplicaSetDetails } from "../components/+workloads-replicasets";
import { StatefulSetDetails } from "../components/+workloads-statefulsets";
import type { KubeObjectDetailsProps } from "../components/kube-object-details";
export function initKubeObjectDetailRegistry() {
KubeObjectDetailRegistry.getInstance()
.add([
{
kind: "HorizontalPodAutoscaler",
apiVersions: ["autoscaling/v2beta1"],
components: {
// Note: this line is left in the long form as a validation that this usecase is valid
Details: (props: HpaDetailsProps) => <HpaDetails {...props}/>,
},
},
{
kind: "HorizontalPodAutoscaler",
apiVersions: ["autoscaling/v2beta1"],
priority: 5,
components: {
// Note: this line is left in the long form as a validation that this usecase is valid
Details: (props: KubeObjectDetailsProps) => <KubeEventDetails {...props}/>,
},
},
{
kind: "LimitRange",
apiVersions: ["v1"],
components: {
Details: LimitRangeDetails,
},
},
{
kind: "ConfigMap",
apiVersions: ["v1"],
components: {
Details: ConfigMapDetails,
},
},
{
kind: "ConfigMap",
apiVersions: ["v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "PodDisruptionBudget",
apiVersions: ["policy/v1beta1"],
components: {
Details: PodDisruptionBudgetDetails,
},
},
{
kind: "ResourceQuota",
apiVersions: ["v1"],
components: {
Details: ResourceQuotaDetails,
},
},
{
kind: "Secret",
apiVersions: ["v1"],
components: {
Details: SecretDetails,
},
},
{
kind: "CustomResourceDefinition",
apiVersions: ["apiextensions.k8s.io/v1", "apiextensions.k8s.io/v1beta1"],
components: {
Details: CRDDetails,
},
},
{
kind: "Event",
apiVersions: ["v1"],
components: {
Details: EventDetails,
},
},
{
kind: "Namespace",
apiVersions: ["v1"],
components: {
Details: NamespaceDetails,
},
},
{
kind: "Endpoints",
apiVersions: ["v1"],
components: {
Details: EndpointDetails,
},
},
{
kind: "Endpoints",
apiVersions: ["v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "Ingress",
apiVersions: ["networking.k8s.io/v1", "extensions/v1beta1"],
components: {
Details: IngressDetails,
},
},
{
kind: "Ingress",
apiVersions: ["networking.k8s.io/v1", "extensions/v1beta1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "NetworkPolicy",
apiVersions: ["networking.k8s.io/v1"],
components: {
Details: NetworkPolicyDetails,
},
},
{
kind: "NetworkPolicy",
apiVersions: ["networking.k8s.io/v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "Service",
apiVersions: ["v1"],
components: {
Details: ServiceDetails,
},
},
{
kind: "Service",
apiVersions: ["v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "Node",
apiVersions: ["v1"],
components: {
Details: NodeDetails,
},
},
{
kind: "Node",
apiVersions: ["v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "PodSecurityPolicy",
apiVersions: ["policy/v1beta1"],
components: {
Details: PodSecurityPolicyDetails,
},
},
{
kind: "StorageClass",
apiVersions: ["storage.k8s.io/v1"],
components: {
Details: StorageClassDetails,
},
},
{
kind: "StorageClass",
apiVersions: ["storage.k8s.io/v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "PersistentVolumeClaim",
apiVersions: ["v1"],
components: {
Details: PersistentVolumeClaimDetails,
},
},
{
kind: "PersistentVolumeClaim",
apiVersions: ["v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "PersistentVolume",
apiVersions: ["v1"],
components: {
Details: PersistentVolumeDetails,
},
},
{
kind: "PersistentVolume",
apiVersions: ["v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "Role",
apiVersions: ["rbac.authorization.k8s.io/v1"],
components: {
Details: RoleDetails,
},
},
{
kind: "Role",
apiVersions: ["rbac.authorization.k8s.io/v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "ClusterRole",
apiVersions: ["rbac.authorization.k8s.io/v1"],
components: {
Details: ClusterRoleDetails,
},
},
{
kind: "ClusterRole",
apiVersions: ["rbac.authorization.k8s.io/v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "RoleBinding",
apiVersions: ["rbac.authorization.k8s.io/v1"],
components: {
Details: RoleBindingDetails,
},
},
{
kind: "RoleBinding",
apiVersions: ["rbac.authorization.k8s.io/v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "ClusterRoleBinding",
apiVersions: ["rbac.authorization.k8s.io/v1"],
components: {
Details: ClusterRoleBindingDetails,
},
},
{
kind: "ClusterRoleBinding",
apiVersions: ["rbac.authorization.k8s.io/v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "ServiceAccount",
apiVersions: ["v1"],
components: {
Details: ServiceAccountsDetails,
},
},
{
kind: "ServiceAccount",
apiVersions: ["v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "CronJob",
apiVersions: ["batch/v1beta1"],
components: {
Details: CronJobDetails,
},
},
{
kind: "CronJob",
apiVersions: ["batch/v1beta1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "DaemonSet",
apiVersions: ["apps/v1"],
components: {
Details: DaemonSetDetails,
},
},
{
kind: "DaemonSet",
apiVersions: ["apps/v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "Deployment",
apiVersions: ["apps/v1"],
components: {
Details: DeploymentDetails,
},
},
{
kind: "Deployment",
apiVersions: ["apps/v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "Job",
apiVersions: ["batch/v1"],
components: {
Details: JobDetails,
},
},
{
kind: "Job",
apiVersions: ["batch/v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "Pod",
apiVersions: ["v1"],
components: {
Details: PodDetails,
},
},
{
kind: "Pod",
apiVersions: ["v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "ReplicaSet",
apiVersions: ["apps/v1"],
components: {
Details: ReplicaSetDetails,
},
},
{
kind: "ReplicaSet",
apiVersions: ["apps/v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
{
kind: "StatefulSet",
apiVersions: ["apps/v1"],
components: {
Details: StatefulSetDetails,
},
},
{
kind: "StatefulSet",
apiVersions: ["apps/v1"],
priority: 5,
components: {
Details: KubeEventDetails,
},
},
]);
}

View File

@ -28,7 +28,6 @@ export function initRegistries() {
registries.ClusterPageRegistry.createInstance();
registries.EntitySettingRegistry.createInstance();
registries.GlobalPageRegistry.createInstance();
registries.KubeObjectDetailRegistry.createInstance();
registries.KubeObjectMenuRegistry.createInstance();
registries.KubeObjectStatusRegistry.createInstance();
registries.StatusBarRegistry.createInstance();

View File

@ -5467,6 +5467,17 @@ eslint-import-resolver-node@^0.3.6:
debug "^3.2.7"
resolve "^1.20.0"
eslint-import-resolver-typescript@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.5.0.tgz#07661966b272d14ba97f597b51e1a588f9722f0a"
integrity sha512-qZ6e5CFr+I7K4VVhQu3M/9xGv9/YmwsEXrsm3nimw8vWaVHRDrQRp26BgCypTxBp3vUp4o5aVEJRiy0F2DFddQ==
dependencies:
debug "^4.3.1"
glob "^7.1.7"
is-glob "^4.0.1"
resolve "^1.20.0"
tsconfig-paths "^3.9.0"
eslint-module-utils@^2.7.1:
version "2.7.1"
resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.1.tgz#b435001c9f8dd4ab7f6d0efcae4b9696d4c24b7c"
@ -6525,7 +6536,7 @@ glob-to-regexp@^0.4.1:
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0:
glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.1.7, glob@^7.2.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==