mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
List layout column injection token package (#7544)
Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> Signed-off-by: Sami Tiilikainen <97873007+samitiilikainen@users.noreply.github.com> Co-authored-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
parent
4bb37df1cd
commit
5eb41e8a6c
19
package-lock.json
generated
19
package-lock.json
generated
@ -3935,6 +3935,10 @@
|
||||
"resolved": "packages/technical-features/application/legacy-extensions",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@k8slens/list-layout": {
|
||||
"resolved": "packages/list-layout",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@k8slens/messaging": {
|
||||
"resolved": "packages/technical-features/messaging/agnostic",
|
||||
"link": true
|
||||
@ -35719,6 +35723,7 @@
|
||||
"@astronautlabs/jsonpath": "^1.1.0",
|
||||
"@hapi/call": "^9.0.1",
|
||||
"@hapi/subtext": "^7.1.0",
|
||||
"@k8slens/list-layout": "^1.0.0-alpha.0",
|
||||
"@k8slens/metrics": "^6.5.0-alpha.3",
|
||||
"@k8slens/node-fetch": "^6.5.0-alpha.3",
|
||||
"@k8slens/react-application": "^1.0.0-alpha.2",
|
||||
@ -36657,6 +36662,20 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"packages/list-layout": {
|
||||
"name": "@k8slens/list-layout",
|
||||
"version": "1.0.0-alpha.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@k8slens/eslint-config": "^6.5.0-alpha.2",
|
||||
"@k8slens/jest": "^6.5.0-alpha.2",
|
||||
"@k8slens/typescript": "^6.5.0-alpha.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ogre-tools/injectable": "^15.1.2",
|
||||
"react": "^17.0.2"
|
||||
}
|
||||
},
|
||||
"packages/metrics": {
|
||||
"name": "@k8slens/metrics",
|
||||
"version": "6.5.0-alpha.3",
|
||||
|
||||
@ -115,6 +115,7 @@
|
||||
"@k8slens/metrics": "^6.5.0-alpha.3",
|
||||
"@k8slens/node-fetch": "^6.5.0-alpha.3",
|
||||
"@k8slens/react-application": "^1.0.0-alpha.2",
|
||||
"@k8slens/list-layout": "^1.0.0-alpha.0",
|
||||
"@kubernetes/client-node": "^0.18.1",
|
||||
"@material-ui/styles": "^4.11.5",
|
||||
"@sentry/electron": "^3.0.8",
|
||||
|
||||
@ -3,14 +3,11 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { ItemObject } from "@k8slens/list-layout";
|
||||
import autoBind from "auto-bind";
|
||||
import orderBy from "lodash/orderBy";
|
||||
import { action, computed, observable, when, makeObservable } from "mobx";
|
||||
|
||||
export interface ItemObject {
|
||||
getId(): string;
|
||||
getName(): string;
|
||||
}
|
||||
|
||||
export abstract class ItemStore<Item extends ItemObject> {
|
||||
protected defaultSorting = (item: Item) => item.getName();
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { ItemObject } from "../../item.store";
|
||||
import type { ItemObject } from "@k8slens/list-layout";
|
||||
import type { HelmReleaseDetails } from "./helm-releases.api/request-details.injectable";
|
||||
|
||||
export interface HelmReleaseUpdateDetails {
|
||||
|
||||
@ -22,7 +22,7 @@ import {
|
||||
isTypedArray,
|
||||
isRecord,
|
||||
} from "@k8slens/utilities";
|
||||
import type { ItemObject } from "../item.store";
|
||||
import type { ItemObject } from "@k8slens/list-layout";
|
||||
import type { Patch } from "rfc6902";
|
||||
import assert from "assert";
|
||||
import type { JsonObject } from "type-fest";
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
132
packages/core/src/features/cluster/workloads/pods.test.tsx
Normal file
132
packages/core/src/features/cluster/workloads/pods.test.tsx
Normal file
@ -0,0 +1,132 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { RenderResult } from "@testing-library/react";
|
||||
import navigateToPodsInjectable from "../../../common/front-end-routing/routes/cluster/workloads/pods/navigate-to-pods.injectable";
|
||||
import { type ApplicationBuilder, getApplicationBuilder } from "../../../renderer/components/test-utils/get-application-builder";
|
||||
import podStoreInjectable from "../../../renderer/components/+workloads-pods/store.injectable";
|
||||
import type { PodMetrics } from "../../../common/k8s-api/endpoints";
|
||||
import { Pod } from "../../../common/k8s-api/endpoints";
|
||||
import podMetricsApiInjectable from "../../../common/k8s-api/endpoints/pod-metrics.api.injectable";
|
||||
import requestMetricsInjectable from "../../../common/k8s-api/endpoints/metrics.api/request-metrics.injectable";
|
||||
import apiManagerInjectable from "../../../common/k8s-api/api-manager/manager.injectable";
|
||||
|
||||
describe("workloads / pods", () => {
|
||||
let rendered: RenderResult;
|
||||
let applicationBuilder: ApplicationBuilder;
|
||||
const podMetrics: PodMetrics[] = [];
|
||||
|
||||
beforeEach(async () => {
|
||||
applicationBuilder = getApplicationBuilder().setEnvironmentToClusterFrame();
|
||||
applicationBuilder.namespaces.add("default");
|
||||
applicationBuilder.beforeWindowStart(({ windowDi }) => {
|
||||
applicationBuilder.allowKubeResource({
|
||||
apiName: "pods",
|
||||
group: "",
|
||||
});
|
||||
|
||||
windowDi.override(podMetricsApiInjectable, () => ({
|
||||
list: async () => podMetrics,
|
||||
} as any));
|
||||
|
||||
const apiManager = windowDi.inject(apiManagerInjectable);
|
||||
const podStore = windowDi.inject(podStoreInjectable);
|
||||
|
||||
apiManager.registerStore(podStore);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when navigating to workloads / pods view", () => {
|
||||
describe("given pods are loading", () => {
|
||||
beforeEach(async () => {
|
||||
applicationBuilder.afterWindowStart(({ windowDi }) => {
|
||||
const podStore = windowDi.inject(podStoreInjectable);
|
||||
|
||||
podStore.items.clear();
|
||||
podStore.isLoaded = false;
|
||||
podStore.isLoading = true;
|
||||
});
|
||||
|
||||
rendered = await applicationBuilder.render();
|
||||
applicationBuilder.navigateWith(navigateToPodsInjectable);
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("shows loading spinner", async () => {
|
||||
expect(await rendered.findByTestId("kube-object-list-layout-spinner")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("given no pods", () => {
|
||||
beforeEach(async () => {
|
||||
applicationBuilder.afterWindowStart(({ windowDi }) => {
|
||||
const podStore = windowDi.inject(podStoreInjectable);
|
||||
|
||||
podStore.items.clear();
|
||||
podStore.isLoaded = true;
|
||||
});
|
||||
|
||||
rendered = await applicationBuilder.render();
|
||||
applicationBuilder.navigateWith(navigateToPodsInjectable);
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("shows item list is empty", async () => {
|
||||
expect(rendered.getByText("Item list is empty")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("given a namespace has pods", () => {
|
||||
beforeEach(async () => {
|
||||
applicationBuilder.afterWindowStart(({ windowDi }) => {
|
||||
windowDi.override(requestMetricsInjectable, () => () => ({} as any));
|
||||
|
||||
const podStore = windowDi.inject(podStoreInjectable);
|
||||
|
||||
podStore.items.push(new Pod({
|
||||
apiVersion: "v1",
|
||||
kind: "Pod",
|
||||
metadata: {
|
||||
name: "test-pod-1",
|
||||
namespace: "default",
|
||||
resourceVersion: "irrelevant",
|
||||
selfLink: "/api/v1/namespaces/default/pods/test-pod-1",
|
||||
uid: "uuid-1",
|
||||
},
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: "container-1",
|
||||
},
|
||||
{
|
||||
name: "container-2",
|
||||
},
|
||||
],
|
||||
},
|
||||
status: {} as any,
|
||||
}));
|
||||
podStore.isLoaded = true;
|
||||
});
|
||||
|
||||
rendered = await applicationBuilder.render();
|
||||
applicationBuilder.navigateWith(navigateToPodsInjectable);
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders the pod list", async () => {
|
||||
expect(await rendered.findByTestId(`list-pod-name-uuid-1`)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -13,7 +13,7 @@ import { Icon } from "../icon";
|
||||
import { SubHeader } from "../layout/sub-header";
|
||||
import { Table, TableCell, TableHead, TableRow } from "../table";
|
||||
import { cssNames, prevDefault } from "@k8slens/utilities";
|
||||
import type { ItemObject } from "../../../common/item.store";
|
||||
import type { ItemObject } from "@k8slens/list-layout";
|
||||
import { Spinner } from "../spinner";
|
||||
import type { ApiManager } from "../../../common/k8s-api/api-manager";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import React from "react";
|
||||
import type { Pod } from "../../../../common/k8s-api/endpoints";
|
||||
import type { KubeObjectListLayoutColumn } from "@k8slens/list-layout";
|
||||
import { kubeObjectListLayoutColumnInjectionToken } from "@k8slens/list-layout";
|
||||
import { KubeObjectAge } from "../../kube-object/age";
|
||||
|
||||
export const podsAgeColumnInjectable = getInjectable({
|
||||
id: "pods-age-column",
|
||||
instantiate: (): KubeObjectListLayoutColumn<Pod> => {
|
||||
const columnId = "age";
|
||||
|
||||
return {
|
||||
id: columnId,
|
||||
kind: "Pod",
|
||||
apiVersion: "v1",
|
||||
priority: 30,
|
||||
content: (pod: Pod) => {
|
||||
return <KubeObjectAge key="age" object={pod} />;
|
||||
},
|
||||
header: { title: "Age", className: "age", sortBy: columnId, id: columnId },
|
||||
sortingCallBack: (pod: Pod) => -pod.getCreationTimestamp(),
|
||||
};
|
||||
},
|
||||
injectionToken: kubeObjectListLayoutColumnInjectionToken,
|
||||
});
|
||||
@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { cssNames } from "@k8slens/utilities";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import startCase from "lodash/startCase";
|
||||
import React from "react";
|
||||
import type { ContainerStateValues, Pod } from "../../../../common/k8s-api/endpoints";
|
||||
import type { KubeObjectListLayoutColumn } from "@k8slens/list-layout";
|
||||
import { kubeObjectListLayoutColumnInjectionToken } from "@k8slens/list-layout";
|
||||
import { StatusBrick } from "../../status-brick";
|
||||
|
||||
function renderState(name: string, ready: boolean, key: string, data?: ContainerStateValues) {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="title">
|
||||
{name}
|
||||
{" "}
|
||||
<span className="text-secondary">
|
||||
{key}
|
||||
{ready ? ", ready" : ""}
|
||||
</span>
|
||||
</div>
|
||||
{Object.entries(data).map(([name, value]) => (
|
||||
<React.Fragment key={name}>
|
||||
<div className="name">{startCase(name)}</div>
|
||||
<div className="value">{value}</div>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function renderContainersStatus(pod: Pod) {
|
||||
return pod.getContainerStatuses().map(({ name, state, ready }) => {
|
||||
return (
|
||||
<StatusBrick
|
||||
key={name}
|
||||
className={cssNames(state, { ready })}
|
||||
tooltip={{
|
||||
formatters: {
|
||||
tableView: true,
|
||||
nowrap: true,
|
||||
},
|
||||
children: (
|
||||
<>
|
||||
{renderState(name, ready, "running", state?.running)}
|
||||
{renderState(name, ready, "waiting", state?.waiting)}
|
||||
{renderState(name, ready, "terminated", state?.terminated)}
|
||||
</>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export const podsContainersColumnInjectable = getInjectable({
|
||||
id: "pods-containers-column",
|
||||
instantiate: (): KubeObjectListLayoutColumn<Pod> => {
|
||||
const columnId = "containers";
|
||||
|
||||
return {
|
||||
id: columnId,
|
||||
kind: "Pod",
|
||||
apiVersion: "v1",
|
||||
priority: 80,
|
||||
content: (pod: Pod) => {
|
||||
return renderContainersStatus(pod);
|
||||
},
|
||||
header: {
|
||||
title: "Containers",
|
||||
className: "containers",
|
||||
sortBy: columnId,
|
||||
id: columnId,
|
||||
},
|
||||
sortingCallBack: (pod: Pod) => pod.getContainerStatuses().length,
|
||||
};
|
||||
},
|
||||
injectionToken: kubeObjectListLayoutColumnInjectionToken,
|
||||
});
|
||||
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getConvertedParts } from "@k8slens/utilities";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import React from "react";
|
||||
import type { Pod } from "../../../../common/k8s-api/endpoints";
|
||||
import type { KubeObjectListLayoutColumn } from "@k8slens/list-layout";
|
||||
import { kubeObjectListLayoutColumnInjectionToken } from "@k8slens/list-layout";
|
||||
import { Tooltip } from "@k8slens/tooltip";
|
||||
|
||||
export const podsNameColumnInjectable = getInjectable({
|
||||
id: "pods-name-column",
|
||||
instantiate: (): KubeObjectListLayoutColumn<Pod> => {
|
||||
const columnId = "name";
|
||||
|
||||
return {
|
||||
id: columnId,
|
||||
kind: "Pod",
|
||||
apiVersion: "v1",
|
||||
priority: 100,
|
||||
content: (pod: Pod) => {
|
||||
return (
|
||||
<>
|
||||
<span id={`list-pod-name-${pod.getId()}`} data-testid={`list-pod-name-${pod.getId()}`}>
|
||||
{pod.getName()}
|
||||
</span>
|
||||
<Tooltip targetId={`list-pod-name-${pod.getId()}`}>
|
||||
{pod.getName()}
|
||||
</Tooltip>
|
||||
</>
|
||||
);
|
||||
},
|
||||
header: { title: "Name", className: "name", sortBy: columnId, id: columnId },
|
||||
sortingCallBack: (pod: Pod) => getConvertedParts(pod.getName()),
|
||||
searchFilter: (pod: Pod) => pod.getSearchFields(),
|
||||
};
|
||||
},
|
||||
injectionToken: kubeObjectListLayoutColumnInjectionToken,
|
||||
});
|
||||
|
||||
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import React from "react";
|
||||
import { NamespaceSelectBadge } from "../../+namespaces/namespace-select-badge";
|
||||
import type { Pod } from "../../../../common/k8s-api/endpoints";
|
||||
import type { KubeObjectListLayoutColumn } from "@k8slens/list-layout";
|
||||
import { kubeObjectListLayoutColumnInjectionToken } from "@k8slens/list-layout";
|
||||
|
||||
export const podsNamespaceColumnInjectable = getInjectable({
|
||||
id: "pods-namespace-column",
|
||||
instantiate: (): KubeObjectListLayoutColumn<Pod> => {
|
||||
const columnId = "namespace";
|
||||
|
||||
return {
|
||||
id: columnId,
|
||||
kind: "Pod",
|
||||
apiVersion: "v1",
|
||||
priority: 90,
|
||||
content: (pod: Pod) => {
|
||||
return (<NamespaceSelectBadge key="namespace" namespace={pod.getNs()} />);
|
||||
},
|
||||
header: { title: "Namespace", className: "namespace", sortBy: columnId, id: columnId },
|
||||
sortingCallBack: (pod: Pod) => pod.getNs(),
|
||||
};
|
||||
},
|
||||
injectionToken: kubeObjectListLayoutColumnInjectionToken,
|
||||
});
|
||||
|
||||
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import nodeApiInjectable from "../../../../common/k8s-api/endpoints/node.api.injectable";
|
||||
import type { Pod } from "../../../../common/k8s-api/endpoints";
|
||||
import { Badge } from "../../badge";
|
||||
import getDetailsUrlInjectable from "../../kube-detail-params/get-details-url.injectable";
|
||||
import type { KubeObjectListLayoutColumn } from "@k8slens/list-layout";
|
||||
import { kubeObjectListLayoutColumnInjectionToken } from "@k8slens/list-layout";
|
||||
import { stopPropagation } from "@k8slens/utilities";
|
||||
|
||||
export const podsNodeColumnInjectable = getInjectable({
|
||||
id: "pods-node-column",
|
||||
instantiate: (di): KubeObjectListLayoutColumn<Pod> => {
|
||||
const getDetailsUrl = di.inject(getDetailsUrlInjectable);
|
||||
const nodeApi = di.inject(nodeApiInjectable);
|
||||
const columnId = "node";
|
||||
|
||||
return {
|
||||
id: columnId,
|
||||
kind: "Pod",
|
||||
apiVersion: "v1",
|
||||
priority: 50,
|
||||
content: (pod: Pod) => {
|
||||
return pod.getNodeName() ? (
|
||||
<Badge
|
||||
flat
|
||||
key="node"
|
||||
className="node"
|
||||
tooltip={pod.getNodeName()}
|
||||
expandable={false}
|
||||
>
|
||||
<Link
|
||||
to={getDetailsUrl(nodeApi.getUrl({ name: pod.getNodeName() }))}
|
||||
onClick={stopPropagation}>
|
||||
{pod.getNodeName()}
|
||||
</Link>
|
||||
</Badge>
|
||||
)
|
||||
: "";
|
||||
},
|
||||
header: { title: "Node", className: "node", sortBy: columnId, id: columnId },
|
||||
sortingCallBack: (pod: Pod) => pod.getNodeName(),
|
||||
};
|
||||
},
|
||||
injectionToken: kubeObjectListLayoutColumnInjectionToken,
|
||||
});
|
||||
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { stopPropagation } from "@k8slens/utilities";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import apiManagerInjectable from "../../../../common/k8s-api/api-manager/manager.injectable";
|
||||
import type { Pod } from "../../../../common/k8s-api/endpoints";
|
||||
import { Badge } from "../../badge";
|
||||
import getDetailsUrlInjectable from "../../kube-detail-params/get-details-url.injectable";
|
||||
import type { KubeObjectListLayoutColumn } from "@k8slens/list-layout";
|
||||
import { kubeObjectListLayoutColumnInjectionToken } from "@k8slens/list-layout";
|
||||
|
||||
export const podsOwnersColumnInjectable = getInjectable({
|
||||
id: "pods-owners-column",
|
||||
instantiate: (di): KubeObjectListLayoutColumn<Pod> => {
|
||||
const getDetailsUrl = di.inject(getDetailsUrlInjectable);
|
||||
const apiManager = di.inject(apiManagerInjectable);
|
||||
const columnId = "owners";
|
||||
|
||||
return {
|
||||
id: columnId,
|
||||
kind: "Pod",
|
||||
apiVersion: "v1",
|
||||
priority: 60,
|
||||
content: (pod: Pod) => {
|
||||
return pod.getOwnerRefs().map(ref => {
|
||||
const { kind, name } = ref;
|
||||
const detailsLink = getDetailsUrl(apiManager.lookupApiLink(ref, pod));
|
||||
|
||||
return (
|
||||
<Badge
|
||||
flat
|
||||
key={name}
|
||||
className="owner"
|
||||
tooltip={name}
|
||||
>
|
||||
<Link to={detailsLink} onClick={stopPropagation}>
|
||||
{kind}
|
||||
</Link>
|
||||
</Badge>
|
||||
);
|
||||
});
|
||||
},
|
||||
header: { title: "Controlled By", className: "owners", sortBy: columnId, id: columnId },
|
||||
sortingCallBack: (pod: Pod) => pod.getOwnerRefs().map(ref => ref.kind),
|
||||
};
|
||||
},
|
||||
injectionToken: kubeObjectListLayoutColumnInjectionToken,
|
||||
});
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { Pod } from "../../../../common/k8s-api/endpoints";
|
||||
import type { KubeObjectListLayoutColumn } from "@k8slens/list-layout";
|
||||
import { kubeObjectListLayoutColumnInjectionToken } from "@k8slens/list-layout";
|
||||
|
||||
export const podsQosColumnInjectable = getInjectable({
|
||||
id: "pods-qos-column",
|
||||
instantiate: (): KubeObjectListLayoutColumn<Pod> => {
|
||||
const columnId = "qos";
|
||||
|
||||
return {
|
||||
id: columnId,
|
||||
kind: "Pod",
|
||||
apiVersion: "v1",
|
||||
priority: 40,
|
||||
content: (pod: Pod) => {
|
||||
return pod.getQosClass();
|
||||
},
|
||||
header: { title: "QoS", className: "qos", sortBy: columnId, id: columnId },
|
||||
sortingCallBack: (pod: Pod) => pod.getQosClass(),
|
||||
};
|
||||
},
|
||||
injectionToken: kubeObjectListLayoutColumnInjectionToken,
|
||||
});
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { Pod } from "../../../../common/k8s-api/endpoints";
|
||||
import type { KubeObjectListLayoutColumn } from "@k8slens/list-layout";
|
||||
import { kubeObjectListLayoutColumnInjectionToken } from "@k8slens/list-layout";
|
||||
|
||||
export const podsRestartsColumnInjectable = getInjectable({
|
||||
id: "pods-restarts-column",
|
||||
instantiate: (): KubeObjectListLayoutColumn<Pod> => {
|
||||
const columnId = "restarts";
|
||||
|
||||
return {
|
||||
id: columnId,
|
||||
kind: "Pod",
|
||||
apiVersion: "v1",
|
||||
priority: 70,
|
||||
content: (pod: Pod) => {
|
||||
return pod.getRestartsCount();
|
||||
},
|
||||
header: { title: "Restarts", className: "restarts", sortBy: columnId, id: columnId },
|
||||
sortingCallBack: (pod: Pod) => pod.getRestartsCount(),
|
||||
};
|
||||
},
|
||||
injectionToken: kubeObjectListLayoutColumnInjectionToken,
|
||||
});
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { kebabCase } from "lodash";
|
||||
import type { Pod } from "../../../../common/k8s-api/endpoints";
|
||||
import type { KubeObjectListLayoutColumn } from "@k8slens/list-layout";
|
||||
import { kubeObjectListLayoutColumnInjectionToken } from "@k8slens/list-layout";
|
||||
|
||||
export const podsStatusColumnInjectable = getInjectable({
|
||||
id: "pods-status-column",
|
||||
instantiate: (): KubeObjectListLayoutColumn<Pod> => {
|
||||
const columnId = "status";
|
||||
|
||||
return {
|
||||
id: columnId,
|
||||
kind: "Pod",
|
||||
apiVersion: "v1",
|
||||
priority: 0,
|
||||
content: (pod: Pod) => {
|
||||
return { title: pod.getStatusMessage(), className: kebabCase(pod.getStatusMessage()) };
|
||||
},
|
||||
header: { title: "Status", className: "status", sortBy: columnId, id: columnId },
|
||||
sortingCallBack: (pod: Pod) => pod.getStatusMessage(),
|
||||
searchFilter: (pod: Pod) => pod.getStatusMessage(),
|
||||
};
|
||||
},
|
||||
injectionToken: kubeObjectListLayoutColumnInjectionToken,
|
||||
});
|
||||
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import React from "react";
|
||||
import type { Pod } from "../../../../common/k8s-api/endpoints";
|
||||
import type { KubeObjectListLayoutColumn } from "@k8slens/list-layout";
|
||||
import { kubeObjectListLayoutColumnInjectionToken } from "@k8slens/list-layout";
|
||||
import { KubeObjectStatusIcon } from "../../kube-object-status-icon";
|
||||
|
||||
export const podsQosColumnInjectable = getInjectable({
|
||||
id: "pods-status-icon-column",
|
||||
instantiate: (): KubeObjectListLayoutColumn<Pod> => {
|
||||
const columnId = "qos";
|
||||
|
||||
return {
|
||||
id: columnId,
|
||||
kind: "Pod",
|
||||
apiVersion: "v1",
|
||||
priority: 99,
|
||||
content: (pod: Pod) => {
|
||||
return <KubeObjectStatusIcon key="icon" object={pod} />;
|
||||
},
|
||||
header: { className: "warning", showWithColumn: "name" },
|
||||
sortingCallBack: (pod: Pod) => pod.getQosClass(),
|
||||
};
|
||||
},
|
||||
injectionToken: kubeObjectListLayoutColumnInjectionToken,
|
||||
});
|
||||
|
||||
@ -7,103 +7,23 @@ import "./pods.scss";
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { KubeObjectListLayout } from "../kube-object-list-layout";
|
||||
import type { ContainerStateValues, NodeApi, Pod } from "../../../common/k8s-api/endpoints";
|
||||
import { StatusBrick } from "../status-brick";
|
||||
import { cssNames, getConvertedParts, object, stopPropagation } from "@k8slens/utilities";
|
||||
import startCase from "lodash/startCase";
|
||||
import kebabCase from "lodash/kebabCase";
|
||||
import type { ApiManager } from "../../../common/k8s-api/api-manager";
|
||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||
import { Badge } from "../badge";
|
||||
import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import type { GetDetailsUrl } from "../kube-detail-params/get-details-url.injectable";
|
||||
import getDetailsUrlInjectable from "../kube-detail-params/get-details-url.injectable";
|
||||
import apiManagerInjectable from "../../../common/k8s-api/api-manager/manager.injectable";
|
||||
import type { EventStore } from "../+events/store";
|
||||
import type { PodStore } from "./store";
|
||||
import nodeApiInjectable from "../../../common/k8s-api/endpoints/node.api.injectable";
|
||||
import eventStoreInjectable from "../+events/store.injectable";
|
||||
import podStoreInjectable from "./store.injectable";
|
||||
import { NamespaceSelectBadge } from "../+namespaces/namespace-select-badge";
|
||||
import { Tooltip } from "@k8slens/tooltip";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
namespace = "namespace",
|
||||
containers = "containers",
|
||||
restarts = "restarts",
|
||||
age = "age",
|
||||
qos = "qos",
|
||||
node = "node",
|
||||
owners = "owners",
|
||||
status = "status",
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
getDetailsUrl: GetDetailsUrl;
|
||||
apiManager: ApiManager;
|
||||
eventStore: EventStore;
|
||||
podStore: PodStore;
|
||||
nodeApi: NodeApi;
|
||||
}
|
||||
|
||||
@observer
|
||||
class NonInjectedPods extends React.Component<Dependencies> {
|
||||
renderState(name: string, ready: boolean, key: string, data?: ContainerStateValues) {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="title">
|
||||
{name}
|
||||
{" "}
|
||||
<span className="text-secondary">
|
||||
{key}
|
||||
{ready ? ", ready" : ""}
|
||||
</span>
|
||||
</div>
|
||||
{object.entries(data).map(([name, value]) => (
|
||||
<React.Fragment key={name}>
|
||||
<div className="name">{startCase(name)}</div>
|
||||
<div className="value">{value}</div>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
renderContainersStatus(pod: Pod) {
|
||||
return pod.getContainerStatuses().map(({ name, state, ready }) => {
|
||||
return (
|
||||
<StatusBrick
|
||||
key={name}
|
||||
className={cssNames(state, { ready })}
|
||||
tooltip={{
|
||||
formatters: {
|
||||
tableView: true,
|
||||
nowrap: true,
|
||||
},
|
||||
children: (
|
||||
<>
|
||||
{this.renderState(name, ready, "running", state?.running)}
|
||||
{this.renderState(name, ready, "waiting", state?.waiting)}
|
||||
{this.renderState(name, ready, "terminated", state?.terminated)}
|
||||
</>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { apiManager, getDetailsUrl, podStore, eventStore, nodeApi } = this.props;
|
||||
const { podStore, eventStore } = this.props;
|
||||
|
||||
return (
|
||||
<SiblingsInTabLayout>
|
||||
@ -113,109 +33,12 @@ class NonInjectedPods extends React.Component<Dependencies> {
|
||||
dependentStores={[eventStore]} // status icon component uses event store
|
||||
tableId="workloads_pods"
|
||||
isConfigurable
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: pod => getConvertedParts(pod.getName()),
|
||||
[columnId.namespace]: pod => pod.getNs(),
|
||||
[columnId.containers]: pod => pod.getContainerStatuses().length,
|
||||
[columnId.restarts]: pod => pod.getRestartsCount(),
|
||||
[columnId.owners]: pod => pod.getOwnerRefs().map(ref => ref.kind),
|
||||
[columnId.qos]: pod => pod.getQosClass(),
|
||||
[columnId.node]: pod => pod.getNodeName(),
|
||||
[columnId.age]: pod => -pod.getCreationTimestamp(),
|
||||
[columnId.status]: pod => pod.getStatusMessage(),
|
||||
}}
|
||||
searchFilters={[
|
||||
pod => pod.getSearchFields(),
|
||||
pod => pod.getStatusMessage(),
|
||||
pod => pod.status?.podIP,
|
||||
pod => pod.getNodeName(),
|
||||
]}
|
||||
renderHeaderTitle="Pods"
|
||||
renderTableHeader={[
|
||||
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
|
||||
{ className: "warning", showWithColumn: columnId.name },
|
||||
{
|
||||
title: "Namespace",
|
||||
className: "namespace",
|
||||
sortBy: columnId.namespace,
|
||||
id: columnId.namespace,
|
||||
},
|
||||
{
|
||||
title: "Containers",
|
||||
className: "containers",
|
||||
sortBy: columnId.containers,
|
||||
id: columnId.containers,
|
||||
},
|
||||
{
|
||||
title: "Restarts",
|
||||
className: "restarts",
|
||||
sortBy: columnId.restarts,
|
||||
id: columnId.restarts,
|
||||
},
|
||||
{
|
||||
title: "Controlled By",
|
||||
className: "owners",
|
||||
sortBy: columnId.owners,
|
||||
id: columnId.owners,
|
||||
},
|
||||
{ title: "Node", className: "node", sortBy: columnId.node, id: columnId.node },
|
||||
{ title: "QoS", className: "qos", sortBy: columnId.qos, id: columnId.qos },
|
||||
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
|
||||
{ title: "Status", className: "status", sortBy: columnId.status, id: columnId.status },
|
||||
]}
|
||||
renderTableContents={pod => [
|
||||
<>
|
||||
<span id={`list-pod-${pod.getId()}`}>
|
||||
{pod.getName()}
|
||||
</span>
|
||||
<Tooltip targetId={`list-pod-${pod.getId()}`}>
|
||||
{pod.getName()}
|
||||
</Tooltip>
|
||||
</>,
|
||||
<KubeObjectStatusIcon key="icon" object={pod} />,
|
||||
<NamespaceSelectBadge
|
||||
key="namespace"
|
||||
namespace={pod.getNs()}
|
||||
/>,
|
||||
this.renderContainersStatus(pod),
|
||||
pod.getRestartsCount(),
|
||||
pod.getOwnerRefs().map(ref => {
|
||||
const { kind, name } = ref;
|
||||
const detailsLink = getDetailsUrl(apiManager.lookupApiLink(ref, pod));
|
||||
|
||||
return (
|
||||
<Badge
|
||||
flat
|
||||
key={name}
|
||||
className="owner"
|
||||
tooltip={name}
|
||||
>
|
||||
<Link to={detailsLink} onClick={stopPropagation}>
|
||||
{kind}
|
||||
</Link>
|
||||
</Badge>
|
||||
);
|
||||
}),
|
||||
pod.getNodeName() ? (
|
||||
<Badge
|
||||
flat
|
||||
key="node"
|
||||
className="node"
|
||||
tooltip={pod.getNodeName()}
|
||||
expandable={false}
|
||||
>
|
||||
<Link
|
||||
to={getDetailsUrl(nodeApi.getUrl({ name: pod.getNodeName() }))}
|
||||
onClick={stopPropagation}>
|
||||
{pod.getNodeName()}
|
||||
</Link>
|
||||
</Badge>
|
||||
)
|
||||
: "",
|
||||
pod.getQosClass(),
|
||||
<KubeObjectAge key="age" object={pod} />,
|
||||
{ title: pod.getStatusMessage(), className: kebabCase(pod.getStatusMessage()) },
|
||||
]}
|
||||
renderTableHeader={[]}
|
||||
renderTableContents={() => []}
|
||||
/>
|
||||
</SiblingsInTabLayout>
|
||||
);
|
||||
@ -225,9 +48,6 @@ class NonInjectedPods extends React.Component<Dependencies> {
|
||||
export const Pods = withInjectables<Dependencies>(NonInjectedPods, {
|
||||
getProps: (di, props) => ({
|
||||
...props,
|
||||
apiManager: di.inject(apiManagerInjectable),
|
||||
getDetailsUrl: di.inject(getDetailsUrlInjectable),
|
||||
nodeApi: di.inject(nodeApiInjectable),
|
||||
eventStore: di.inject(eventStoreInjectable),
|
||||
podStore: di.inject(podStoreInjectable),
|
||||
}),
|
||||
|
||||
@ -19,7 +19,7 @@ import type { AddRemoveButtonsProps } from "../add-remove-buttons";
|
||||
import { AddRemoveButtons } from "../add-remove-buttons";
|
||||
import { NoItems } from "../no-items";
|
||||
import { Spinner } from "../spinner";
|
||||
import type { ItemObject } from "../../../common/item.store";
|
||||
import type { ItemObject } from "@k8slens/list-layout";
|
||||
import type { Filter, PageFiltersStore } from "./page-filters/store";
|
||||
import type { LensTheme } from "../../themes/lens-theme";
|
||||
import { MenuActions } from "../menu/menu-actions";
|
||||
|
||||
@ -10,7 +10,7 @@ import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import type { IClassName } from "@k8slens/utilities";
|
||||
import { cssNames, isDefined } from "@k8slens/utilities";
|
||||
import type { ItemObject } from "../../../common/item.store";
|
||||
import type { ItemObject } from "@k8slens/list-layout";
|
||||
import type { Filter } from "./page-filters/store";
|
||||
import type { HeaderCustomizer, HeaderPlaceholders, ItemListStore, SearchFilter } from "./list-layout";
|
||||
import { SearchInputUrl } from "../input";
|
||||
|
||||
@ -14,7 +14,7 @@ import type { TableCellProps, TableProps, TableRowProps, TableSortCallbacks } fr
|
||||
import type { IClassName, SingleOrMany } from "@k8slens/utilities";
|
||||
import { cssNames, noop } from "@k8slens/utilities";
|
||||
import type { AddRemoveButtonsProps } from "../add-remove-buttons";
|
||||
import type { ItemObject } from "../../../common/item.store";
|
||||
import type { ItemObject } from "@k8slens/list-layout";
|
||||
import type { SearchInputUrlProps } from "../input";
|
||||
import type { PageFiltersStore } from "./page-filters/store";
|
||||
import { FilterType } from "./page-filters/store";
|
||||
|
||||
@ -104,10 +104,236 @@ exports[`kube-object-list-layout given pod store renders 1`] = `
|
||||
class="items box grow flex column"
|
||||
>
|
||||
<div
|
||||
class="Table flex column KubeObjectListLayout Pods box grow dark selectable scrollable autoSize virtual"
|
||||
class="Table flex column KubeObjectListLayout Pods box grow dark selectable scrollable sortable autoSize virtual"
|
||||
>
|
||||
<div
|
||||
class="TableHead sticky nowrap topLine"
|
||||
>
|
||||
<div
|
||||
class="TableCell checkbox"
|
||||
>
|
||||
<label
|
||||
class="Checkbox flex align-center"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
/>
|
||||
<i
|
||||
class="box flex align-center"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="TableCell name nowrap sorting"
|
||||
id="name"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
>
|
||||
Name
|
||||
</div>
|
||||
<i
|
||||
class="Icon sortIcon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="arrow_drop_down"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</i>
|
||||
</div>
|
||||
<div
|
||||
class="TableCell warning nowrap"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="TableCell namespace nowrap sorting"
|
||||
id="namespace"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
>
|
||||
Namespace
|
||||
</div>
|
||||
<i
|
||||
class="Icon sortIcon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="arrow_drop_down"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</i>
|
||||
</div>
|
||||
<div
|
||||
class="TableCell containers nowrap sorting"
|
||||
id="containers"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
>
|
||||
Containers
|
||||
</div>
|
||||
<i
|
||||
class="Icon sortIcon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="arrow_drop_down"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</i>
|
||||
</div>
|
||||
<div
|
||||
class="TableCell restarts nowrap sorting"
|
||||
id="restarts"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
>
|
||||
Restarts
|
||||
</div>
|
||||
<i
|
||||
class="Icon sortIcon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="arrow_drop_down"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</i>
|
||||
</div>
|
||||
<div
|
||||
class="TableCell owners nowrap sorting"
|
||||
id="owners"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
>
|
||||
Controlled By
|
||||
</div>
|
||||
<i
|
||||
class="Icon sortIcon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="arrow_drop_down"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</i>
|
||||
</div>
|
||||
<div
|
||||
class="TableCell node nowrap sorting"
|
||||
id="node"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
>
|
||||
Node
|
||||
</div>
|
||||
<i
|
||||
class="Icon sortIcon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="arrow_drop_down"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</i>
|
||||
</div>
|
||||
<div
|
||||
class="TableCell qos nowrap sorting"
|
||||
id="qos"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
>
|
||||
QoS
|
||||
</div>
|
||||
<i
|
||||
class="Icon sortIcon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="arrow_drop_down"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</i>
|
||||
</div>
|
||||
<div
|
||||
class="TableCell age nowrap sorting"
|
||||
id="age"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
>
|
||||
Age
|
||||
</div>
|
||||
<i
|
||||
class="Icon sortIcon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="arrow_drop_down"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</i>
|
||||
</div>
|
||||
<div
|
||||
class="TableCell status nowrap sorting"
|
||||
id="status"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
>
|
||||
Status
|
||||
</div>
|
||||
<i
|
||||
class="Icon sortIcon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="arrow_drop_down"
|
||||
>
|
||||
arrow_drop_down
|
||||
</span>
|
||||
</i>
|
||||
</div>
|
||||
<div
|
||||
class="TableCell menu nowrap"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
>
|
||||
<i
|
||||
class="Icon material interactive focusable"
|
||||
id="menu-actions-for-item-object-list-content"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="more_vert"
|
||||
>
|
||||
more_vert
|
||||
</span>
|
||||
</i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="Spinner singleColor center"
|
||||
data-testid="kube-object-list-layout-spinner"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@ -21,6 +21,7 @@ import directoryForKubeConfigsInjectable from "../../../common/app-paths/directo
|
||||
import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable";
|
||||
import type { PodStore } from "../+workloads-pods/store";
|
||||
import { Cluster } from "../../../common/cluster/cluster";
|
||||
import isTableColumnHiddenInjectable from "../../../features/user-preferences/common/is-table-column-hidden.injectable";
|
||||
|
||||
describe("kube-object-list-layout", () => {
|
||||
let di: DiContainer;
|
||||
@ -33,6 +34,7 @@ describe("kube-object-list-layout", () => {
|
||||
di.override(directoryForUserDataInjectable, () => "/some-user-store-path");
|
||||
di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs");
|
||||
di.override(storesAndApisCanBeCreatedInjectable, () => true);
|
||||
di.override(isTableColumnHiddenInjectable, () => () => false);
|
||||
|
||||
di.override(hostedClusterInjectable, () => new Cluster({
|
||||
contextName: "some-context-name",
|
||||
|
||||
@ -29,6 +29,9 @@ import type { ToggleKubeDetailsPane } from "../kube-detail-params/toggle-details
|
||||
import kubeSelectedUrlParamInjectable from "../kube-detail-params/kube-selected-url.injectable";
|
||||
import toggleKubeDetailsPaneInjectable from "../kube-detail-params/toggle-details.injectable";
|
||||
import type { ClusterContext } from "../../cluster-frame-context/cluster-frame-context";
|
||||
import type { KubeObjectListLayoutColumn, ItemObject } from "@k8slens/list-layout";
|
||||
import { kubeObjectListLayoutColumnInjectionToken } from "@k8slens/list-layout";
|
||||
import { sortBy } from "lodash";
|
||||
|
||||
export interface KubeObjectListLayoutProps<
|
||||
K extends KubeObject,
|
||||
@ -53,6 +56,7 @@ interface Dependencies {
|
||||
subscribeToStores: SubscribeStores;
|
||||
kubeSelectedUrlParam: PageParam<string>;
|
||||
toggleKubeDetailsPane: ToggleKubeDetailsPane;
|
||||
columns: KubeObjectListLayoutColumn<ItemObject>[];
|
||||
}
|
||||
|
||||
const getLoadErrorMessage = (error: unknown): string => {
|
||||
@ -140,9 +144,25 @@ class NonInjectedKubeObjectListLayout<
|
||||
dependentStores,
|
||||
toggleKubeDetailsPane: toggleDetails,
|
||||
onDetails,
|
||||
renderTableContents,
|
||||
renderTableHeader,
|
||||
columns,
|
||||
sortingCallbacks = {},
|
||||
...layoutProps
|
||||
} = this.props;
|
||||
const resourceName = this.props.resourceName || ResourceNames[ResourceKindMap[store.api.kind]] || store.api.kind;
|
||||
const targetColumns = columns.filter((col) => col.kind === store.api.kind && col.apiVersion === store.api.apiVersionWithGroup);
|
||||
|
||||
targetColumns.forEach((col) => {
|
||||
if (col.sortingCallBack) {
|
||||
sortingCallbacks[col.id] = col.sortingCallBack;
|
||||
}
|
||||
});
|
||||
|
||||
const headers = sortBy([
|
||||
...(renderTableHeader || []).map((header, index) => ({ priority: (20 - index), header })),
|
||||
...targetColumns,
|
||||
], (v) => -v.priority).map((col) => col.header);
|
||||
|
||||
return (
|
||||
<ItemListLayout<K, false>
|
||||
@ -175,6 +195,15 @@ class NonInjectedKubeObjectListLayout<
|
||||
]}
|
||||
renderItemMenu={item => <KubeObjectMenu object={item} />}
|
||||
onDetails={onDetails ?? ((item) => toggleDetails(item.selfLink))}
|
||||
sortingCallbacks={sortingCallbacks}
|
||||
renderTableHeader={headers}
|
||||
renderTableContents={(item) => {
|
||||
return sortBy([
|
||||
...(renderTableContents(item).map((content, index) => ({ priority: (20 - index), content }))),
|
||||
...targetColumns.map((col) => ({ priority: col.priority, content: col.content(item) })),
|
||||
], (item) => -item.priority).map((value) => value.content);
|
||||
}}
|
||||
spinnerTestId="kube-object-list-layout-spinner"
|
||||
{...layoutProps}
|
||||
/>
|
||||
);
|
||||
@ -191,6 +220,7 @@ export const KubeObjectListLayout = withInjectables<
|
||||
subscribeToStores: di.inject(subscribeStoresInjectable),
|
||||
kubeSelectedUrlParam: di.inject(kubeSelectedUrlParamInjectable),
|
||||
toggleKubeDetailsPane: di.inject(toggleKubeDetailsPaneInjectable),
|
||||
columns: di.injectMany(kubeObjectListLayoutColumnInjectionToken),
|
||||
}),
|
||||
}) as <
|
||||
K extends KubeObject,
|
||||
|
||||
@ -20,7 +20,7 @@ import { getSorted } from "./sorting";
|
||||
import type { TableModel } from "./table-model/table-model";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import tableModelInjectable from "./table-model/table-model.injectable";
|
||||
import type { ItemObject } from "../../../common/item.store";
|
||||
import type { ItemObject } from "@k8slens/list-layout";
|
||||
import assert from "assert";
|
||||
import orderByUrlParamInjectable from "./order-by-url-param.injectable";
|
||||
import sortByUrlParamInjectable from "./sort-by-url-param.injectable";
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
|
||||
|
||||
import autoBind from "auto-bind";
|
||||
import type { ItemObject } from "../../common/item.store";
|
||||
import type { ItemObject } from "@k8slens/list-layout";
|
||||
|
||||
export type ForwardedPortStatus = "Active" | "Disabled";
|
||||
export interface ForwardedPort {
|
||||
|
||||
6
packages/list-layout/.eslintrc.js
Normal file
6
packages/list-layout/.eslintrc.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
extends: "@k8slens/eslint-config/eslint",
|
||||
parserOptions: {
|
||||
project: "./tsconfig.json",
|
||||
},
|
||||
};
|
||||
1
packages/list-layout/.prettierrc
Normal file
1
packages/list-layout/.prettierrc
Normal file
@ -0,0 +1 @@
|
||||
"@k8slens/eslint-config/prettier"
|
||||
1
packages/list-layout/index.ts
Normal file
1
packages/list-layout/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./kube-object-list-layout-column-injection-token";
|
||||
1
packages/list-layout/jest.config.js
Normal file
1
packages/list-layout/jest.config.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require("@k8slens/jest").monorepoPackageConfig(__dirname).configForReact;
|
||||
@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { ReactNode } from "react";
|
||||
import type { SingleOrMany } from "@k8slens/utilities";
|
||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||
|
||||
export interface ItemObject {
|
||||
getId(): string;
|
||||
getName(): string;
|
||||
}
|
||||
|
||||
export type TableSortBy = string;
|
||||
export type TableOrderBy = "asc" | "desc";
|
||||
export interface TableSortParams {
|
||||
sortBy: TableSortBy;
|
||||
orderBy: TableOrderBy;
|
||||
}
|
||||
|
||||
export type TableSortCallback<Item> = (data: Item) => undefined | string | number | (string | number)[];
|
||||
export type TableSortCallbacks<Item> = Record<string, TableSortCallback<Item>>;
|
||||
|
||||
export type SearchFilter<I extends ItemObject> = (item: I) => SingleOrMany<string | number | undefined | null>;
|
||||
|
||||
export interface TableCellProps extends React.DOMAttributes<HTMLDivElement> {
|
||||
/**
|
||||
* used for configuration visibility of columns
|
||||
*/
|
||||
id?: string;
|
||||
|
||||
/**
|
||||
* Any css class names for this table cell. Only used if `title` is a "simple" react node
|
||||
*/
|
||||
className?: string;
|
||||
|
||||
/**
|
||||
* The actual value of the cell
|
||||
*/
|
||||
title?: ReactNode;
|
||||
|
||||
/**
|
||||
* content inside could be scrolled
|
||||
*/
|
||||
scrollable?: boolean;
|
||||
|
||||
/**
|
||||
* render cell with a checkbox
|
||||
*/
|
||||
checkbox?: boolean;
|
||||
|
||||
/**
|
||||
* mark checkbox as checked or not
|
||||
*/
|
||||
isChecked?: boolean;
|
||||
|
||||
/**
|
||||
* column name, must be same as key in sortable object <Table sortable={}/>
|
||||
*/
|
||||
sortBy?: TableSortBy;
|
||||
|
||||
/**
|
||||
* id of the column which follow same visibility rules
|
||||
*/
|
||||
showWithColumn?: string;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_sorting?: Partial<TableSortParams>;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_sort?(sortBy: TableSortBy): void;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* indicator, might come from parent <TableHead>, don't use this prop outside (!)
|
||||
*/
|
||||
_nowrap?: boolean;
|
||||
}
|
||||
|
||||
export interface KubeObjectListLayoutColumn<Item extends ItemObject> {
|
||||
id: string;
|
||||
kind: string;
|
||||
apiVersion: string;
|
||||
priority: number;
|
||||
sortingCallBack?: TableSortCallback<Item>;
|
||||
searchFilter?: SearchFilter<Item>;
|
||||
header: TableCellProps | undefined | null;
|
||||
content: (item: Item) => ReactNode | TableCellProps;
|
||||
}
|
||||
|
||||
export const kubeObjectListLayoutColumnInjectionToken = getInjectionToken<KubeObjectListLayoutColumn<any>>({
|
||||
id: "kube-object-list-layout-column",
|
||||
});
|
||||
43
packages/list-layout/package.json
Normal file
43
packages/list-layout/package.json
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "@k8slens/list-layout",
|
||||
"private": false,
|
||||
"version": "1.0.0-alpha.0",
|
||||
"description": "Injection tokens for list layout",
|
||||
"type": "commonjs",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/lensapp/lens.git"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"author": {
|
||||
"name": "OpenLens Authors",
|
||||
"email": "info@k8slens.dev"
|
||||
},
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/lensapp/lens",
|
||||
"scripts": {
|
||||
"build": "webpack",
|
||||
"clean": "rimraf dist/",
|
||||
"dev": "webpack --mode=development --watch",
|
||||
"test": "jest --coverage --runInBand",
|
||||
"lint": "lens-lint",
|
||||
"lint:fix": "lens-lint --fix"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ogre-tools/injectable": "^15.1.2",
|
||||
"react": "^17.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@k8slens/eslint-config": "^6.5.0-alpha.2",
|
||||
"@k8slens/jest": "^6.5.0-alpha.2",
|
||||
"@k8slens/typescript": "^6.5.0-alpha.2"
|
||||
}
|
||||
}
|
||||
4
packages/list-layout/tsconfig.json
Normal file
4
packages/list-layout/tsconfig.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "@k8slens/typescript/config/base.json",
|
||||
"include": ["**/*.ts"]
|
||||
}
|
||||
1
packages/list-layout/webpack.config.js
Normal file
1
packages/list-layout/webpack.config.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require("@k8slens/webpack").configForReact;
|
||||
Loading…
Reference in New Issue
Block a user