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

Add support for LimitRange (#1796)

Signed-off-by: vshakirova <vshakirova@mirantis.com>
This commit is contained in:
Violetta 2021-01-14 12:49:39 +04:00 committed by GitHub
parent 12c538b0eb
commit c48816ca5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 292 additions and 1 deletions

View File

@ -313,6 +313,12 @@ describe("Lens integration tests", () => {
expectedSelector: "h5.title",
expectedText: "Resource Quotas"
},
{
name: "Limit Ranges",
href: "limitranges",
expectedSelector: "h5.title",
expectedText: "Limit Ranges"
},
{
name: "HPA",
href: "hpa",

View File

@ -1,7 +1,7 @@
import { getHostedCluster } from "./cluster-store";
export type KubeResource =
"namespaces" | "nodes" | "events" | "resourcequotas" | "services" |
"namespaces" | "nodes" | "events" | "resourcequotas" | "services" | "limitranges" |
"secrets" | "configmaps" | "ingresses" | "networkpolicies" | "persistentvolumeclaims" | "persistentvolumes" | "storageclasses" |
"pods" | "daemonsets" | "deployments" | "statefulsets" | "replicasets" | "jobs" | "cronjobs" |
"endpoints" | "customresourcedefinitions" | "horizontalpodautoscalers" | "podsecuritypolicies" | "poddisruptionbudgets";
@ -23,6 +23,7 @@ export const apiResources: KubeApiResource[] = [
{ resource: "horizontalpodautoscalers" },
{ resource: "ingresses", group: "networking.k8s.io" },
{ resource: "jobs", group: "batch" },
{ resource: "limitranges" },
{ resource: "namespaces" },
{ resource: "networkpolicies", group: "networking.k8s.io" },
{ resource: "nodes" },

View File

@ -14,6 +14,7 @@ export { ConfigMap, configMapApi } from "../../renderer/api/endpoints";
export { Secret, secretsApi, ISecretRef } from "../../renderer/api/endpoints";
export { ReplicaSet, replicaSetApi } from "../../renderer/api/endpoints";
export { ResourceQuota, resourceQuotaApi } from "../../renderer/api/endpoints";
export { LimitRange, limitRangeApi } from "../../renderer/api/endpoints";
export { HorizontalPodAutoscaler, hpaApi } from "../../renderer/api/endpoints";
export { PodDisruptionBudget, pdbApi } from "../../renderer/api/endpoints";
export { Service, serviceApi } from "../../renderer/api/endpoints";
@ -46,6 +47,7 @@ export type { ConfigMapsStore } from "../../renderer/components/+config-maps/con
export type { SecretsStore } from "../../renderer/components/+config-secrets/secrets.store";
export type { ReplicaSetStore } from "../../renderer/components/+workloads-replicasets/replicasets.store";
export type { ResourceQuotasStore } from "../../renderer/components/+config-resource-quotas/resource-quotas.store";
export type { LimitRangesStore } from "../../renderer/components/+config-limit-ranges/limit-ranges.store";
export type { HPAStore } from "../../renderer/components/+config-autoscalers/hpa.store";
export type { PodDisruptionBudgetsStore } from "../../renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.store";
export type { ServiceStore } from "../../renderer/components/+network-services/services.store";

View File

@ -14,6 +14,7 @@ export * from "./events.api";
export * from "./hpa.api";
export * from "./ingress.api";
export * from "./job.api";
export * from "./limit-range.api";
export * from "./namespaces.api";
export * from "./network-policy.api";
export * from "./nodes.api";

View File

@ -0,0 +1,57 @@
import { KubeObject } from "../kube-object";
import { KubeApi } from "../kube-api";
import { autobind } from "../../utils";
export enum LimitType {
CONTAINER = "Container",
POD = "Pod",
PVC = "PersistentVolumeClaim",
}
export enum Resource {
MEMORY = "memory",
CPU = "cpu",
STORAGE = "storage",
EPHEMERAL_STORAGE = "ephemeral-storage",
}
export enum LimitPart {
MAX = "max",
MIN = "min",
DEFAULT = "default",
DEFAULT_REQUEST = "defaultRequest",
MAX_LIMIT_REQUEST_RATIO = "maxLimitRequestRatio",
}
type LimitRangeParts = Partial<Record<LimitPart, Record<string, string>>>;
export interface LimitRangeItem extends LimitRangeParts {
type: string
}
@autobind()
export class LimitRange extends KubeObject {
static kind = "LimitRange";
static namespaced = true;
static apiBase = "/api/v1/limitranges";
spec: {
limits: LimitRangeItem[];
};
getContainerLimits() {
return this.spec.limits.filter(limit => limit.type === LimitType.CONTAINER);
}
getPodLimits() {
return this.spec.limits.filter(limit => limit.type === LimitType.POD);
}
getPVCLimits() {
return this.spec.limits.filter(limit => limit.type === LimitType.PVC);
}
}
export const limitRangeApi = new KubeApi({
objectConstructor: LimitRange,
});

View File

@ -0,0 +1,3 @@
export * from "./limit-ranges";
export * from "./limit-ranges.route";
export * from "./limit-range-details";

View File

@ -0,0 +1,12 @@
.LimitRangeDetails {
.DrawerItem {
> .name {
font-weight: $font-weight-normal;
padding-left: 4px;
}
.DrawerItem {
padding-top: 4px;
}
}
}

View File

@ -0,0 +1,97 @@
import "./limit-range-details.scss";
import React from "react";
import { observer } from "mobx-react";
import { KubeObjectDetailsProps } from "../kube-object";
import { LimitPart, LimitRange, LimitRangeItem, Resource } from "../../api/endpoints/limit-range.api";
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
import { DrawerItem } from "../drawer/drawer-item";
import { Badge } from "../badge";
interface Props extends KubeObjectDetailsProps<LimitRange> {
}
function renderLimit(limit: LimitRangeItem, part: LimitPart, resource: Resource) {
const resourceLimit = limit[part]?.[resource];
if (!resourceLimit) {
return null;
}
return <Badge label={`${part}:${resourceLimit}`}/>;
}
function renderResourceLimits(limit: LimitRangeItem, resource: Resource) {
return (
<React.Fragment key={limit.type + resource}>
{renderLimit(limit, LimitPart.MIN, resource)}
{renderLimit(limit, LimitPart.MAX, resource)}
{renderLimit(limit, LimitPart.DEFAULT, resource)}
{renderLimit(limit, LimitPart.DEFAULT_REQUEST, resource)}
{renderLimit(limit, LimitPart.MAX_LIMIT_REQUEST_RATIO, resource)}
</React.Fragment>
);
}
function renderLimitDetails(limits: LimitRangeItem[], resources: Resource[]) {
return resources.map(resource =>
<DrawerItem key={resource} name={resource}>
{
limits.map(limit =>
renderResourceLimits(limit, resource)
)
}
</DrawerItem>
);
}
@observer
export class LimitRangeDetails extends React.Component<Props> {
render() {
const { object: limitRange } = this.props;
if (!limitRange) return null;
const containerLimits = limitRange.getContainerLimits();
const podLimits = limitRange.getPodLimits();
const pvcLimits = limitRange.getPVCLimits();
return (
<div className="LimitRangeDetails">
<KubeObjectMeta object={limitRange}/>
{containerLimits.length > 0 &&
<DrawerItem name={`Container Limits`} labelsOnly>
{
renderLimitDetails(containerLimits, [Resource.CPU, Resource.MEMORY, Resource.EPHEMERAL_STORAGE])
}
</DrawerItem>
}
{podLimits.length > 0 &&
<DrawerItem name={`Pod Limits`} labelsOnly>
{
renderLimitDetails(podLimits, [Resource.CPU, Resource.MEMORY, Resource.EPHEMERAL_STORAGE])
}
</DrawerItem>
}
{pvcLimits.length > 0 &&
<DrawerItem name={`Persistent Volume Claim Limits`} labelsOnly>
{
renderLimitDetails(pvcLimits, [Resource.STORAGE])
}
</DrawerItem>
}
</div>
);
}
}
kubeObjectDetailRegistry.add({
kind: "LimitRange",
apiVersions: ["v1"],
components: {
Details: (props) => <LimitRangeDetails {...props} />
}
});

View File

@ -0,0 +1,11 @@
import type { RouteProps } from "react-router";
import { buildURL } from "../../../common/utils/buildUrl";
export const limitRangesRoute: RouteProps = {
path: "/limitranges"
};
export interface LimitRangeRouteParams {
}
export const limitRangeURL = buildURL<LimitRangeRouteParams>(limitRangesRoute.path);

View File

@ -0,0 +1,7 @@
.LimitRanges {
.TableCell {
&.warning {
@include table-cell-warning;
}
}
}

View File

@ -0,0 +1,12 @@
import { autobind } from "../../../common/utils/autobind";
import { KubeObjectStore } from "../../kube-object.store";
import { apiManager } from "../../api/api-manager";
import { LimitRange, limitRangeApi } from "../../api/endpoints/limit-range.api";
@autobind()
export class LimitRangesStore extends KubeObjectStore<LimitRange> {
api = limitRangeApi;
}
export const limitRangeStore = new LimitRangesStore();
apiManager.registerStore(limitRangeStore);

View File

@ -0,0 +1,53 @@
import "./limit-ranges.scss";
import { RouteComponentProps } from "react-router";
import { observer } from "mobx-react";
import { KubeObjectListLayout } from "../kube-object/kube-object-list-layout";
import { limitRangeStore } from "./limit-ranges.store";
import { LimitRangeRouteParams } from "./limit-ranges.route";
import React from "react";
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
import { LimitRange } from "../../api/endpoints/limit-range.api";
enum sortBy {
name = "name",
namespace = "namespace",
age = "age",
}
interface Props extends RouteComponentProps<LimitRangeRouteParams> {
}
@observer
export class LimitRanges extends React.Component<Props> {
render() {
return (
<KubeObjectListLayout
className="LimitRanges"
store={limitRangeStore}
sortingCallbacks={{
[sortBy.name]: (item: LimitRange) => item.getName(),
[sortBy.namespace]: (item: LimitRange) => item.getNs(),
[sortBy.age]: (item: LimitRange) => item.metadata.creationTimestamp,
}}
searchFilters={[
(item: LimitRange) => item.getName(),
(item: LimitRange) => item.getNs(),
]}
renderHeaderTitle={"Limit Ranges"}
renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name },
{ className: "warning" },
{ title: "Namespace", className: "namespace", sortBy: sortBy.namespace },
{ title: "Age", className: "age", sortBy: sortBy.age },
]}
renderTableContents={(limitRange: LimitRange) => [
limitRange.getName(),
<KubeObjectStatusIcon key="icon" object={limitRange}/>,
limitRange.getNs(),
limitRange.getAge(),
]}
/>
);
}
}

View File

@ -8,6 +8,7 @@ import { resourceQuotaRoute, ResourceQuotas, resourceQuotaURL } from "../+config
import { pdbRoute, pdbURL, PodDisruptionBudgets } from "../+config-pod-disruption-budgets";
import { HorizontalPodAutoscalers, hpaRoute, hpaURL } from "../+config-autoscalers";
import { isAllowedResource } from "../../../common/rbac";
import { LimitRanges, limitRangesRoute, limitRangeURL } from "../+config-limit-ranges";
@observer
export class Config extends React.Component {
@ -42,6 +43,15 @@ export class Config extends React.Component {
});
}
if (isAllowedResource("limitranges")) {
routes.push({
title: "Limit Ranges",
component: LimitRanges,
url: limitRangeURL({ query }),
routePath: limitRangesRoute.path.toString(),
});
}
if (isAllowedResource("horizontalpodautoscalers")) {
routes.push({
title: "HPA",

View File

@ -12,6 +12,7 @@ import { Spinner } from "../spinner";
import { resourceQuotaStore } from "../+config-resource-quotas/resource-quotas.store";
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
import { limitRangeStore } from "../+config-limit-ranges/limit-ranges.store";
interface Props extends KubeObjectDetailsProps<Namespace> {
}
@ -24,8 +25,15 @@ export class NamespaceDetails extends React.Component<Props> {
return resourceQuotaStore.getAllByNs(namespace);
}
@computed get limitranges() {
const namespace = this.props.object.getName();
return limitRangeStore.getAllByNs(namespace);
}
componentDidMount() {
resourceQuotaStore.loadAll();
limitRangeStore.loadAll();
}
render() {
@ -52,6 +60,16 @@ export class NamespaceDetails extends React.Component<Props> {
);
})}
</DrawerItem>
<DrawerItem name={`Limit Ranges`}>
{!this.limitranges && limitRangeStore.isLoading && <Spinner/>}
{this.limitranges.map(limitrange => {
return (
<Link key={limitrange.getId()} to={getDetailsUrl(limitrange.selfLink)}>
{limitrange.getName()}
</Link>
);
})}
</DrawerItem>
</div>
);
}

View File

@ -25,4 +25,5 @@ export const ResourceNames: Record<KubeResource, string> = {
"horizontalpodautoscalers": "Horizontal Pod Autoscalers",
"podsecuritypolicies": "Pod Security Policies",
"poddisruptionbudgets": "Pod Disruption Budgets",
"limitranges": "Limit Ranges",
};