1
0
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:
Violetta 2020-12-10 16:23:51 +04:00 committed by GitHub
parent d961d8b159
commit d143b234b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 677 additions and 154 deletions

View File

@ -273,6 +273,12 @@ describe("Lens integration tests", () => {
expectedSelector: "h5.title", expectedSelector: "h5.title",
expectedText: "Stateful Sets" expectedText: "Stateful Sets"
}, },
{
name: "ReplicaSets",
href: "replicasets",
expectedSelector: "h5.title",
expectedText: "Replica Sets"
},
{ {
name: "Jobs", name: "Jobs",
href: "jobs", href: "jobs",

View File

@ -223,6 +223,7 @@ msgstr "Affinities"
#: src/renderer/components/+workloads-pods/pods.tsx:78 #: src/renderer/components/+workloads-pods/pods.tsx:78
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:51 #: src/renderer/components/+workloads-replicasets/replicasets.tsx:51
#: src/renderer/components/+workloads-statefulsets/statefulsets.tsx:41 #: src/renderer/components/+workloads-statefulsets/statefulsets.tsx:41
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:56
msgid "Age" msgid "Age"
msgstr "Age" msgstr "Age"
@ -766,6 +767,10 @@ msgstr "Cron Jobs"
msgid "CronJobs" msgid "CronJobs"
msgstr "CronJobs" msgstr "CronJobs"
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:54
msgid "Current"
msgstr "Current"
#: src/renderer/components/+config-autoscalers/hpa-details.tsx:50 #: src/renderer/components/+config-autoscalers/hpa-details.tsx:50
msgid "Current / Target" msgid "Current / Target"
msgstr "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-statefulsets/statefulset-scale-dialog.tsx:101
#: src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx:103 #: 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}" msgid "Current replica scale: {currentReplicas}"
msgstr "Current replica scale: {currentReplicas}" msgstr "Current replica scale: {currentReplicas}"
@ -861,6 +867,10 @@ msgstr "Deployments"
msgid "Description" msgid "Description"
msgstr "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-details.tsx:42
#: src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.tsx:41 #: src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.tsx:41
msgid "Desired Healthy" msgid "Desired Healthy"
@ -868,6 +878,7 @@ msgstr "Desired Healthy"
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:105 #: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:105
#: src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx:107 #: 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" msgid "Desired number of replicas"
msgstr "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-statefulsets/statefulset-scale-dialog.tsx:127
#: src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx:116 #: 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" msgid "High number of replicas may cause cluster performance issues"
msgstr "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/+workspaces/workspaces.tsx:135
#: src/renderer/components/dock/edit-resource.tsx:89 #: src/renderer/components/dock/edit-resource.tsx:89
#: src/renderer/components/kube-object/kube-object-meta.tsx:20 #: src/renderer/components/kube-object/kube-object-meta.tsx:20
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:50
msgid "Name" msgid "Name"
msgstr "Name" msgstr "Name"
@ -1632,6 +1645,7 @@ msgstr "Names"
#: src/renderer/components/item-object-list/page-filters-select.tsx:57 #: src/renderer/components/item-object-list/page-filters-select.tsx:57
#: src/renderer/components/kube-object/kube-object-meta.tsx:23 #: src/renderer/components/kube-object/kube-object-meta.tsx:23
#: src/renderer/components/+workloads-pods/pod-details-list.tsx:144 #: src/renderer/components/+workloads-pods/pod-details-list.tsx:144
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:52
msgid "Namespace" msgid "Namespace"
msgstr "Namespace" msgstr "Namespace"
@ -2041,6 +2055,7 @@ msgstr "Read-only Root Filesystem"
msgid "Readiness" msgid "Readiness"
msgstr "Readiness" msgstr "Readiness"
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:55
#: src/renderer/components/+workloads-pods/pod-details-list.tsx:145 #: src/renderer/components/+workloads-pods/pod-details-list.tsx:145
msgid "Ready" msgid "Ready"
msgstr "Ready" msgstr "Ready"
@ -2156,6 +2171,10 @@ msgstr "Removing helm branch <0>{0}</0> has failed: {1}"
msgid "Replicas" msgid "Replicas"
msgstr "Replicas" msgstr "Replicas"
#: src/renderer/components/+workloads/workloads.tsx:70
msgid "ReplicaSets"
msgstr "ReplicaSets"
#: src/renderer/components/dock/install-chart.tsx:119 #: src/renderer/components/dock/install-chart.tsx:119
msgid "Repo/Name" msgid "Repo/Name"
msgstr "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/deployment-scale-dialog.tsx:128
#: src/renderer/components/+workloads-deployments/deployments.tsx:83 #: src/renderer/components/+workloads-deployments/deployments.tsx:83
#: src/renderer/components/+workloads-deployments/deployments.tsx:84 #: src/renderer/components/+workloads-deployments/deployments.tsx:84
#: src/renderer/components/+workloads-replicasets/replicaset-scale-dialog.tsx:160
msgid "Scale" msgid "Scale"
msgstr "Scale" msgstr "Scale"
@ -2354,6 +2374,10 @@ msgstr "Scale"
msgid "Scale Deployment <0>{deploymentName}</0>" msgid "Scale Deployment <0>{deploymentName}</0>"
msgstr "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 #: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:139
msgid "Scale Stateful Set <0>{statefulSetName}</0>" msgid "Scale Stateful Set <0>{statefulSetName}</0>"
msgstr "Scale Stateful Set <0>{statefulSetName}</0>" msgstr "Scale Stateful Set <0>{statefulSetName}</0>"

View File

@ -222,6 +222,7 @@ msgstr ""
#: src/renderer/components/+workloads-pods/pods.tsx:78 #: src/renderer/components/+workloads-pods/pods.tsx:78
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:51 #: src/renderer/components/+workloads-replicasets/replicasets.tsx:51
#: src/renderer/components/+workloads-statefulsets/statefulsets.tsx:41 #: src/renderer/components/+workloads-statefulsets/statefulsets.tsx:41
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:56
msgid "Age" msgid "Age"
msgstr "" msgstr ""
@ -761,6 +762,10 @@ msgstr ""
msgid "CronJobs" msgid "CronJobs"
msgstr "" msgstr ""
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:54
msgid "Current"
msgstr ""
#: src/renderer/components/+config-autoscalers/hpa-details.tsx:50 #: src/renderer/components/+config-autoscalers/hpa-details.tsx:50
msgid "Current / Target" msgid "Current / Target"
msgstr "" msgstr ""
@ -772,6 +777,7 @@ msgstr ""
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:101 #: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:101
#: src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx:103 #: 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}" msgid "Current replica scale: {currentReplicas}"
msgstr "" msgstr ""
@ -856,6 +862,10 @@ msgstr ""
msgid "Description" msgid "Description"
msgstr "" 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-details.tsx:42
#: src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.tsx:41 #: src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.tsx:41
msgid "Desired Healthy" msgid "Desired Healthy"
@ -863,6 +873,7 @@ msgstr ""
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:105 #: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:105
#: src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx:107 #: 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" msgid "Desired number of replicas"
msgstr "" msgstr ""
@ -1122,6 +1133,7 @@ msgstr ""
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:127 #: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:127
#: src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx:116 #: 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" msgid "High number of replicas may cause cluster performance issues"
msgstr "" msgstr ""
@ -1573,6 +1585,7 @@ msgstr ""
#: src/renderer/components/+workspaces/workspaces.tsx:135 #: src/renderer/components/+workspaces/workspaces.tsx:135
#: src/renderer/components/dock/edit-resource.tsx:89 #: src/renderer/components/dock/edit-resource.tsx:89
#: src/renderer/components/kube-object/kube-object-meta.tsx:20 #: src/renderer/components/kube-object/kube-object-meta.tsx:20
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:50
msgid "Name" msgid "Name"
msgstr "" msgstr ""
@ -1622,6 +1635,7 @@ msgstr ""
#: src/renderer/components/item-object-list/page-filters-select.tsx:57 #: src/renderer/components/item-object-list/page-filters-select.tsx:57
#: src/renderer/components/kube-object/kube-object-meta.tsx:23 #: src/renderer/components/kube-object/kube-object-meta.tsx:23
#: src/renderer/components/+workloads-pods/pod-details-list.tsx:144 #: src/renderer/components/+workloads-pods/pod-details-list.tsx:144
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:52
msgid "Namespace" msgid "Namespace"
msgstr "" msgstr ""
@ -2023,6 +2037,7 @@ msgstr ""
msgid "Readiness" msgid "Readiness"
msgstr "" msgstr ""
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:55
#: src/renderer/components/+workloads-pods/pod-details-list.tsx:145 #: src/renderer/components/+workloads-pods/pod-details-list.tsx:145
msgid "Ready" msgid "Ready"
msgstr "" msgstr ""
@ -2138,6 +2153,10 @@ msgstr ""
msgid "Replicas" msgid "Replicas"
msgstr "" msgstr ""
#: src/renderer/components/+workloads/workloads.tsx:70
msgid "ReplicaSets"
msgstr ""
#: src/renderer/components/dock/install-chart.tsx:119 #: src/renderer/components/dock/install-chart.tsx:119
msgid "Repo/Name" msgid "Repo/Name"
msgstr "" msgstr ""
@ -2329,6 +2348,7 @@ msgstr ""
#: src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx:128 #: 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:83
#: src/renderer/components/+workloads-deployments/deployments.tsx:84 #: src/renderer/components/+workloads-deployments/deployments.tsx:84
#: src/renderer/components/+workloads-replicasets/replicaset-scale-dialog.tsx:160
msgid "Scale" msgid "Scale"
msgstr "" msgstr ""
@ -2336,6 +2356,10 @@ msgstr ""
msgid "Scale Deployment <0>{deploymentName}</0>" msgid "Scale Deployment <0>{deploymentName}</0>"
msgstr "" 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 #: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:139
msgid "Scale Stateful Set <0>{statefulSetName}</0>" msgid "Scale Stateful Set <0>{statefulSetName}</0>"
msgstr "" msgstr ""

View File

@ -223,6 +223,7 @@ msgstr "Аффинитеты"
#: src/renderer/components/+workloads-pods/pods.tsx:78 #: src/renderer/components/+workloads-pods/pods.tsx:78
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:51 #: src/renderer/components/+workloads-replicasets/replicasets.tsx:51
#: src/renderer/components/+workloads-statefulsets/statefulsets.tsx:41 #: src/renderer/components/+workloads-statefulsets/statefulsets.tsx:41
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:56
msgid "Age" msgid "Age"
msgstr "Возраст" msgstr "Возраст"
@ -766,6 +767,10 @@ msgstr ""
msgid "CronJobs" msgid "CronJobs"
msgstr "CronJobs" msgstr "CronJobs"
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:54
msgid "Current"
msgstr "Текущее"
#: src/renderer/components/+config-autoscalers/hpa-details.tsx:50 #: src/renderer/components/+config-autoscalers/hpa-details.tsx:50
msgid "Current / Target" msgid "Current / Target"
msgstr "Текущее / Цель" msgstr "Текущее / Цель"
@ -777,6 +782,7 @@ msgstr ""
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:101 #: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:101
#: src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx:103 #: 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}" msgid "Current replica scale: {currentReplicas}"
msgstr "Текущий размер реплики: {currentReplicas}" msgstr "Текущий размер реплики: {currentReplicas}"
@ -861,6 +867,10 @@ msgstr "Deployments"
msgid "Description" msgid "Description"
msgstr "Описание" 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-details.tsx:42
#: src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.tsx:41 #: src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.tsx:41
msgid "Desired Healthy" msgid "Desired Healthy"
@ -868,6 +878,7 @@ msgstr ""
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:105 #: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:105
#: src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx:107 #: 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" msgid "Desired number of replicas"
msgstr "Нужный уровень реплик" msgstr "Нужный уровень реплик"
@ -1132,6 +1143,7 @@ msgstr "Скрыть"
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:127 #: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:127
#: src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx:116 #: 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" msgid "High number of replicas may cause cluster performance issues"
msgstr "Большое количество реплик может вызвать проблемы с производительностью кластера" msgstr "Большое количество реплик может вызвать проблемы с производительностью кластера"
@ -1583,6 +1595,7 @@ msgstr "Установки"
#: src/renderer/components/+workspaces/workspaces.tsx:135 #: src/renderer/components/+workspaces/workspaces.tsx:135
#: src/renderer/components/dock/edit-resource.tsx:89 #: src/renderer/components/dock/edit-resource.tsx:89
#: src/renderer/components/kube-object/kube-object-meta.tsx:20 #: src/renderer/components/kube-object/kube-object-meta.tsx:20
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:50
msgid "Name" msgid "Name"
msgstr "Имя" msgstr "Имя"
@ -1632,6 +1645,7 @@ msgstr ""
#: src/renderer/components/item-object-list/page-filters-select.tsx:57 #: src/renderer/components/item-object-list/page-filters-select.tsx:57
#: src/renderer/components/kube-object/kube-object-meta.tsx:23 #: src/renderer/components/kube-object/kube-object-meta.tsx:23
#: src/renderer/components/+workloads-pods/pod-details-list.tsx:144 #: src/renderer/components/+workloads-pods/pod-details-list.tsx:144
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:52
msgid "Namespace" msgid "Namespace"
msgstr "Namespace" msgstr "Namespace"
@ -2041,6 +2055,7 @@ msgstr ""
msgid "Readiness" msgid "Readiness"
msgstr "Готовность" msgstr "Готовность"
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:55
#: src/renderer/components/+workloads-pods/pod-details-list.tsx:145 #: src/renderer/components/+workloads-pods/pod-details-list.tsx:145
msgid "Ready" msgid "Ready"
msgstr "Готовы" msgstr "Готовы"
@ -2156,6 +2171,10 @@ msgstr ""
msgid "Replicas" msgid "Replicas"
msgstr "Реплики" msgstr "Реплики"
#: src/renderer/components/+workloads/workloads.tsx:70
msgid "ReplicaSets"
msgstr "ReplicaSets"
#: src/renderer/components/dock/install-chart.tsx:119 #: src/renderer/components/dock/install-chart.tsx:119
msgid "Repo/Name" msgid "Repo/Name"
msgstr "Репозиторий/Имя" msgstr "Репозиторий/Имя"
@ -2347,6 +2366,7 @@ msgstr "Сохранить"
#: src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx:128 #: 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:83
#: src/renderer/components/+workloads-deployments/deployments.tsx:84 #: src/renderer/components/+workloads-deployments/deployments.tsx:84
#: src/renderer/components/+workloads-replicasets/replicaset-scale-dialog.tsx:160
msgid "Scale" msgid "Scale"
msgstr "Масштабировать" msgstr "Масштабировать"
@ -2354,6 +2374,10 @@ msgstr "Масштабировать"
msgid "Scale Deployment <0>{deploymentName}</0>" msgid "Scale Deployment <0>{deploymentName}</0>"
msgstr "Масштабировать 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 #: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:139
msgid "Scale Stateful Set <0>{statefulSetName}</0>" msgid "Scale Stateful Set <0>{statefulSetName}</0>"
msgstr "Масштабировать Stateful Set <0>{statefulSetName}</0>" msgstr "Масштабировать Stateful Set <0>{statefulSetName}</0>"

View File

@ -31,6 +31,7 @@ export const apiResources: KubeApiResource[] = [
{ resource: "poddisruptionbudgets" }, { resource: "poddisruptionbudgets" },
{ resource: "podsecuritypolicies" }, { resource: "podsecuritypolicies" },
{ resource: "resourcequotas" }, { resource: "resourcequotas" },
{ resource: "replicasets", group: "apps" },
{ resource: "secrets" }, { resource: "secrets" },
{ resource: "services" }, { resource: "services" },
{ resource: "statefulsets", group: "apps" }, { resource: "statefulsets", group: "apps" },

View File

@ -67,7 +67,7 @@ export interface IPodContainer {
image: string; image: string;
command?: string[]; command?: string[];
args?: string[]; args?: string[];
ports: { ports?: {
name?: string; name?: string;
containerPort: number; containerPort: number;
protocol: string; protocol: string;
@ -137,7 +137,7 @@ interface IContainerProbe {
export interface IPodContainerStatus { export interface IPodContainerStatus {
name: string; name: string;
state: { state?: {
[index: string]: object; [index: string]: object;
running?: { running?: {
startedAt: string; startedAt: string;
@ -153,21 +153,28 @@ export interface IPodContainerStatus {
reason: string; reason: string;
}; };
}; };
lastState: { lastState?: {
[index: string]: object; [index: string]: object;
running?: {
startedAt: string;
};
waiting?: {
reason: string;
message: string;
};
terminated?: { terminated?: {
startedAt: string; startedAt: string;
finishedAt: string; finishedAt: string;
exitCode: number; exitCode: number;
reason: string; reason: string;
containerID: string;
}; };
}; };
ready: boolean; ready: boolean;
restartCount: number; restartCount: number;
image: string; image: string;
imageID: string; imageID: string;
containerID: string; containerID?: string;
started?: boolean;
} }
@autobind() @autobind()
@ -196,28 +203,44 @@ export class Pod extends WorkloadKubeObject {
}[]; }[];
initContainers: IPodContainer[]; initContainers: IPodContainer[];
containers: IPodContainer[]; containers: IPodContainer[];
restartPolicy: string; restartPolicy?: string;
terminationGracePeriodSeconds: number; terminationGracePeriodSeconds?: number;
dnsPolicy: string; activeDeadlineSeconds?: number;
dnsPolicy?: string;
serviceAccountName: string; serviceAccountName: string;
serviceAccount: string; serviceAccount: string;
priority: number; automountServiceAccountToken?: boolean;
priorityClassName: string; priority?: number;
nodeName: string; priorityClassName?: string;
nodeName?: string;
nodeSelector?: { nodeSelector?: {
[selector: string]: string; [selector: string]: string;
}; };
securityContext: {}; securityContext?: {};
schedulerName: string; imagePullSecrets?: {
tolerations: { name: string;
key: string;
operator: string;
effect: string;
tolerationSeconds: number;
}[]; }[];
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[];
};
affinity?: IAffinity;
}; };
status: { status?: {
phase: string; phase: string;
conditions: { conditions: {
type: string; type: string;
@ -230,7 +253,7 @@ export class Pod extends WorkloadKubeObject {
startTime: string; startTime: string;
initContainerStatuses?: IPodContainerStatus[]; initContainerStatuses?: IPodContainerStatus[];
containerStatuses?: IPodContainerStatus[]; containerStatuses?: IPodContainerStatus[];
qosClass: string; qosClass?: string;
reason?: string; reason?: string;
}; };

View File

@ -1,51 +1,78 @@
import get from "lodash/get"; import get from "lodash/get";
import { autobind } from "../../utils"; import { autobind } from "../../utils";
import { IAffinity, WorkloadKubeObject } from "../workload-kube-object"; import { WorkloadKubeObject } from "../workload-kube-object";
import { IPodContainer } from "./pods.api"; import { IPodContainer, Pod } from "./pods.api";
import { KubeApi } from "../kube-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() @autobind()
export class ReplicaSet extends WorkloadKubeObject { export class ReplicaSet extends WorkloadKubeObject {
static kind = "ReplicaSet"; static kind = "ReplicaSet";
static namespaced = true; static namespaced = true;
static apiBase = "/apis/apps/v1/replicasets"; static apiBase = "/apis/apps/v1/replicasets";
spec: { spec: {
replicas?: number; replicas?: number;
selector?: { selector: { matchLabels: { [app: string]: string } };
matchLabels: {
[key: string]: string;
};
};
containers?: IPodContainer[];
template?: { template?: {
spec?: { metadata: {
affinity?: IAffinity; labels: {
nodeSelector?: { app: string;
[selector: string]: string;
}; };
tolerations: {
key: string;
operator: string;
effect: string;
tolerationSeconds: number;
}[];
containers: IPodContainer[];
}; };
spec?: Pod["spec"];
}; };
restartPolicy?: string; minReadySeconds?: number;
terminationGracePeriodSeconds?: number;
dnsPolicy?: string;
schedulerName?: string;
}; };
status: { status: {
replicas: number; replicas: number;
fullyLabeledReplicas: number; fullyLabeledReplicas?: number;
readyReplicas: number; readyReplicas?: number;
availableReplicas: number; availableReplicas?: number;
observedGeneration: 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() { getImages() {
const containers: IPodContainer[] = get(this, "spec.template.spec.containers", []); 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, objectConstructor: ReplicaSet,
}); });

View File

@ -11,7 +11,6 @@ import { cssNames } from "../../utils";
import { PodDetailsTolerations } from "../+workloads-pods/pod-details-tolerations"; import { PodDetailsTolerations } from "../+workloads-pods/pod-details-tolerations";
import { PodDetailsAffinities } from "../+workloads-pods/pod-details-affinities"; import { PodDetailsAffinities } from "../+workloads-pods/pod-details-affinities";
import { KubeEventDetails } from "../+events/kube-event-details"; import { KubeEventDetails } from "../+events/kube-event-details";
import { replicaSetStore } from "../+workloads-replicasets/replicasets.store";
import { podsStore } from "../+workloads-pods/pods.store"; import { podsStore } from "../+workloads-pods/pods.store";
import { KubeObjectDetailsProps } from "../kube-object"; import { KubeObjectDetailsProps } from "../kube-object";
import { _i18n } from "../../i18n"; import { _i18n } from "../../i18n";
@ -20,7 +19,6 @@ import { deploymentStore } from "./deployments.store";
import { PodCharts, podMetricTabs } from "../+workloads-pods/pod-charts"; import { PodCharts, podMetricTabs } from "../+workloads-pods/pod-charts";
import { reaction } from "mobx"; import { reaction } from "mobx";
import { PodDetailsList } from "../+workloads-pods/pod-details-list"; import { PodDetailsList } from "../+workloads-pods/pod-details-list";
import { ReplicaSets } from "../+workloads-replicasets";
import { KubeObjectMeta } from "../kube-object/kube-object-meta"; import { KubeObjectMeta } from "../kube-object/kube-object-meta";
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry"; import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
@ -38,10 +36,6 @@ export class DeploymentDetails extends React.Component<Props> {
if (!podsStore.isLoaded) { if (!podsStore.isLoaded) {
podsStore.loadAll(); podsStore.loadAll();
} }
if (!replicaSetStore.isLoaded) {
replicaSetStore.loadAll();
}
} }
componentWillUnmount() { componentWillUnmount() {
@ -56,7 +50,6 @@ export class DeploymentDetails extends React.Component<Props> {
const nodeSelector = deployment.getNodeSelectors(); const nodeSelector = deployment.getNodeSelectors();
const selectors = deployment.getSelectors(); const selectors = deployment.getSelectors();
const childPods = deploymentStore.getChildPods(deployment); const childPods = deploymentStore.getChildPods(deployment);
const replicaSets = replicaSetStore.getReplicaSetsByOwner(deployment);
const metrics = deploymentStore.metrics; const metrics = deploymentStore.metrics;
return ( return (
@ -118,7 +111,6 @@ export class DeploymentDetails extends React.Component<Props> {
<PodDetailsTolerations workload={deployment}/> <PodDetailsTolerations workload={deployment}/>
<PodDetailsAffinities workload={deployment}/> <PodDetailsAffinities workload={deployment}/>
<ResourceMetricsText metrics={metrics}/> <ResourceMetricsText metrics={metrics}/>
<ReplicaSets replicaSets={replicaSets}/>
<PodDetailsList pods={childPods} owner={deployment}/> <PodDetailsList pods={childPods} owner={deployment}/>
</div> </div>
); );

View File

@ -15,7 +15,7 @@
.workloads { .workloads {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, 155px); grid-template-columns: repeat(auto-fit, 180px);
justify-content: space-evenly; justify-content: space-evenly;
grid-gap: $margin; grid-gap: $margin;
padding: $padding * 2; padding: $padding * 2;

View File

@ -18,6 +18,7 @@ const resources: KubeResource[] = [
"deployments", "deployments",
"statefulsets", "statefulsets",
"daemonsets", "daemonsets",
"replicasets",
"jobs", "jobs",
"cronjobs", "cronjobs",
]; ];

View File

@ -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;
}
}
}
}

View 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");
});
});

View File

@ -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>
);
}
}

View File

@ -1,20 +1,5 @@
.ReplicaSets { .ReplicaSets {
position: relative;
min-height: 80px;
.Table {
margin: 0 (-$margin * 3);
}
.TableCell { .TableCell {
&:first-child {
margin-left: $margin;
}
&:last-child {
margin-right: $margin;
}
&.name { &.name {
flex-grow: 2; flex-grow: 2;
} }
@ -22,13 +7,5 @@
&.warning { &.warning {
@include table-cell-warning; @include table-cell-warning;
} }
&.namespace {
flex-grow: 1.2;
}
&.actions {
@include table-cell-action;
}
} }
} }

View File

@ -4,6 +4,7 @@ import { KubeObjectStore } from "../../kube-object.store";
import { Deployment, IPodMetrics, podsApi, ReplicaSet, replicaSetApi } from "../../api/endpoints"; import { Deployment, IPodMetrics, podsApi, ReplicaSet, replicaSetApi } from "../../api/endpoints";
import { podsStore } from "../+workloads-pods/pods.store"; import { podsStore } from "../+workloads-pods/pods.store";
import { apiManager } from "../../api/api-manager"; import { apiManager } from "../../api/api-manager";
import { PodStatus } from "../../api/endpoints/pods.api";
@autobind() @autobind()
export class ReplicaSetStore extends KubeObjectStore<ReplicaSet> { export class ReplicaSetStore extends KubeObjectStore<ReplicaSet> {
@ -20,6 +21,26 @@ export class ReplicaSetStore extends KubeObjectStore<ReplicaSet> {
return podsStore.getPodsByOwner(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) { getReplicaSetsByOwner(deployment: Deployment) {
return this.items.filter(replicaSet => return this.items.filter(replicaSet =>
!!replicaSet.getOwnerRefs().find(owner => owner.uid === deployment.getId()) !!replicaSet.getOwnerRefs().find(owner => owner.uid === deployment.getId())

View File

@ -2,97 +2,93 @@ import "./replicasets.scss";
import React from "react"; import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { Trans } from "@lingui/macro"; import { t, Trans } from "@lingui/macro";
import { ReplicaSet } from "../../api/endpoints"; 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 { 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 { 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 { enum sortBy {
name = "name", name = "name",
namespace = "namespace", namespace = "namespace",
pods = "pods", desired = "desired",
current = "current",
ready = "ready",
age = "age", age = "age",
} }
interface Props { interface Props extends RouteComponentProps<IReplicaSetsRouteParams> {
replicaSets: ReplicaSet[];
} }
@observer @observer
export class ReplicaSets extends React.Component<Props> { export class ReplicaSets extends React.Component<Props> {
private sortingCallbacks = {
[sortBy.name]: (replicaSet: ReplicaSet) => replicaSet.getName(),
[sortBy.namespace]: (replicaSet: ReplicaSet) => replicaSet.getNs(),
[sortBy.age]: (replicaSet: ReplicaSet) => replicaSet.metadata.creationTimestamp,
[sortBy.pods]: (replicaSet: ReplicaSet) => this.getPodsLength(replicaSet),
};
getPodsLength(replicaSet: ReplicaSet) {
return replicaSetStore.getChildPods(replicaSet).length;
}
render() { render() {
const { replicaSets } = this.props;
if (!replicaSets.length && !replicaSetStore.isLoaded) return (
<div className="ReplicaSets"><Spinner center/></div>
);
if (!replicaSets.length) return null;
return ( return (
<div className="ReplicaSets flex column"> <KubeObjectListLayout
<DrawerTitle title={<Trans>Deploy Revisions</Trans>}/> className="ReplicaSets" store={replicaSetStore}
<Table sortingCallbacks={{
selectable [sortBy.name]: (replicaSet: ReplicaSet) => replicaSet.getName(),
scrollable={false} [sortBy.namespace]: (replicaSet: ReplicaSet) => replicaSet.getNs(),
sortable={this.sortingCallbacks} [sortBy.desired]: (replicaSet: ReplicaSet) => replicaSet.getDesired(),
sortByDefault={{ sortBy: sortBy.pods, orderBy: "desc" }} [sortBy.current]: (replicaSet: ReplicaSet) => replicaSet.getCurrent(),
sortSyncWithUrl={false} [sortBy.ready]: (replicaSet: ReplicaSet) => replicaSet.getReady(),
className="box grow" [sortBy.age]: (replicaSet: ReplicaSet) => replicaSet.metadata.creationTimestamp,
> }}
<TableHead> searchFilters={[
<TableCell className="name" sortBy={sortBy.name}><Trans>Name</Trans></TableCell> (replicaSet: ReplicaSet) => replicaSet.getSearchFields(),
<TableCell className="warning"/> ]}
<TableCell className="namespace" sortBy={sortBy.namespace}>Namespace</TableCell> renderHeaderTitle={<Trans>Replica Sets</Trans>}
<TableCell className="pods" sortBy={sortBy.pods}><Trans>Pods</Trans></TableCell> renderTableHeader={[
<TableCell className="age" sortBy={sortBy.age}><Trans>Age</Trans></TableCell> { title: <Trans>Name</Trans>, className: "name", sortBy: sortBy.name },
<TableCell className="actions"/> { className: "warning" },
</TableHead> { title: <Trans>Namespace</Trans>, className: "namespace", sortBy: sortBy.namespace },
{ { title: <Trans>Desired</Trans>, className: "desired", sortBy: sortBy.desired },
replicaSets.map(replica => { { title: <Trans>Current</Trans>, className: "current", sortBy: sortBy.current },
return ( { title: <Trans>Ready</Trans>, className: "ready", sortBy: sortBy.ready },
<TableRow { title: <Trans>Age</Trans>, className: "age", sortBy: sortBy.age },
key={replica.getId()} ]}
sortItem={replica} renderTableContents={(replicaSet: ReplicaSet) => [
nowrap replicaSet.getName(),
onClick={prevDefault(() => showDetails(replica.selfLink, false))} <KubeObjectStatusIcon key="icon" object={replicaSet}/>,
> replicaSet.getNs(),
<TableCell className="name">{replica.getName()}</TableCell> replicaSet.getDesired(),
<TableCell className="warning"><KubeObjectStatusIcon key="icon" object={replica}/></TableCell> replicaSet.getCurrent(),
<TableCell className="namespace">{replica.getNs()}</TableCell> replicaSet.getReady(),
<TableCell className="pods">{this.getPodsLength(replica)}</TableCell> replicaSet.getAge(),
<TableCell className="age">{replica.getAge()}</TableCell> ]}
<TableCell className="actions" onClick={stopPropagation}> renderItemMenu={(item: ReplicaSet) => {
<ReplicaSetMenu object={replica}/> return <ReplicaSetMenu object={item}/>;
</TableCell> }}
</TableRow> />
);
})
}
</Table>
</div>
); );
} }
} }
export function ReplicaSetMenu(props: KubeObjectMenuProps<ReplicaSet>) { export function ReplicaSetMenu(props: KubeObjectMenuProps<ReplicaSet>) {
const { object, toolbar } = props;
return ( 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
}
});

View File

@ -25,6 +25,9 @@ export const daemonSetsRoute: RouteProps = {
export const statefulSetsRoute: RouteProps = { export const statefulSetsRoute: RouteProps = {
path: "/statefulsets" path: "/statefulsets"
}; };
export const replicaSetsRoute: RouteProps = {
path: "/replicasets"
};
export const jobsRoute: RouteProps = { export const jobsRoute: RouteProps = {
path: "/jobs" path: "/jobs"
}; };
@ -48,6 +51,9 @@ export interface IDaemonSetsRouteParams {
export interface IStatefulSetsRouteParams { export interface IStatefulSetsRouteParams {
} }
export interface IReplicaSetsRouteParams {
}
export interface IJobsRouteParams { export interface IJobsRouteParams {
} }
@ -61,6 +67,7 @@ export const podsURL = buildURL<IPodsRouteParams>(podsRoute.path);
export const deploymentsURL = buildURL<IDeploymentsRouteParams>(deploymentsRoute.path); export const deploymentsURL = buildURL<IDeploymentsRouteParams>(deploymentsRoute.path);
export const daemonSetsURL = buildURL<IDaemonSetsRouteParams>(daemonSetsRoute.path); export const daemonSetsURL = buildURL<IDaemonSetsRouteParams>(daemonSetsRoute.path);
export const statefulSetsURL = buildURL<IStatefulSetsRouteParams>(statefulSetsRoute.path); export const statefulSetsURL = buildURL<IStatefulSetsRouteParams>(statefulSetsRoute.path);
export const replicaSetsURL = buildURL<IReplicaSetsRouteParams>(replicaSetsRoute.path);
export const jobsURL = buildURL<IJobsRouteParams>(jobsRoute.path); export const jobsURL = buildURL<IJobsRouteParams>(jobsRoute.path);
export const cronJobsURL = buildURL<ICronJobsRouteParams>(cronJobsRoute.path); export const cronJobsURL = buildURL<ICronJobsRouteParams>(cronJobsRoute.path);
@ -69,6 +76,7 @@ export const workloadURL: Partial<Record<KubeResource, ReturnType<typeof buildUR
"deployments": deploymentsURL, "deployments": deploymentsURL,
"daemonsets": daemonSetsURL, "daemonsets": daemonSetsURL,
"statefulsets": statefulSetsURL, "statefulsets": statefulSetsURL,
"replicasets": replicaSetsURL,
"jobs": jobsURL, "jobs": jobsURL,
"cronjobs": cronJobsURL, "cronjobs": cronJobsURL,
}; };

View File

@ -6,12 +6,14 @@ import { statefulSetStore } from "../+workloads-statefulsets/statefulset.store";
import { jobStore } from "../+workloads-jobs/job.store"; import { jobStore } from "../+workloads-jobs/job.store";
import { cronJobStore } from "../+workloads-cronjobs/cronjob.store"; import { cronJobStore } from "../+workloads-cronjobs/cronjob.store";
import { KubeResource } from "../../../common/rbac"; import { KubeResource } from "../../../common/rbac";
import { replicaSetStore } from "../+workloads-replicasets/replicasets.store";
export const workloadStores: Partial<Record<KubeResource, KubeObjectStore>> = { export const workloadStores: Partial<Record<KubeResource, KubeObjectStore>> = {
"pods": podsStore, "pods": podsStore,
"deployments": deploymentStore, "deployments": deploymentStore,
"daemonsets": daemonSetStore, "daemonsets": daemonSetStore,
"statefulsets": statefulSetStore, "statefulsets": statefulSetStore,
"replicasets": replicaSetStore,
"jobs": jobStore, "jobs": jobStore,
"cronjobs": cronJobStore, "cronjobs": cronJobStore,
}; };

View File

@ -5,7 +5,7 @@ import { observer } from "mobx-react";
import { Trans } from "@lingui/macro"; import { Trans } from "@lingui/macro";
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout"; import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
import { WorkloadsOverview } from "../+workloads-overview/overview"; 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 { namespaceStore } from "../+namespaces/namespace.store";
import { Pods } from "../+workloads-pods"; import { Pods } from "../+workloads-pods";
import { Deployments } from "../+workloads-deployments"; import { Deployments } from "../+workloads-deployments";
@ -14,6 +14,7 @@ import { StatefulSets } from "../+workloads-statefulsets";
import { Jobs } from "../+workloads-jobs"; import { Jobs } from "../+workloads-jobs";
import { CronJobs } from "../+workloads-cronjobs"; import { CronJobs } from "../+workloads-cronjobs";
import { isAllowedResource } from "../../../common/rbac"; import { isAllowedResource } from "../../../common/rbac";
import { ReplicaSets } from "../+workloads-replicasets";
@observer @observer
export class Workloads extends React.Component { 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")) { if (isAllowedResource("jobs")) {
routes.push({ routes.push({
title: <Trans>Jobs</Trans>, title: <Trans>Jobs</Trans>,

View File

@ -48,6 +48,7 @@ import { reaction, computed } from "mobx";
import { nodesStore } from "./+nodes/nodes.store"; import { nodesStore } from "./+nodes/nodes.store";
import { podsStore } from "./+workloads-pods/pods.store"; import { podsStore } from "./+workloads-pods/pods.store";
import { sum } from "lodash"; import { sum } from "lodash";
import { ReplicaSetScaleDialog } from "./+workloads-replicasets/replicaset-scale-dialog";
@observer @observer
export class App extends React.Component { export class App extends React.Component {
@ -204,6 +205,7 @@ export class App extends React.Component {
<AddRoleBindingDialog/> <AddRoleBindingDialog/>
<DeploymentScaleDialog/> <DeploymentScaleDialog/>
<StatefulSetScaleDialog/> <StatefulSetScaleDialog/>
<ReplicaSetScaleDialog/>
<CronJobTriggerDialog/> <CronJobTriggerDialog/>
</ErrorBoundary> </ErrorBoundary>
</Router> </Router>