diff --git a/src/common/rbac.ts b/src/common/rbac.ts index 667e49c9bc..363a94f3e2 100644 --- a/src/common/rbac.ts +++ b/src/common/rbac.ts @@ -4,7 +4,7 @@ export type KubeResource = "namespaces" | "nodes" | "events" | "resourcequotas" | "services" | "secrets" | "configmaps" | "ingresses" | "networkpolicies" | "persistentvolumes" | "storageclasses" | "pods" | "daemonsets" | "deployments" | "statefulsets" | "replicasets" | "jobs" | "cronjobs" | - "endpoints" | "customresourcedefinitions" | "horizontalpodautoscalers" | "podsecuritypolicies" + "endpoints" | "customresourcedefinitions" | "horizontalpodautoscalers" | "podsecuritypolicies" | "poddisruptionbudgets" export interface KubeApiResource { resource: KubeResource; // valid resource name @@ -28,6 +28,7 @@ export const apiResources: KubeApiResource[] = [ { resource: "nodes" }, { resource: "persistentvolumes" }, { resource: "pods" }, + { resource: "poddisruptionbudgets" }, { resource: "podsecuritypolicies" }, { resource: "resourcequotas" }, { resource: "secrets" }, diff --git a/src/renderer/api/endpoints/index.ts b/src/renderer/api/endpoints/index.ts index d723766f07..839c5bdd25 100644 --- a/src/renderer/api/endpoints/index.ts +++ b/src/renderer/api/endpoints/index.ts @@ -28,3 +28,4 @@ export * from "./storage-class.api" export * from "./pod-metrics.api" export * from "./podsecuritypolicy.api" export * from "./selfsubjectrulesreviews.api" +export * from "./poddisruptionbudget.api" diff --git a/src/renderer/api/endpoints/poddisruptionbudget.api.ts b/src/renderer/api/endpoints/poddisruptionbudget.api.ts new file mode 100644 index 0000000000..08c90ff60e --- /dev/null +++ b/src/renderer/api/endpoints/poddisruptionbudget.api.ts @@ -0,0 +1,49 @@ +import { autobind } from "../../utils"; +import { KubeObject } from "../kube-object"; +import { KubeApi } from "../kube-api"; + +@autobind() +export class PodDisruptionBudget extends KubeObject { + static kind = "PodDisruptionBudget"; + + spec: { + minAvailable: string; + maxUnavailable: string; + selector: { matchLabels: { [app: string]: string } }; + } + status: { + currentHealthy: number + desiredHealthy: number + disruptionsAllowed: number + expectedPods: number + } + + getSelectors() { + const selector = this.spec.selector; + return KubeObject.stringifyLabels(selector ? selector.matchLabels : null); + } + + getMinAvailable() { + return this.spec.minAvailable || "N/A"; + } + + getMaxUnavailable() { + return this.spec.maxUnavailable || "N/A"; + } + + getCurrentHealthy() { + return this.status.currentHealthy; + } + + getDesiredHealthy() { + return this.status.desiredHealthy; + } + +} + +export const pdbApi = new KubeApi({ + kind: PodDisruptionBudget.kind, + apiBase: "/apis/policy/v1beta1/poddisruptionbudgets", + isNamespaced: true, + objectConstructor: PodDisruptionBudget, +}); diff --git a/src/renderer/components/+config-pod-disruption-budgets/index.ts b/src/renderer/components/+config-pod-disruption-budgets/index.ts new file mode 100644 index 0000000000..e729464c0a --- /dev/null +++ b/src/renderer/components/+config-pod-disruption-budgets/index.ts @@ -0,0 +1,3 @@ +export * from "./pod-disruption-budgets.route" +export * from "./pod-disruption-budgets" +export * from "./pod-disruption-budgets-details" diff --git a/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets-details.scss b/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets-details.scss new file mode 100644 index 0000000000..39bb3b6b20 --- /dev/null +++ b/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets-details.scss @@ -0,0 +1,2 @@ +.PodDisruptionBudgetDetails { +} diff --git a/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets-details.tsx b/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets-details.tsx new file mode 100644 index 0000000000..a87d7c0138 --- /dev/null +++ b/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets-details.tsx @@ -0,0 +1,61 @@ +import "./pod-disruption-budgets-details.scss"; + +import React from "react"; +import { observer } from "mobx-react"; +import { Trans } from "@lingui/macro"; +import { DrawerItem, DrawerTitle } from "../drawer"; +import { Badge } from "../badge"; +import { Table, TableCell, TableHead, TableRow } from "../table"; +import { KubeObjectDetailsProps } from "../kube-object"; +import { PodDisruptionBudget, pdbApi } from "../../api/endpoints"; +import { apiManager } from "../../api/api-manager"; +import { KubeObjectStore } from "../../kube-object.store"; +import { KubeObjectMeta } from "../kube-object/kube-object-meta"; + +interface Props extends KubeObjectDetailsProps { +} + +@observer +export class PodDisruptionBudgetDetails extends React.Component { + + render() { + const { object: pdb } = this.props; + if (!pdb) return null + const { status, spec } = pdb + const selectors = pdb.getSelectors(); + return ( +
+ + + {selectors.length > 0 && + Selector} labelsOnly> + { + selectors.map(label => ) + } + + } + + Min Available}> + {pdb.getMinAvailable()} + + + Max Unavailable}> + {pdb.getMaxUnavailable()} + + + Current Healthy}> + {pdb.getCurrentHealthy()} + + + Desired Healthy}> + {pdb.getDesiredHealthy()} + + +
+ ) + } +} + +apiManager.registerViews(pdbApi, { + Details: PodDisruptionBudgetDetails, +}); diff --git a/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.route.ts b/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.route.ts new file mode 100644 index 0000000000..f7a3206f0f --- /dev/null +++ b/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.route.ts @@ -0,0 +1,11 @@ +import { RouteProps } from "react-router"; +import { buildURL } from "../../navigation"; + +export const pdbRoute: RouteProps = { + path: "/poddisruptionbudgets" +} + +export interface IPodDisruptionBudgetsRouteParams { +} + +export const pdbURL = buildURL(pdbRoute.path) diff --git a/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.scss b/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.scss new file mode 100644 index 0000000000..fa8c5ee2a4 --- /dev/null +++ b/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.scss @@ -0,0 +1,15 @@ +.PodDisruptionBudgets { + .TableCell { + &.name { + flex: 2; + } + + &.keys { + flex: 2.5; + } + + &.age { + flex: .5; + } + } +} diff --git a/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.store.ts b/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.store.ts new file mode 100644 index 0000000000..f25904119e --- /dev/null +++ b/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.store.ts @@ -0,0 +1,12 @@ +import { KubeObjectStore } from "../../kube-object.store"; +import { autobind } from "../../utils"; +import { PodDisruptionBudget, pdbApi } from "../../api/endpoints/poddisruptionbudget.api"; +import { apiManager } from "../../api/api-manager"; + +@autobind() +export class PodDisruptionBudgetsStore extends KubeObjectStore { + api = pdbApi +} + +export const podDisruptionBudgetsStore = new PodDisruptionBudgetsStore(); +apiManager.registerStore(pdbApi, podDisruptionBudgetsStore); diff --git a/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.tsx b/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.tsx new file mode 100644 index 0000000000..934d55e148 --- /dev/null +++ b/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.tsx @@ -0,0 +1,83 @@ +import "./pod-disruption-budgets.scss" + +import * as React from "react"; +import { observer } from "mobx-react"; +import { Trans } from "@lingui/macro"; +import { RouteComponentProps } from "react-router"; +import { podDisruptionBudgetsStore } from "./pod-disruption-budgets.store"; +import { PodDisruptionBudget, pdbApi } from "../../api/endpoints/poddisruptionbudget.api"; +import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object/kube-object-menu"; +import { KubeObjectDetailsProps, KubeObjectListLayout } from "../kube-object"; +import { IPodDisruptionBudgetsRouteParams } from "./pod-disruption-budgets.route"; +import { apiManager } from "../../api/api-manager"; + +enum sortBy { + name = "name", + namespace = "namespace", + minAvailable = "min-available", + maxUnavailable = "max-unavailable", + currentHealthy = "current-healthy", + desiredHealthy = "desired-healthy", + age = "age", +} + +interface Props extends KubeObjectDetailsProps { +} + +@observer +export class PodDisruptionBudgets extends React.Component { + render() { + return ( + pdb.getName(), + [sortBy.namespace]: (pdb: PodDisruptionBudget) => pdb.getNs(), + [sortBy.minAvailable]: (pdb: PodDisruptionBudget) => pdb.getMinAvailable(), + [sortBy.maxUnavailable]: (pdb: PodDisruptionBudget) => pdb.getMaxUnavailable(), + [sortBy.currentHealthy]: (pdb: PodDisruptionBudget) => pdb.getCurrentHealthy(), + [sortBy.desiredHealthy]: (pdb: PodDisruptionBudget) => pdb.getDesiredHealthy(), + [sortBy.age]: (pdb: PodDisruptionBudget) => pdb.getAge(), + }} + searchFilters={[ + (pdb: PodDisruptionBudget) => pdb.getSearchFields(), + ]} + renderHeaderTitle={Pod Disruption Budgets} + renderTableHeader={[ + { title: Name, className: "name", sortBy: sortBy.name }, + { title: Namespace, className: "namespace", sortBy: sortBy.namespace }, + { title: Min Available, className: "min-available", sortBy: sortBy.minAvailable }, + { title: Max Unavailable, className: "max-unavailable", sortBy: sortBy.maxUnavailable }, + { title: Current Healthy, className: "current-healthy", sortBy: sortBy.currentHealthy }, + { title: Desired Healthy, className: "desired-healthy", sortBy: sortBy.desiredHealthy }, + { title: Age, className: "age", sortBy: sortBy.age }, + ]} + renderTableContents={(pdb: PodDisruptionBudget) => { + return [ + pdb.getName(), + pdb.getNs(), + pdb.getMinAvailable(), + pdb.getMaxUnavailable(), + pdb.getCurrentHealthy(), + pdb.getDesiredHealthy(), + pdb.getAge(), + ] + }} + renderItemMenu={(pdb: PodDisruptionBudget) => { + return + }} + /> + ); + } +} + +export function PodDisruptionBudgetsMenu(props: KubeObjectMenuProps) { + return ( + + ) +} + +apiManager.registerViews(pdbApi, { + Menu: PodDisruptionBudgetsMenu, +}) diff --git a/src/renderer/components/+config/config.tsx b/src/renderer/components/+config/config.tsx index 5b6b240a00..8d6d81d5fa 100644 --- a/src/renderer/components/+config/config.tsx +++ b/src/renderer/components/+config/config.tsx @@ -7,6 +7,7 @@ import { ConfigMaps, configMapsRoute, configMapsURL } from "../+config-maps"; import { Secrets, secretsRoute, secretsURL } from "../+config-secrets"; import { namespaceStore } from "../+namespaces/namespace.store"; import { resourceQuotaRoute, ResourceQuotas, resourceQuotaURL } from "../+config-resource-quotas"; +import { PodDisruptionBudgets, pdbRoute, pdbURL } from "../+config-pod-disruption-budgets"; import { configURL } from "./config.route"; import { HorizontalPodAutoscalers, hpaRoute, hpaURL } from "../+config-autoscalers"; import { buildURL } from "../../navigation"; @@ -53,6 +54,14 @@ export class Config extends React.Component { path: hpaRoute.path, }) } + if (isAllowedResource("poddisruptionbudgets")) { + routes.push({ + title: Pod Disruption Budgets, + component: PodDisruptionBudgets, + url: pdbURL({ query }), + path: pdbRoute.path, + }) + } return routes; }