mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Full support for ReplicaSets (#1704)
Signed-off-by: vshakirova <vshakirova@mirantis.com>
This commit is contained in:
parent
d961d8b159
commit
d143b234b7
@ -273,6 +273,12 @@ describe("Lens integration tests", () => {
|
||||
expectedSelector: "h5.title",
|
||||
expectedText: "Stateful Sets"
|
||||
},
|
||||
{
|
||||
name: "ReplicaSets",
|
||||
href: "replicasets",
|
||||
expectedSelector: "h5.title",
|
||||
expectedText: "Replica Sets"
|
||||
},
|
||||
{
|
||||
name: "Jobs",
|
||||
href: "jobs",
|
||||
|
||||
@ -223,6 +223,7 @@ msgstr "Affinities"
|
||||
#: src/renderer/components/+workloads-pods/pods.tsx:78
|
||||
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:51
|
||||
#: src/renderer/components/+workloads-statefulsets/statefulsets.tsx:41
|
||||
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:56
|
||||
msgid "Age"
|
||||
msgstr "Age"
|
||||
|
||||
@ -766,6 +767,10 @@ msgstr "Cron Jobs"
|
||||
msgid "CronJobs"
|
||||
msgstr "CronJobs"
|
||||
|
||||
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:54
|
||||
msgid "Current"
|
||||
msgstr "Current"
|
||||
|
||||
#: src/renderer/components/+config-autoscalers/hpa-details.tsx:50
|
||||
msgid "Current / Target"
|
||||
msgstr "Current / Target"
|
||||
@ -777,6 +782,7 @@ msgstr "Current Healthy"
|
||||
|
||||
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:101
|
||||
#: src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx:103
|
||||
#: src/renderer/components/+workloads-replicasets/replicaset-scale-dialog.tsx:104
|
||||
msgid "Current replica scale: {currentReplicas}"
|
||||
msgstr "Current replica scale: {currentReplicas}"
|
||||
|
||||
@ -861,6 +867,10 @@ msgstr "Deployments"
|
||||
msgid "Description"
|
||||
msgstr "Description"
|
||||
|
||||
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:53
|
||||
msgid "Desired"
|
||||
msgstr "Desired"
|
||||
|
||||
#: src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets-details.tsx:42
|
||||
#: src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.tsx:41
|
||||
msgid "Desired Healthy"
|
||||
@ -868,6 +878,7 @@ msgstr "Desired Healthy"
|
||||
|
||||
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:105
|
||||
#: src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx:107
|
||||
#: src/renderer/components/+workloads-replicasets/replicaset-scale-dialog.tsx:108
|
||||
msgid "Desired number of replicas"
|
||||
msgstr "Desired number of replicas"
|
||||
|
||||
@ -1132,6 +1143,7 @@ msgstr "Hide"
|
||||
|
||||
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:127
|
||||
#: src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx:116
|
||||
#: src/renderer/components/+workloads-replicasets/replicaset-scale-dialog.tsx:131
|
||||
msgid "High number of replicas may cause cluster performance issues"
|
||||
msgstr "High number of replicas may cause cluster performance issues"
|
||||
|
||||
@ -1583,6 +1595,7 @@ msgstr "Mounts"
|
||||
#: src/renderer/components/+workspaces/workspaces.tsx:135
|
||||
#: src/renderer/components/dock/edit-resource.tsx:89
|
||||
#: src/renderer/components/kube-object/kube-object-meta.tsx:20
|
||||
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:50
|
||||
msgid "Name"
|
||||
msgstr "Name"
|
||||
|
||||
@ -1632,6 +1645,7 @@ msgstr "Names"
|
||||
#: src/renderer/components/item-object-list/page-filters-select.tsx:57
|
||||
#: src/renderer/components/kube-object/kube-object-meta.tsx:23
|
||||
#: src/renderer/components/+workloads-pods/pod-details-list.tsx:144
|
||||
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:52
|
||||
msgid "Namespace"
|
||||
msgstr "Namespace"
|
||||
|
||||
@ -2041,6 +2055,7 @@ msgstr "Read-only Root Filesystem"
|
||||
msgid "Readiness"
|
||||
msgstr "Readiness"
|
||||
|
||||
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:55
|
||||
#: src/renderer/components/+workloads-pods/pod-details-list.tsx:145
|
||||
msgid "Ready"
|
||||
msgstr "Ready"
|
||||
@ -2156,6 +2171,10 @@ msgstr "Removing helm branch <0>{0}</0> has failed: {1}"
|
||||
msgid "Replicas"
|
||||
msgstr "Replicas"
|
||||
|
||||
#: src/renderer/components/+workloads/workloads.tsx:70
|
||||
msgid "ReplicaSets"
|
||||
msgstr "ReplicaSets"
|
||||
|
||||
#: src/renderer/components/dock/install-chart.tsx:119
|
||||
msgid "Repo/Name"
|
||||
msgstr "Repo/Name"
|
||||
@ -2347,6 +2366,7 @@ msgstr "Save"
|
||||
#: src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx:128
|
||||
#: src/renderer/components/+workloads-deployments/deployments.tsx:83
|
||||
#: src/renderer/components/+workloads-deployments/deployments.tsx:84
|
||||
#: src/renderer/components/+workloads-replicasets/replicaset-scale-dialog.tsx:160
|
||||
msgid "Scale"
|
||||
msgstr "Scale"
|
||||
|
||||
@ -2354,6 +2374,10 @@ msgstr "Scale"
|
||||
msgid "Scale Deployment <0>{deploymentName}</0>"
|
||||
msgstr "Scale Deployment <0>{deploymentName}</0>"
|
||||
|
||||
#: src/renderer/components/+workloads-replicasets/replicaset-scale-dialog.tsx:143
|
||||
msgid "Scale Replica Set <0>{replicaSetName}</0>"
|
||||
msgstr "Scale Replica Set <0>{replicaSetName}</0>"
|
||||
|
||||
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:139
|
||||
msgid "Scale Stateful Set <0>{statefulSetName}</0>"
|
||||
msgstr "Scale Stateful Set <0>{statefulSetName}</0>"
|
||||
|
||||
@ -222,6 +222,7 @@ msgstr ""
|
||||
#: src/renderer/components/+workloads-pods/pods.tsx:78
|
||||
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:51
|
||||
#: src/renderer/components/+workloads-statefulsets/statefulsets.tsx:41
|
||||
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:56
|
||||
msgid "Age"
|
||||
msgstr ""
|
||||
|
||||
@ -761,6 +762,10 @@ msgstr ""
|
||||
msgid "CronJobs"
|
||||
msgstr ""
|
||||
|
||||
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:54
|
||||
msgid "Current"
|
||||
msgstr ""
|
||||
|
||||
#: src/renderer/components/+config-autoscalers/hpa-details.tsx:50
|
||||
msgid "Current / Target"
|
||||
msgstr ""
|
||||
@ -772,6 +777,7 @@ msgstr ""
|
||||
|
||||
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:101
|
||||
#: src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx:103
|
||||
#: src/renderer/components/+workloads-replicasets/replicaset-scale-dialog.tsx:104
|
||||
msgid "Current replica scale: {currentReplicas}"
|
||||
msgstr ""
|
||||
|
||||
@ -856,6 +862,10 @@ msgstr ""
|
||||
msgid "Description"
|
||||
msgstr ""
|
||||
|
||||
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:53
|
||||
msgid "Desired"
|
||||
msgstr ""
|
||||
|
||||
#: src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets-details.tsx:42
|
||||
#: src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.tsx:41
|
||||
msgid "Desired Healthy"
|
||||
@ -863,6 +873,7 @@ msgstr ""
|
||||
|
||||
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:105
|
||||
#: src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx:107
|
||||
#: src/renderer/components/+workloads-replicasets/replicaset-scale-dialog.tsx:108
|
||||
msgid "Desired number of replicas"
|
||||
msgstr ""
|
||||
|
||||
@ -1122,6 +1133,7 @@ msgstr ""
|
||||
|
||||
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:127
|
||||
#: src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx:116
|
||||
#: src/renderer/components/+workloads-replicaset/replicaset-scale-dialog.tsx:131
|
||||
msgid "High number of replicas may cause cluster performance issues"
|
||||
msgstr ""
|
||||
|
||||
@ -1573,6 +1585,7 @@ msgstr ""
|
||||
#: src/renderer/components/+workspaces/workspaces.tsx:135
|
||||
#: src/renderer/components/dock/edit-resource.tsx:89
|
||||
#: src/renderer/components/kube-object/kube-object-meta.tsx:20
|
||||
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:50
|
||||
msgid "Name"
|
||||
msgstr ""
|
||||
|
||||
@ -1622,6 +1635,7 @@ msgstr ""
|
||||
#: src/renderer/components/item-object-list/page-filters-select.tsx:57
|
||||
#: src/renderer/components/kube-object/kube-object-meta.tsx:23
|
||||
#: src/renderer/components/+workloads-pods/pod-details-list.tsx:144
|
||||
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:52
|
||||
msgid "Namespace"
|
||||
msgstr ""
|
||||
|
||||
@ -2023,6 +2037,7 @@ msgstr ""
|
||||
msgid "Readiness"
|
||||
msgstr ""
|
||||
|
||||
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:55
|
||||
#: src/renderer/components/+workloads-pods/pod-details-list.tsx:145
|
||||
msgid "Ready"
|
||||
msgstr ""
|
||||
@ -2138,6 +2153,10 @@ msgstr ""
|
||||
msgid "Replicas"
|
||||
msgstr ""
|
||||
|
||||
#: src/renderer/components/+workloads/workloads.tsx:70
|
||||
msgid "ReplicaSets"
|
||||
msgstr ""
|
||||
|
||||
#: src/renderer/components/dock/install-chart.tsx:119
|
||||
msgid "Repo/Name"
|
||||
msgstr ""
|
||||
@ -2329,6 +2348,7 @@ msgstr ""
|
||||
#: src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx:128
|
||||
#: src/renderer/components/+workloads-deployments/deployments.tsx:83
|
||||
#: src/renderer/components/+workloads-deployments/deployments.tsx:84
|
||||
#: src/renderer/components/+workloads-replicasets/replicaset-scale-dialog.tsx:160
|
||||
msgid "Scale"
|
||||
msgstr ""
|
||||
|
||||
@ -2336,6 +2356,10 @@ msgstr ""
|
||||
msgid "Scale Deployment <0>{deploymentName}</0>"
|
||||
msgstr ""
|
||||
|
||||
#: src/renderer/components/+workloads-replicasets/replicaset-scale-dialog.tsx:143
|
||||
msgid "Scale Replica Set <0>{replicaSetName}</0>"
|
||||
msgstr ""
|
||||
|
||||
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:139
|
||||
msgid "Scale Stateful Set <0>{statefulSetName}</0>"
|
||||
msgstr ""
|
||||
|
||||
@ -223,6 +223,7 @@ msgstr "Аффинитеты"
|
||||
#: src/renderer/components/+workloads-pods/pods.tsx:78
|
||||
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:51
|
||||
#: src/renderer/components/+workloads-statefulsets/statefulsets.tsx:41
|
||||
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:56
|
||||
msgid "Age"
|
||||
msgstr "Возраст"
|
||||
|
||||
@ -766,6 +767,10 @@ msgstr ""
|
||||
msgid "CronJobs"
|
||||
msgstr "CronJobs"
|
||||
|
||||
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:54
|
||||
msgid "Current"
|
||||
msgstr "Текущее"
|
||||
|
||||
#: src/renderer/components/+config-autoscalers/hpa-details.tsx:50
|
||||
msgid "Current / Target"
|
||||
msgstr "Текущее / Цель"
|
||||
@ -777,6 +782,7 @@ msgstr ""
|
||||
|
||||
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:101
|
||||
#: src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx:103
|
||||
#: src/renderer/components/+workloads-replicasets/replicaset-scale-dialog.tsx:104
|
||||
msgid "Current replica scale: {currentReplicas}"
|
||||
msgstr "Текущий размер реплики: {currentReplicas}"
|
||||
|
||||
@ -861,6 +867,10 @@ msgstr "Deployments"
|
||||
msgid "Description"
|
||||
msgstr "Описание"
|
||||
|
||||
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:53
|
||||
msgid "Desired"
|
||||
msgstr "Желаемое"
|
||||
|
||||
#: src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets-details.tsx:42
|
||||
#: src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.tsx:41
|
||||
msgid "Desired Healthy"
|
||||
@ -868,6 +878,7 @@ msgstr ""
|
||||
|
||||
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:105
|
||||
#: src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx:107
|
||||
#: src/renderer/components/+workloads-replicasets/replicaset-scale-dialog.tsx:108
|
||||
msgid "Desired number of replicas"
|
||||
msgstr "Нужный уровень реплик"
|
||||
|
||||
@ -1132,6 +1143,7 @@ msgstr "Скрыть"
|
||||
|
||||
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:127
|
||||
#: src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx:116
|
||||
#: src/renderer/components/+workloads-replicasets/replicaset-scale-dialog.tsx:131
|
||||
msgid "High number of replicas may cause cluster performance issues"
|
||||
msgstr "Большое количество реплик может вызвать проблемы с производительностью кластера"
|
||||
|
||||
@ -1583,6 +1595,7 @@ msgstr "Установки"
|
||||
#: src/renderer/components/+workspaces/workspaces.tsx:135
|
||||
#: src/renderer/components/dock/edit-resource.tsx:89
|
||||
#: src/renderer/components/kube-object/kube-object-meta.tsx:20
|
||||
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:50
|
||||
msgid "Name"
|
||||
msgstr "Имя"
|
||||
|
||||
@ -1632,6 +1645,7 @@ msgstr ""
|
||||
#: src/renderer/components/item-object-list/page-filters-select.tsx:57
|
||||
#: src/renderer/components/kube-object/kube-object-meta.tsx:23
|
||||
#: src/renderer/components/+workloads-pods/pod-details-list.tsx:144
|
||||
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:52
|
||||
msgid "Namespace"
|
||||
msgstr "Namespace"
|
||||
|
||||
@ -2041,6 +2055,7 @@ msgstr ""
|
||||
msgid "Readiness"
|
||||
msgstr "Готовность"
|
||||
|
||||
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:55
|
||||
#: src/renderer/components/+workloads-pods/pod-details-list.tsx:145
|
||||
msgid "Ready"
|
||||
msgstr "Готовы"
|
||||
@ -2156,6 +2171,10 @@ msgstr ""
|
||||
msgid "Replicas"
|
||||
msgstr "Реплики"
|
||||
|
||||
#: src/renderer/components/+workloads/workloads.tsx:70
|
||||
msgid "ReplicaSets"
|
||||
msgstr "ReplicaSets"
|
||||
|
||||
#: src/renderer/components/dock/install-chart.tsx:119
|
||||
msgid "Repo/Name"
|
||||
msgstr "Репозиторий/Имя"
|
||||
@ -2347,6 +2366,7 @@ msgstr "Сохранить"
|
||||
#: src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx:128
|
||||
#: src/renderer/components/+workloads-deployments/deployments.tsx:83
|
||||
#: src/renderer/components/+workloads-deployments/deployments.tsx:84
|
||||
#: src/renderer/components/+workloads-replicasets/replicaset-scale-dialog.tsx:160
|
||||
msgid "Scale"
|
||||
msgstr "Масштабировать"
|
||||
|
||||
@ -2354,6 +2374,10 @@ msgstr "Масштабировать"
|
||||
msgid "Scale Deployment <0>{deploymentName}</0>"
|
||||
msgstr "Масштабировать Deployment <0>{deploymentName}</0>"
|
||||
|
||||
#: src/renderer/components/+workloads-replicasets/replicaset-scale-dialog.tsx:143
|
||||
msgid "Scale Replica Set <0>{replicaSetName}</0>"
|
||||
msgstr "Масштабировать Replica Set <0>{replicaSetName}</0>"
|
||||
|
||||
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:139
|
||||
msgid "Scale Stateful Set <0>{statefulSetName}</0>"
|
||||
msgstr "Масштабировать Stateful Set <0>{statefulSetName}</0>"
|
||||
|
||||
@ -31,6 +31,7 @@ export const apiResources: KubeApiResource[] = [
|
||||
{ resource: "poddisruptionbudgets" },
|
||||
{ resource: "podsecuritypolicies" },
|
||||
{ resource: "resourcequotas" },
|
||||
{ resource: "replicasets", group: "apps" },
|
||||
{ resource: "secrets" },
|
||||
{ resource: "services" },
|
||||
{ resource: "statefulsets", group: "apps" },
|
||||
|
||||
@ -67,7 +67,7 @@ export interface IPodContainer {
|
||||
image: string;
|
||||
command?: string[];
|
||||
args?: string[];
|
||||
ports: {
|
||||
ports?: {
|
||||
name?: string;
|
||||
containerPort: number;
|
||||
protocol: string;
|
||||
@ -137,7 +137,7 @@ interface IContainerProbe {
|
||||
|
||||
export interface IPodContainerStatus {
|
||||
name: string;
|
||||
state: {
|
||||
state?: {
|
||||
[index: string]: object;
|
||||
running?: {
|
||||
startedAt: string;
|
||||
@ -153,21 +153,28 @@ export interface IPodContainerStatus {
|
||||
reason: string;
|
||||
};
|
||||
};
|
||||
lastState: {
|
||||
lastState?: {
|
||||
[index: string]: object;
|
||||
running?: {
|
||||
startedAt: string;
|
||||
};
|
||||
waiting?: {
|
||||
reason: string;
|
||||
message: string;
|
||||
};
|
||||
terminated?: {
|
||||
startedAt: string;
|
||||
finishedAt: string;
|
||||
exitCode: number;
|
||||
reason: string;
|
||||
containerID: string;
|
||||
};
|
||||
};
|
||||
ready: boolean;
|
||||
restartCount: number;
|
||||
image: string;
|
||||
imageID: string;
|
||||
containerID: string;
|
||||
containerID?: string;
|
||||
started?: boolean;
|
||||
}
|
||||
|
||||
@autobind()
|
||||
@ -196,28 +203,44 @@ export class Pod extends WorkloadKubeObject {
|
||||
}[];
|
||||
initContainers: IPodContainer[];
|
||||
containers: IPodContainer[];
|
||||
restartPolicy: string;
|
||||
terminationGracePeriodSeconds: number;
|
||||
dnsPolicy: string;
|
||||
restartPolicy?: string;
|
||||
terminationGracePeriodSeconds?: number;
|
||||
activeDeadlineSeconds?: number;
|
||||
dnsPolicy?: string;
|
||||
serviceAccountName: string;
|
||||
serviceAccount: string;
|
||||
priority: number;
|
||||
priorityClassName: string;
|
||||
nodeName: string;
|
||||
automountServiceAccountToken?: boolean;
|
||||
priority?: number;
|
||||
priorityClassName?: string;
|
||||
nodeName?: string;
|
||||
nodeSelector?: {
|
||||
[selector: string]: string;
|
||||
};
|
||||
securityContext: {};
|
||||
schedulerName: string;
|
||||
tolerations: {
|
||||
key: string;
|
||||
operator: string;
|
||||
effect: string;
|
||||
tolerationSeconds: number;
|
||||
securityContext?: {};
|
||||
imagePullSecrets?: {
|
||||
name: string;
|
||||
}[];
|
||||
affinity: IAffinity;
|
||||
hostNetwork?: boolean;
|
||||
hostPID?: boolean;
|
||||
hostIPC?: boolean;
|
||||
shareProcessNamespace?: boolean;
|
||||
hostname?: string;
|
||||
subdomain?: string;
|
||||
schedulerName?: string;
|
||||
tolerations?: {
|
||||
key?: string;
|
||||
operator?: string;
|
||||
effect?: string;
|
||||
tolerationSeconds?: number;
|
||||
value?: string;
|
||||
}[];
|
||||
hostAliases?: {
|
||||
ip: string;
|
||||
hostnames: string[];
|
||||
};
|
||||
status: {
|
||||
affinity?: IAffinity;
|
||||
};
|
||||
status?: {
|
||||
phase: string;
|
||||
conditions: {
|
||||
type: string;
|
||||
@ -230,7 +253,7 @@ export class Pod extends WorkloadKubeObject {
|
||||
startTime: string;
|
||||
initContainerStatuses?: IPodContainerStatus[];
|
||||
containerStatuses?: IPodContainerStatus[];
|
||||
qosClass: string;
|
||||
qosClass?: string;
|
||||
reason?: string;
|
||||
};
|
||||
|
||||
|
||||
@ -1,51 +1,78 @@
|
||||
import get from "lodash/get";
|
||||
import { autobind } from "../../utils";
|
||||
import { IAffinity, WorkloadKubeObject } from "../workload-kube-object";
|
||||
import { IPodContainer } from "./pods.api";
|
||||
import { WorkloadKubeObject } from "../workload-kube-object";
|
||||
import { IPodContainer, Pod } from "./pods.api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
|
||||
export class ReplicaSetApi extends KubeApi<ReplicaSet> {
|
||||
protected getScaleApiUrl(params: { namespace: string; name: string }) {
|
||||
return `${this.getUrl(params)}/scale`;
|
||||
}
|
||||
|
||||
getReplicas(params: { namespace: string; name: string }): Promise<number> {
|
||||
return this.request
|
||||
.get(this.getScaleApiUrl(params))
|
||||
.then(({ status }: any) => status?.replicas);
|
||||
}
|
||||
|
||||
scale(params: { namespace: string; name: string }, replicas: number) {
|
||||
return this.request.put(this.getScaleApiUrl(params), {
|
||||
data: {
|
||||
metadata: params,
|
||||
spec: {
|
||||
replicas
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@autobind()
|
||||
export class ReplicaSet extends WorkloadKubeObject {
|
||||
static kind = "ReplicaSet";
|
||||
static namespaced = true;
|
||||
static apiBase = "/apis/apps/v1/replicasets";
|
||||
|
||||
spec: {
|
||||
replicas?: number;
|
||||
selector?: {
|
||||
matchLabels: {
|
||||
[key: string]: string;
|
||||
};
|
||||
};
|
||||
containers?: IPodContainer[];
|
||||
selector: { matchLabels: { [app: string]: string } };
|
||||
template?: {
|
||||
spec?: {
|
||||
affinity?: IAffinity;
|
||||
nodeSelector?: {
|
||||
[selector: string]: string;
|
||||
};
|
||||
tolerations: {
|
||||
key: string;
|
||||
operator: string;
|
||||
effect: string;
|
||||
tolerationSeconds: number;
|
||||
}[];
|
||||
containers: IPodContainer[];
|
||||
metadata: {
|
||||
labels: {
|
||||
app: string;
|
||||
};
|
||||
};
|
||||
restartPolicy?: string;
|
||||
terminationGracePeriodSeconds?: number;
|
||||
dnsPolicy?: string;
|
||||
schedulerName?: string;
|
||||
spec?: Pod["spec"];
|
||||
};
|
||||
minReadySeconds?: number;
|
||||
};
|
||||
status: {
|
||||
replicas: number;
|
||||
fullyLabeledReplicas: number;
|
||||
readyReplicas: number;
|
||||
availableReplicas: number;
|
||||
observedGeneration: number;
|
||||
fullyLabeledReplicas?: number;
|
||||
readyReplicas?: number;
|
||||
availableReplicas?: number;
|
||||
observedGeneration?: number;
|
||||
conditions?: {
|
||||
type: string;
|
||||
status: string;
|
||||
lastUpdateTime: string;
|
||||
lastTransitionTime: string;
|
||||
reason: string;
|
||||
message: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
getDesired() {
|
||||
return this.spec.replicas || 0;
|
||||
}
|
||||
|
||||
getCurrent() {
|
||||
return this.status.availableReplicas || 0;
|
||||
}
|
||||
|
||||
getReady() {
|
||||
return this.status.readyReplicas || 0;
|
||||
}
|
||||
|
||||
getImages() {
|
||||
const containers: IPodContainer[] = get(this, "spec.template.spec.containers", []);
|
||||
|
||||
@ -53,6 +80,6 @@ export class ReplicaSet extends WorkloadKubeObject {
|
||||
}
|
||||
}
|
||||
|
||||
export const replicaSetApi = new KubeApi({
|
||||
export const replicaSetApi = new ReplicaSetApi({
|
||||
objectConstructor: ReplicaSet,
|
||||
});
|
||||
|
||||
@ -11,7 +11,6 @@ import { cssNames } from "../../utils";
|
||||
import { PodDetailsTolerations } from "../+workloads-pods/pod-details-tolerations";
|
||||
import { PodDetailsAffinities } from "../+workloads-pods/pod-details-affinities";
|
||||
import { KubeEventDetails } from "../+events/kube-event-details";
|
||||
import { replicaSetStore } from "../+workloads-replicasets/replicasets.store";
|
||||
import { podsStore } from "../+workloads-pods/pods.store";
|
||||
import { KubeObjectDetailsProps } from "../kube-object";
|
||||
import { _i18n } from "../../i18n";
|
||||
@ -20,7 +19,6 @@ import { deploymentStore } from "./deployments.store";
|
||||
import { PodCharts, podMetricTabs } from "../+workloads-pods/pod-charts";
|
||||
import { reaction } from "mobx";
|
||||
import { PodDetailsList } from "../+workloads-pods/pod-details-list";
|
||||
import { ReplicaSets } from "../+workloads-replicasets";
|
||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||
|
||||
@ -38,10 +36,6 @@ export class DeploymentDetails extends React.Component<Props> {
|
||||
if (!podsStore.isLoaded) {
|
||||
podsStore.loadAll();
|
||||
}
|
||||
|
||||
if (!replicaSetStore.isLoaded) {
|
||||
replicaSetStore.loadAll();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@ -56,7 +50,6 @@ export class DeploymentDetails extends React.Component<Props> {
|
||||
const nodeSelector = deployment.getNodeSelectors();
|
||||
const selectors = deployment.getSelectors();
|
||||
const childPods = deploymentStore.getChildPods(deployment);
|
||||
const replicaSets = replicaSetStore.getReplicaSetsByOwner(deployment);
|
||||
const metrics = deploymentStore.metrics;
|
||||
|
||||
return (
|
||||
@ -118,7 +111,6 @@ export class DeploymentDetails extends React.Component<Props> {
|
||||
<PodDetailsTolerations workload={deployment}/>
|
||||
<PodDetailsAffinities workload={deployment}/>
|
||||
<ResourceMetricsText metrics={metrics}/>
|
||||
<ReplicaSets replicaSets={replicaSets}/>
|
||||
<PodDetailsList pods={childPods} owner={deployment}/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
|
||||
.workloads {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, 155px);
|
||||
grid-template-columns: repeat(auto-fit, 180px);
|
||||
justify-content: space-evenly;
|
||||
grid-gap: $margin;
|
||||
padding: $padding * 2;
|
||||
|
||||
@ -18,6 +18,7 @@ const resources: KubeResource[] = [
|
||||
"deployments",
|
||||
"statefulsets",
|
||||
"daemonsets",
|
||||
"replicasets",
|
||||
"jobs",
|
||||
"cronjobs",
|
||||
];
|
||||
|
||||
@ -0,0 +1,49 @@
|
||||
.ReplicaSetScaleDialog {
|
||||
.Wizard {
|
||||
.header {
|
||||
span {
|
||||
color: #a0a0a0;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.WizardStep {
|
||||
.step-content {
|
||||
min-height: 90px;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.current-scale {
|
||||
font-weight: bold
|
||||
}
|
||||
|
||||
.desired-scale {
|
||||
flex: 1.1 0;
|
||||
}
|
||||
|
||||
.slider-container {
|
||||
flex: 1 0;
|
||||
}
|
||||
|
||||
.plus-minus-container {
|
||||
margin-left: $margin * 2;
|
||||
.Icon {
|
||||
--color-active: black;
|
||||
}
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: $colorSoftError;
|
||||
font-size: small;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.Icon {
|
||||
margin: 0;
|
||||
margin-right: $margin;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
167
src/renderer/components/+workloads-replicasets/replicaset-scale-dialog.test.tsx
Executable file
167
src/renderer/components/+workloads-replicasets/replicaset-scale-dialog.test.tsx
Executable file
@ -0,0 +1,167 @@
|
||||
import "@testing-library/jest-dom/extend-expect";
|
||||
|
||||
jest.mock("../../api/endpoints");
|
||||
import { ReplicaSetScaleDialog } from "./replicaset-scale-dialog";
|
||||
import { render, waitFor, fireEvent } from "@testing-library/react";
|
||||
import React from "react";
|
||||
import { replicaSetApi } from "../../api/endpoints/replica-set.api";
|
||||
|
||||
const dummyReplicaSet = {
|
||||
apiVersion: "v1",
|
||||
kind: "dummy",
|
||||
metadata: {
|
||||
uid: "dummy",
|
||||
name: "dummy",
|
||||
creationTimestamp: "dummy",
|
||||
resourceVersion: "dummy",
|
||||
selfLink: "link",
|
||||
},
|
||||
selfLink: "link",
|
||||
spec: {
|
||||
replicas: 1,
|
||||
selector: {
|
||||
matchLabels: { "label": "label" }
|
||||
},
|
||||
template: {
|
||||
metadata: {
|
||||
labels: {
|
||||
app: "label",
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
containers: [{
|
||||
name: "dummy",
|
||||
image: "dummy",
|
||||
imagePullPolicy: "dummy",
|
||||
}],
|
||||
initContainers: [{
|
||||
name: "dummy",
|
||||
image: "dummy",
|
||||
imagePullPolicy: "dummy",
|
||||
}],
|
||||
priority: 1,
|
||||
serviceAccountName: "dummy",
|
||||
serviceAccount: "dummy",
|
||||
securityContext: {},
|
||||
schedulerName: "dummy",
|
||||
},
|
||||
},
|
||||
minReadySeconds: 1,
|
||||
},
|
||||
status: {
|
||||
replicas: 1,
|
||||
fullyLabeledReplicas: 1,
|
||||
readyReplicas: 1,
|
||||
availableReplicas: 1,
|
||||
observedGeneration: 1,
|
||||
conditions: [{
|
||||
type: "dummy",
|
||||
status: "dummy",
|
||||
lastUpdateTime: "dummy",
|
||||
lastTransitionTime: "dummy",
|
||||
reason: "dummy",
|
||||
message: "dummy",
|
||||
}],
|
||||
},
|
||||
getDesired: jest.fn(),
|
||||
getCurrent: jest.fn(),
|
||||
getReady: jest.fn(),
|
||||
getImages: jest.fn(),
|
||||
getReplicas: jest.fn(),
|
||||
getSelectors: jest.fn(),
|
||||
getTemplateLabels: jest.fn(),
|
||||
getAffinity: jest.fn(),
|
||||
getTolerations: jest.fn(),
|
||||
getNodeSelectors: jest.fn(),
|
||||
getAffinityNumber: jest.fn(),
|
||||
getId: jest.fn(),
|
||||
getResourceVersion: jest.fn(),
|
||||
getName: jest.fn(),
|
||||
getNs: jest.fn(),
|
||||
getAge: jest.fn(),
|
||||
getFinalizers: jest.fn(),
|
||||
getLabels: jest.fn(),
|
||||
getAnnotations: jest.fn(),
|
||||
getOwnerRefs: jest.fn(),
|
||||
getSearchFields: jest.fn(),
|
||||
toPlainObject: jest.fn(),
|
||||
update: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
};
|
||||
|
||||
describe("<ReplicaSetScaleDialog />", () => {
|
||||
it("renders w/o errors", () => {
|
||||
const { container } = render(<ReplicaSetScaleDialog/>);
|
||||
|
||||
expect(container).toBeInstanceOf(HTMLElement);
|
||||
});
|
||||
|
||||
it("init with a dummy replica set and mocked current/desired scale", async () => {
|
||||
// mock replicaSetApi.getReplicas() which will be called
|
||||
// when <ReplicaSetScaleDialog /> rendered.
|
||||
const initReplicas = 1;
|
||||
|
||||
replicaSetApi.getReplicas = jest.fn().mockImplementationOnce(async () => initReplicas);
|
||||
const { getByTestId } = render(<ReplicaSetScaleDialog/>);
|
||||
|
||||
ReplicaSetScaleDialog.open(dummyReplicaSet);
|
||||
// we need to wait for the replicaSetScaleDialog to show up
|
||||
// because there is an <Animate /> in <Dialog /> which renders null at start.
|
||||
await waitFor(async () => {
|
||||
const [currentScale, desiredScale] = await Promise.all([
|
||||
getByTestId("current-scale"),
|
||||
getByTestId("desired-scale"),
|
||||
]);
|
||||
|
||||
expect(currentScale).toHaveTextContent(`${initReplicas}`);
|
||||
expect(desiredScale).toHaveTextContent(`${initReplicas}`);
|
||||
});
|
||||
});
|
||||
|
||||
it("changes the desired scale when clicking the icon buttons +/-", async () => {
|
||||
const initReplicas = 1;
|
||||
|
||||
replicaSetApi.getReplicas = jest.fn().mockImplementationOnce(async () => initReplicas);
|
||||
const component = render(<ReplicaSetScaleDialog/>);
|
||||
|
||||
ReplicaSetScaleDialog.open(dummyReplicaSet);
|
||||
await waitFor(async () => {
|
||||
expect(await component.findByTestId("desired-scale")).toHaveTextContent(`${initReplicas}`);
|
||||
expect(await component.findByTestId("current-scale")).toHaveTextContent(`${initReplicas}`);
|
||||
expect((await component.baseElement.querySelector("input").value)).toBe(`${initReplicas}`);
|
||||
});
|
||||
|
||||
const up = await component.findByTestId("desired-replicas-up");
|
||||
const down = await component.findByTestId("desired-replicas-down");
|
||||
|
||||
fireEvent.click(up);
|
||||
expect(await component.findByTestId("desired-scale")).toHaveTextContent(`${initReplicas + 1}`);
|
||||
expect(await component.findByTestId("current-scale")).toHaveTextContent(`${initReplicas}`);
|
||||
expect((await component.baseElement.querySelector("input").value)).toBe(`${initReplicas + 1}`);
|
||||
|
||||
fireEvent.click(down);
|
||||
expect(await component.findByTestId("desired-scale")).toHaveTextContent(`${initReplicas}`);
|
||||
expect(await component.findByTestId("current-scale")).toHaveTextContent(`${initReplicas}`);
|
||||
expect((await component.baseElement.querySelector("input").value)).toBe(`${initReplicas}`);
|
||||
|
||||
// edge case, desiredScale must >= 0
|
||||
let times = 10;
|
||||
|
||||
for (let i = 0; i < times; i++) {
|
||||
fireEvent.click(down);
|
||||
}
|
||||
expect(await component.findByTestId("desired-scale")).toHaveTextContent("0");
|
||||
expect((await component.baseElement.querySelector("input").value)).toBe("0");
|
||||
|
||||
// edge case, desiredScale must <= scaleMax (100)
|
||||
times = 120;
|
||||
|
||||
for (let i = 0; i < times; i++) {
|
||||
fireEvent.click(up);
|
||||
}
|
||||
expect(await component.findByTestId("desired-scale")).toHaveTextContent("100");
|
||||
expect((component.baseElement.querySelector("input").value)).toBe("100");
|
||||
expect(await component.findByTestId("warning"))
|
||||
.toHaveTextContent("High number of replicas may cause cluster performance issues");
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,169 @@
|
||||
import "./replicaset-scale-dialog.scss";
|
||||
|
||||
import React, { Component } from "react";
|
||||
import { computed, observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { Trans } from "@lingui/macro";
|
||||
import { Dialog, DialogProps } from "../dialog";
|
||||
import { Wizard, WizardStep } from "../wizard";
|
||||
import { Icon } from "../icon";
|
||||
import { Slider } from "../slider";
|
||||
import { Notifications } from "../notifications";
|
||||
import { cssNames } from "../../utils";
|
||||
import { ReplicaSet, replicaSetApi } from "../../api/endpoints/replica-set.api";
|
||||
|
||||
interface Props extends Partial<DialogProps> {
|
||||
}
|
||||
|
||||
@observer
|
||||
export class ReplicaSetScaleDialog extends Component<Props> {
|
||||
@observable static isOpen = false;
|
||||
@observable static data: ReplicaSet = null;
|
||||
|
||||
@observable ready = false;
|
||||
@observable currentReplicas = 0;
|
||||
@observable desiredReplicas = 0;
|
||||
|
||||
static open(replicaSet: ReplicaSet) {
|
||||
ReplicaSetScaleDialog.isOpen = true;
|
||||
ReplicaSetScaleDialog.data = replicaSet;
|
||||
}
|
||||
|
||||
static close() {
|
||||
ReplicaSetScaleDialog.isOpen = false;
|
||||
}
|
||||
|
||||
get replicaSet() {
|
||||
return ReplicaSetScaleDialog.data;
|
||||
}
|
||||
|
||||
close = () => {
|
||||
ReplicaSetScaleDialog.close();
|
||||
};
|
||||
|
||||
onOpen = async () => {
|
||||
const { replicaSet } = this;
|
||||
|
||||
this.currentReplicas = await replicaSetApi.getReplicas({
|
||||
namespace: replicaSet.getNs(),
|
||||
name: replicaSet.getName(),
|
||||
});
|
||||
this.desiredReplicas = this.currentReplicas;
|
||||
this.ready = true;
|
||||
};
|
||||
|
||||
onClose = () => {
|
||||
this.ready = false;
|
||||
};
|
||||
|
||||
onChange = (evt: React.ChangeEvent, value: number) => {
|
||||
this.desiredReplicas = value;
|
||||
};
|
||||
|
||||
@computed get scaleMax() {
|
||||
const { currentReplicas } = this;
|
||||
const defaultMax = 50;
|
||||
|
||||
return currentReplicas <= defaultMax
|
||||
? defaultMax * 2
|
||||
: currentReplicas * 2;
|
||||
}
|
||||
|
||||
scale = async () => {
|
||||
const { replicaSet } = this;
|
||||
const { currentReplicas, desiredReplicas, close } = this;
|
||||
|
||||
try {
|
||||
if (currentReplicas !== desiredReplicas) {
|
||||
await replicaSetApi.scale({
|
||||
name: replicaSet.getName(),
|
||||
namespace: replicaSet.getNs(),
|
||||
}, desiredReplicas);
|
||||
}
|
||||
close();
|
||||
} catch (err) {
|
||||
Notifications.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
desiredReplicasUp = () => {
|
||||
this.desiredReplicas < this.scaleMax && this.desiredReplicas++;
|
||||
};
|
||||
|
||||
desiredReplicasDown = () => {
|
||||
this.desiredReplicas > 0 && this.desiredReplicas--;
|
||||
};
|
||||
|
||||
renderContents() {
|
||||
const { currentReplicas, desiredReplicas, onChange, scaleMax } = this;
|
||||
const warning = currentReplicas < 10 && desiredReplicas > 90;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="current-scale" data-testid="current-scale">
|
||||
<Trans>Current replica scale: {currentReplicas}</Trans>
|
||||
</div>
|
||||
<div className="flex gaps align-center">
|
||||
<div className="desired-scale" data-testid="desired-scale">
|
||||
<Trans>Desired number of replicas</Trans>: {desiredReplicas}
|
||||
</div>
|
||||
<div className="slider-container flex align-center" data-testid="slider">
|
||||
<Slider value={desiredReplicas} max={scaleMax}
|
||||
onChange={onChange as any /** see: https://github.com/mui-org/material-ui/issues/20191 */}
|
||||
/>
|
||||
</div>
|
||||
<div className="plus-minus-container flex gaps">
|
||||
<Icon
|
||||
material="add_circle_outline"
|
||||
onClick={this.desiredReplicasUp}
|
||||
data-testid="desired-replicas-up"
|
||||
/>
|
||||
<Icon
|
||||
material="remove_circle_outline"
|
||||
onClick={this.desiredReplicasDown}
|
||||
data-testid="desired-replicas-down"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{warning &&
|
||||
<div className="warning" data-testid="warning">
|
||||
<Icon material="warning"/>
|
||||
<Trans>High number of replicas may cause cluster performance issues</Trans>
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, ...dialogProps } = this.props;
|
||||
const replicaSetName = this.replicaSet ? this.replicaSet.getName() : "";
|
||||
const header = (
|
||||
<h5>
|
||||
<Trans>Scale Replica Set <span>{replicaSetName}</span></Trans>
|
||||
</h5>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
{...dialogProps}
|
||||
isOpen={ReplicaSetScaleDialog.isOpen}
|
||||
className={cssNames("ReplicaSetScaleDialog", className)}
|
||||
onOpen={this.onOpen}
|
||||
onClose={this.onClose}
|
||||
close={this.close}
|
||||
>
|
||||
<Wizard header={header} done={this.close}>
|
||||
<WizardStep
|
||||
contentClass="flex gaps column"
|
||||
next={this.scale}
|
||||
nextLabel={<Trans>Scale</Trans>}
|
||||
disabledNext={!this.ready}
|
||||
>
|
||||
{this.renderContents()}
|
||||
</WizardStep>
|
||||
</Wizard>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,20 +1,5 @@
|
||||
.ReplicaSets {
|
||||
position: relative;
|
||||
min-height: 80px;
|
||||
|
||||
.Table {
|
||||
margin: 0 (-$margin * 3);
|
||||
}
|
||||
|
||||
.TableCell {
|
||||
&:first-child {
|
||||
margin-left: $margin;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: $margin;
|
||||
}
|
||||
|
||||
&.name {
|
||||
flex-grow: 2;
|
||||
}
|
||||
@ -22,13 +7,5 @@
|
||||
&.warning {
|
||||
@include table-cell-warning;
|
||||
}
|
||||
|
||||
&.namespace {
|
||||
flex-grow: 1.2;
|
||||
}
|
||||
|
||||
&.actions {
|
||||
@include table-cell-action;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,7 @@ import { KubeObjectStore } from "../../kube-object.store";
|
||||
import { Deployment, IPodMetrics, podsApi, ReplicaSet, replicaSetApi } from "../../api/endpoints";
|
||||
import { podsStore } from "../+workloads-pods/pods.store";
|
||||
import { apiManager } from "../../api/api-manager";
|
||||
import { PodStatus } from "../../api/endpoints/pods.api";
|
||||
|
||||
@autobind()
|
||||
export class ReplicaSetStore extends KubeObjectStore<ReplicaSet> {
|
||||
@ -20,6 +21,26 @@ export class ReplicaSetStore extends KubeObjectStore<ReplicaSet> {
|
||||
return podsStore.getPodsByOwner(replicaSet);
|
||||
}
|
||||
|
||||
getStatuses(replicaSets: ReplicaSet[]) {
|
||||
const status = { failed: 0, pending: 0, running: 0 };
|
||||
|
||||
replicaSets.forEach(replicaSet => {
|
||||
const pods = this.getChildPods(replicaSet);
|
||||
|
||||
if (pods.some(pod => pod.getStatus() === PodStatus.FAILED)) {
|
||||
status.failed++;
|
||||
}
|
||||
else if (pods.some(pod => pod.getStatus() === PodStatus.PENDING)) {
|
||||
status.pending++;
|
||||
}
|
||||
else {
|
||||
status.running++;
|
||||
}
|
||||
});
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
getReplicaSetsByOwner(deployment: Deployment) {
|
||||
return this.items.filter(replicaSet =>
|
||||
!!replicaSet.getOwnerRefs().find(owner => owner.uid === deployment.getId())
|
||||
|
||||
@ -2,97 +2,93 @@ import "./replicasets.scss";
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Trans } from "@lingui/macro";
|
||||
import { t, Trans } from "@lingui/macro";
|
||||
import { ReplicaSet } from "../../api/endpoints";
|
||||
import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object/kube-object-menu";
|
||||
import { KubeObjectMenuProps } from "../kube-object/kube-object-menu";
|
||||
import { replicaSetStore } from "./replicasets.store";
|
||||
import { Spinner } from "../spinner";
|
||||
import { prevDefault, stopPropagation } from "../../utils";
|
||||
import { DrawerTitle } from "../drawer";
|
||||
import { Table, TableCell, TableHead, TableRow } from "../table";
|
||||
import { showDetails } from "../../navigation";
|
||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||
import { RouteComponentProps } from "react-router";
|
||||
import { IReplicaSetsRouteParams } from "../+workloads/workloads.route";
|
||||
import { KubeObjectListLayout } from "../kube-object/kube-object-list-layout";
|
||||
import { MenuItem } from "../menu/menu";
|
||||
import { Icon } from "../icon/icon";
|
||||
import { _i18n } from "../../i18n";
|
||||
import { kubeObjectMenuRegistry } from "../../../extensions/registries/kube-object-menu-registry";
|
||||
import { ReplicaSetScaleDialog } from "./replicaset-scale-dialog";
|
||||
|
||||
enum sortBy {
|
||||
name = "name",
|
||||
namespace = "namespace",
|
||||
pods = "pods",
|
||||
desired = "desired",
|
||||
current = "current",
|
||||
ready = "ready",
|
||||
age = "age",
|
||||
}
|
||||
|
||||
interface Props {
|
||||
replicaSets: ReplicaSet[];
|
||||
interface Props extends RouteComponentProps<IReplicaSetsRouteParams> {
|
||||
}
|
||||
|
||||
@observer
|
||||
export class ReplicaSets extends React.Component<Props> {
|
||||
private sortingCallbacks = {
|
||||
render() {
|
||||
return (
|
||||
<KubeObjectListLayout
|
||||
className="ReplicaSets" store={replicaSetStore}
|
||||
sortingCallbacks={{
|
||||
[sortBy.name]: (replicaSet: ReplicaSet) => replicaSet.getName(),
|
||||
[sortBy.namespace]: (replicaSet: ReplicaSet) => replicaSet.getNs(),
|
||||
[sortBy.desired]: (replicaSet: ReplicaSet) => replicaSet.getDesired(),
|
||||
[sortBy.current]: (replicaSet: ReplicaSet) => replicaSet.getCurrent(),
|
||||
[sortBy.ready]: (replicaSet: ReplicaSet) => replicaSet.getReady(),
|
||||
[sortBy.age]: (replicaSet: ReplicaSet) => replicaSet.metadata.creationTimestamp,
|
||||
[sortBy.pods]: (replicaSet: ReplicaSet) => this.getPodsLength(replicaSet),
|
||||
};
|
||||
|
||||
getPodsLength(replicaSet: ReplicaSet) {
|
||||
return replicaSetStore.getChildPods(replicaSet).length;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { replicaSets } = this.props;
|
||||
|
||||
if (!replicaSets.length && !replicaSetStore.isLoaded) return (
|
||||
<div className="ReplicaSets"><Spinner center/></div>
|
||||
);
|
||||
if (!replicaSets.length) return null;
|
||||
|
||||
return (
|
||||
<div className="ReplicaSets flex column">
|
||||
<DrawerTitle title={<Trans>Deploy Revisions</Trans>}/>
|
||||
<Table
|
||||
selectable
|
||||
scrollable={false}
|
||||
sortable={this.sortingCallbacks}
|
||||
sortByDefault={{ sortBy: sortBy.pods, orderBy: "desc" }}
|
||||
sortSyncWithUrl={false}
|
||||
className="box grow"
|
||||
>
|
||||
<TableHead>
|
||||
<TableCell className="name" sortBy={sortBy.name}><Trans>Name</Trans></TableCell>
|
||||
<TableCell className="warning"/>
|
||||
<TableCell className="namespace" sortBy={sortBy.namespace}>Namespace</TableCell>
|
||||
<TableCell className="pods" sortBy={sortBy.pods}><Trans>Pods</Trans></TableCell>
|
||||
<TableCell className="age" sortBy={sortBy.age}><Trans>Age</Trans></TableCell>
|
||||
<TableCell className="actions"/>
|
||||
</TableHead>
|
||||
{
|
||||
replicaSets.map(replica => {
|
||||
return (
|
||||
<TableRow
|
||||
key={replica.getId()}
|
||||
sortItem={replica}
|
||||
nowrap
|
||||
onClick={prevDefault(() => showDetails(replica.selfLink, false))}
|
||||
>
|
||||
<TableCell className="name">{replica.getName()}</TableCell>
|
||||
<TableCell className="warning"><KubeObjectStatusIcon key="icon" object={replica}/></TableCell>
|
||||
<TableCell className="namespace">{replica.getNs()}</TableCell>
|
||||
<TableCell className="pods">{this.getPodsLength(replica)}</TableCell>
|
||||
<TableCell className="age">{replica.getAge()}</TableCell>
|
||||
<TableCell className="actions" onClick={stopPropagation}>
|
||||
<ReplicaSetMenu object={replica}/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})
|
||||
}
|
||||
</Table>
|
||||
</div>
|
||||
}}
|
||||
searchFilters={[
|
||||
(replicaSet: ReplicaSet) => replicaSet.getSearchFields(),
|
||||
]}
|
||||
renderHeaderTitle={<Trans>Replica Sets</Trans>}
|
||||
renderTableHeader={[
|
||||
{ title: <Trans>Name</Trans>, className: "name", sortBy: sortBy.name },
|
||||
{ className: "warning" },
|
||||
{ title: <Trans>Namespace</Trans>, className: "namespace", sortBy: sortBy.namespace },
|
||||
{ title: <Trans>Desired</Trans>, className: "desired", sortBy: sortBy.desired },
|
||||
{ title: <Trans>Current</Trans>, className: "current", sortBy: sortBy.current },
|
||||
{ title: <Trans>Ready</Trans>, className: "ready", sortBy: sortBy.ready },
|
||||
{ title: <Trans>Age</Trans>, className: "age", sortBy: sortBy.age },
|
||||
]}
|
||||
renderTableContents={(replicaSet: ReplicaSet) => [
|
||||
replicaSet.getName(),
|
||||
<KubeObjectStatusIcon key="icon" object={replicaSet}/>,
|
||||
replicaSet.getNs(),
|
||||
replicaSet.getDesired(),
|
||||
replicaSet.getCurrent(),
|
||||
replicaSet.getReady(),
|
||||
replicaSet.getAge(),
|
||||
]}
|
||||
renderItemMenu={(item: ReplicaSet) => {
|
||||
return <ReplicaSetMenu object={item}/>;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function ReplicaSetMenu(props: KubeObjectMenuProps<ReplicaSet>) {
|
||||
const { object, toolbar } = props;
|
||||
|
||||
return (
|
||||
<KubeObjectMenu {...props}/>
|
||||
<>
|
||||
<MenuItem onClick={() => ReplicaSetScaleDialog.open(object)}>
|
||||
<Icon material="open_with" title={_i18n._(t`Scale`)} interactive={toolbar}/>
|
||||
<span className="title"><Trans>Scale</Trans></span>
|
||||
</MenuItem>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
kubeObjectMenuRegistry.add({
|
||||
kind: "ReplicaSet",
|
||||
apiVersions: ["apps/v1"],
|
||||
components: {
|
||||
MenuItem: ReplicaSetMenu
|
||||
}
|
||||
});
|
||||
|
||||
@ -25,6 +25,9 @@ export const daemonSetsRoute: RouteProps = {
|
||||
export const statefulSetsRoute: RouteProps = {
|
||||
path: "/statefulsets"
|
||||
};
|
||||
export const replicaSetsRoute: RouteProps = {
|
||||
path: "/replicasets"
|
||||
};
|
||||
export const jobsRoute: RouteProps = {
|
||||
path: "/jobs"
|
||||
};
|
||||
@ -48,6 +51,9 @@ export interface IDaemonSetsRouteParams {
|
||||
export interface IStatefulSetsRouteParams {
|
||||
}
|
||||
|
||||
export interface IReplicaSetsRouteParams {
|
||||
}
|
||||
|
||||
export interface IJobsRouteParams {
|
||||
}
|
||||
|
||||
@ -61,6 +67,7 @@ export const podsURL = buildURL<IPodsRouteParams>(podsRoute.path);
|
||||
export const deploymentsURL = buildURL<IDeploymentsRouteParams>(deploymentsRoute.path);
|
||||
export const daemonSetsURL = buildURL<IDaemonSetsRouteParams>(daemonSetsRoute.path);
|
||||
export const statefulSetsURL = buildURL<IStatefulSetsRouteParams>(statefulSetsRoute.path);
|
||||
export const replicaSetsURL = buildURL<IReplicaSetsRouteParams>(replicaSetsRoute.path);
|
||||
export const jobsURL = buildURL<IJobsRouteParams>(jobsRoute.path);
|
||||
export const cronJobsURL = buildURL<ICronJobsRouteParams>(cronJobsRoute.path);
|
||||
|
||||
@ -69,6 +76,7 @@ export const workloadURL: Partial<Record<KubeResource, ReturnType<typeof buildUR
|
||||
"deployments": deploymentsURL,
|
||||
"daemonsets": daemonSetsURL,
|
||||
"statefulsets": statefulSetsURL,
|
||||
"replicasets": replicaSetsURL,
|
||||
"jobs": jobsURL,
|
||||
"cronjobs": cronJobsURL,
|
||||
};
|
||||
|
||||
@ -6,12 +6,14 @@ import { statefulSetStore } from "../+workloads-statefulsets/statefulset.store";
|
||||
import { jobStore } from "../+workloads-jobs/job.store";
|
||||
import { cronJobStore } from "../+workloads-cronjobs/cronjob.store";
|
||||
import { KubeResource } from "../../../common/rbac";
|
||||
import { replicaSetStore } from "../+workloads-replicasets/replicasets.store";
|
||||
|
||||
export const workloadStores: Partial<Record<KubeResource, KubeObjectStore>> = {
|
||||
"pods": podsStore,
|
||||
"deployments": deploymentStore,
|
||||
"daemonsets": daemonSetStore,
|
||||
"statefulsets": statefulSetStore,
|
||||
"replicasets": replicaSetStore,
|
||||
"jobs": jobStore,
|
||||
"cronjobs": cronJobStore,
|
||||
};
|
||||
|
||||
@ -5,7 +5,7 @@ import { observer } from "mobx-react";
|
||||
import { Trans } from "@lingui/macro";
|
||||
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
|
||||
import { WorkloadsOverview } from "../+workloads-overview/overview";
|
||||
import { cronJobsRoute, cronJobsURL, daemonSetsRoute, daemonSetsURL, deploymentsRoute, deploymentsURL, jobsRoute, jobsURL, overviewRoute, overviewURL, podsRoute, podsURL, statefulSetsRoute, statefulSetsURL } from "./workloads.route";
|
||||
import { cronJobsRoute, cronJobsURL, daemonSetsRoute, daemonSetsURL, deploymentsRoute, deploymentsURL, jobsRoute, jobsURL, overviewRoute, overviewURL, podsRoute, podsURL, replicaSetsRoute, replicaSetsURL, statefulSetsRoute, statefulSetsURL } from "./workloads.route";
|
||||
import { namespaceStore } from "../+namespaces/namespace.store";
|
||||
import { Pods } from "../+workloads-pods";
|
||||
import { Deployments } from "../+workloads-deployments";
|
||||
@ -14,6 +14,7 @@ import { StatefulSets } from "../+workloads-statefulsets";
|
||||
import { Jobs } from "../+workloads-jobs";
|
||||
import { CronJobs } from "../+workloads-cronjobs";
|
||||
import { isAllowedResource } from "../../../common/rbac";
|
||||
import { ReplicaSets } from "../+workloads-replicasets";
|
||||
|
||||
@observer
|
||||
export class Workloads extends React.Component {
|
||||
@ -64,6 +65,15 @@ export class Workloads extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
if (isAllowedResource("replicasets")) {
|
||||
routes.push({
|
||||
title: <Trans>ReplicaSets</Trans>,
|
||||
component: ReplicaSets,
|
||||
url: replicaSetsURL({ query }),
|
||||
routePath: replicaSetsRoute.path.toString(),
|
||||
});
|
||||
}
|
||||
|
||||
if (isAllowedResource("jobs")) {
|
||||
routes.push({
|
||||
title: <Trans>Jobs</Trans>,
|
||||
|
||||
@ -48,6 +48,7 @@ import { reaction, computed } from "mobx";
|
||||
import { nodesStore } from "./+nodes/nodes.store";
|
||||
import { podsStore } from "./+workloads-pods/pods.store";
|
||||
import { sum } from "lodash";
|
||||
import { ReplicaSetScaleDialog } from "./+workloads-replicasets/replicaset-scale-dialog";
|
||||
|
||||
@observer
|
||||
export class App extends React.Component {
|
||||
@ -204,6 +205,7 @@ export class App extends React.Component {
|
||||
<AddRoleBindingDialog/>
|
||||
<DeploymentScaleDialog/>
|
||||
<StatefulSetScaleDialog/>
|
||||
<ReplicaSetScaleDialog/>
|
||||
<CronJobTriggerDialog/>
|
||||
</ErrorBoundary>
|
||||
</Router>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user