mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Add stateful set scale slider (#1406)
Signed-off-by: vshakirova <vshakirova@mirantis.com>
This commit is contained in:
parent
c93ee4ea6d
commit
5b484ca692
@ -738,6 +738,7 @@ msgstr "Current / Target"
|
||||
msgid "Current Healthy"
|
||||
msgstr "Current Healthy"
|
||||
|
||||
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:101
|
||||
#: src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx:103
|
||||
msgid "Current replica scale: {currentReplicas}"
|
||||
msgstr "Current replica scale: {currentReplicas}"
|
||||
@ -828,6 +829,7 @@ msgstr "Description"
|
||||
msgid "Desired Healthy"
|
||||
msgstr "Desired Healthy"
|
||||
|
||||
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:105
|
||||
#: src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx:107
|
||||
msgid "Desired number of replicas"
|
||||
msgstr "Desired number of replicas"
|
||||
@ -1091,6 +1093,7 @@ msgstr "Helm branch <0>{0}</0> already in use"
|
||||
msgid "Hide"
|
||||
msgstr "Hide"
|
||||
|
||||
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:127
|
||||
#: src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx:116
|
||||
msgid "High number of replicas may cause cluster performance issues"
|
||||
msgstr "High number of replicas may cause cluster performance issues"
|
||||
@ -2298,6 +2301,7 @@ msgstr "Runtime Class"
|
||||
msgid "Save"
|
||||
msgstr "Save"
|
||||
|
||||
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:155
|
||||
#: 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
|
||||
@ -2308,6 +2312,10 @@ msgstr "Scale"
|
||||
msgid "Scale Deployment <0>{deploymentName}</0>"
|
||||
msgstr "Scale Deployment <0>{deploymentName}</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>"
|
||||
|
||||
#: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:45
|
||||
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:46
|
||||
msgid "Schedule"
|
||||
|
||||
@ -734,6 +734,7 @@ msgstr ""
|
||||
msgid "Current Healthy"
|
||||
msgstr ""
|
||||
|
||||
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:101
|
||||
#: src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx:103
|
||||
msgid "Current replica scale: {currentReplicas}"
|
||||
msgstr ""
|
||||
@ -824,6 +825,7 @@ msgstr ""
|
||||
msgid "Desired Healthy"
|
||||
msgstr ""
|
||||
|
||||
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:105
|
||||
#: src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx:107
|
||||
msgid "Desired number of replicas"
|
||||
msgstr ""
|
||||
@ -1082,6 +1084,7 @@ msgstr ""
|
||||
msgid "Hide"
|
||||
msgstr ""
|
||||
|
||||
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:127
|
||||
#: src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx:116
|
||||
msgid "High number of replicas may cause cluster performance issues"
|
||||
msgstr ""
|
||||
@ -2281,6 +2284,7 @@ msgstr ""
|
||||
msgid "Save"
|
||||
msgstr ""
|
||||
|
||||
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:155
|
||||
#: 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
|
||||
@ -2291,6 +2295,10 @@ msgstr ""
|
||||
msgid "Scale Deployment <0>{deploymentName}</0>"
|
||||
msgstr ""
|
||||
|
||||
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:139
|
||||
msgid "Scale Stateful Set <0>{statefulSetName}</0>"
|
||||
msgstr ""
|
||||
|
||||
#: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:45
|
||||
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:46
|
||||
msgid "Schedule"
|
||||
|
||||
@ -739,6 +739,7 @@ msgstr "Текущее / Цель"
|
||||
msgid "Current Healthy"
|
||||
msgstr ""
|
||||
|
||||
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:101
|
||||
#: src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx:103
|
||||
msgid "Current replica scale: {currentReplicas}"
|
||||
msgstr "Текущий размер реплики: {currentReplicas}"
|
||||
@ -829,6 +830,7 @@ msgstr "Описание"
|
||||
msgid "Desired Healthy"
|
||||
msgstr ""
|
||||
|
||||
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:105
|
||||
#: src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx:107
|
||||
msgid "Desired number of replicas"
|
||||
msgstr "Нужный уровень реплик"
|
||||
@ -1092,6 +1094,7 @@ msgstr ""
|
||||
msgid "Hide"
|
||||
msgstr "Скрыть"
|
||||
|
||||
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:127
|
||||
#: src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx:116
|
||||
msgid "High number of replicas may cause cluster performance issues"
|
||||
msgstr "Большое количество реплик может вызвать проблемы с производительностью кластера"
|
||||
@ -2299,6 +2302,7 @@ msgstr ""
|
||||
msgid "Save"
|
||||
msgstr "Сохранить"
|
||||
|
||||
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:155
|
||||
#: 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
|
||||
@ -2309,6 +2313,10 @@ msgstr "Масштабировать"
|
||||
msgid "Scale Deployment <0>{deploymentName}</0>"
|
||||
msgstr "Масштабировать Deployment <0>{deploymentName}</0>"
|
||||
|
||||
#: src/renderer/components/+workloads-statefulsets/statefulset-scale-dialog.tsx:139
|
||||
msgid "Scale Stateful Set <0>{statefulSetName}</0>"
|
||||
msgstr "Масштабировать Stateful Set <0>{statefulSetName}</0>"
|
||||
|
||||
#: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:45
|
||||
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:46
|
||||
msgid "Schedule"
|
||||
|
||||
@ -4,6 +4,29 @@ import { IAffinity, WorkloadKubeObject } from "../workload-kube-object";
|
||||
import { autobind } from "../../utils";
|
||||
import { KubeApi } from "../kube-api";
|
||||
|
||||
export class StatefulSetApi extends KubeApi<StatefulSet> {
|
||||
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 StatefulSet extends WorkloadKubeObject {
|
||||
static kind = "StatefulSet";
|
||||
@ -67,17 +90,22 @@ export class StatefulSet extends WorkloadKubeObject {
|
||||
observedGeneration: number;
|
||||
replicas: number;
|
||||
currentReplicas: number;
|
||||
readyReplicas: number;
|
||||
currentRevision: string;
|
||||
updateRevision: string;
|
||||
collisionCount: number;
|
||||
};
|
||||
|
||||
getReplicas() {
|
||||
return this.spec.replicas || 0;
|
||||
}
|
||||
|
||||
getImages() {
|
||||
const containers: IPodContainer[] = get(this, "spec.template.spec.containers", []);
|
||||
return [...containers].map(container => container.image);
|
||||
}
|
||||
}
|
||||
|
||||
export const statefulSetApi = new KubeApi({
|
||||
export const statefulSetApi = new StatefulSetApi({
|
||||
objectConstructor: StatefulSet,
|
||||
});
|
||||
|
||||
@ -0,0 +1,49 @@
|
||||
.StatefulSetScaleDialog {
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,167 @@
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
|
||||
jest.mock("../../api/endpoints");
|
||||
import { statefulSetApi } from "../../api/endpoints";
|
||||
import { StatefulSetScaleDialog } from "./statefulset-scale-dialog";
|
||||
import { render, waitFor, fireEvent } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
const dummyStatefulSet = {
|
||||
apiVersion: 'v1',
|
||||
kind: 'dummy',
|
||||
metadata: {
|
||||
uid: 'dummy',
|
||||
name: 'dummy',
|
||||
creationTimestamp: 'dummy',
|
||||
resourceVersion: 'dummy',
|
||||
selfLink: 'link',
|
||||
},
|
||||
selfLink: 'link',
|
||||
|
||||
spec: {
|
||||
serviceName: 'dummy',
|
||||
replicas: 1,
|
||||
selector: {
|
||||
matchLabels: { 'label': 'label' }
|
||||
},
|
||||
template: {
|
||||
metadata: {
|
||||
labels: {
|
||||
app: 'app',
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
containers: [{
|
||||
name: 'dummy',
|
||||
image: 'dummy',
|
||||
ports: [{
|
||||
containerPort: 1234,
|
||||
name: 'dummy',
|
||||
}],
|
||||
volumeMounts: [{
|
||||
name: 'dummy',
|
||||
mountPath: 'dummy',
|
||||
}],
|
||||
}],
|
||||
tolerations: [{
|
||||
key: 'dummy',
|
||||
operator: 'dummy',
|
||||
effect: 'dummy',
|
||||
tolerationSeconds: 1,
|
||||
}],
|
||||
},
|
||||
},
|
||||
volumeClaimTemplates: [{
|
||||
metadata: {
|
||||
name: 'dummy',
|
||||
},
|
||||
spec: {
|
||||
accessModes: ['dummy'],
|
||||
resources: {
|
||||
requests: {
|
||||
storage: 'dummy',
|
||||
},
|
||||
},
|
||||
},
|
||||
}],
|
||||
},
|
||||
status: {
|
||||
observedGeneration: 1,
|
||||
replicas: 1,
|
||||
currentReplicas: 1,
|
||||
readyReplicas: 1,
|
||||
currentRevision: 'dummy',
|
||||
updateRevision: 'dummy',
|
||||
collisionCount: 1,
|
||||
},
|
||||
|
||||
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('<StatefulSetScaleDialog />', () => {
|
||||
it('renders w/o errors', () => {
|
||||
const { container } = render(<StatefulSetScaleDialog/>);
|
||||
expect(container).toBeInstanceOf(HTMLElement);
|
||||
});
|
||||
|
||||
it('init with a dummy stateful set and mocked current/desired scale', async () => {
|
||||
// mock statefulSetApi.getReplicas() which will be called
|
||||
// when <StatefulSetScaleDialog /> rendered.
|
||||
const initReplicas = 1;
|
||||
statefulSetApi.getReplicas = jest.fn().mockImplementationOnce(async () => initReplicas);
|
||||
const { getByTestId } = render(<StatefulSetScaleDialog/>);
|
||||
StatefulSetScaleDialog.open(dummyStatefulSet);
|
||||
// we need to wait for the StatefulSetScaleDialog 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;
|
||||
statefulSetApi.getReplicas = jest.fn().mockImplementationOnce(async () => initReplicas);
|
||||
const component = render(<StatefulSetScaleDialog/>);
|
||||
StatefulSetScaleDialog.open(dummyStatefulSet);
|
||||
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,164 @@
|
||||
import "./statefulset-scale-dialog.scss";
|
||||
|
||||
import { StatefulSet, statefulSetApi } from "../../api/endpoints";
|
||||
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";
|
||||
|
||||
interface Props extends Partial<DialogProps> {
|
||||
}
|
||||
|
||||
@observer
|
||||
export class StatefulSetScaleDialog extends Component<Props> {
|
||||
@observable static isOpen = false;
|
||||
@observable static data: StatefulSet = null;
|
||||
|
||||
@observable ready = false;
|
||||
@observable currentReplicas = 0;
|
||||
@observable desiredReplicas = 0;
|
||||
|
||||
static open(statefulSet: StatefulSet) {
|
||||
StatefulSetScaleDialog.isOpen = true;
|
||||
StatefulSetScaleDialog.data = statefulSet;
|
||||
}
|
||||
|
||||
static close() {
|
||||
StatefulSetScaleDialog.isOpen = false;
|
||||
}
|
||||
|
||||
get statefulSet() {
|
||||
return StatefulSetScaleDialog.data;
|
||||
}
|
||||
|
||||
close = () => {
|
||||
StatefulSetScaleDialog.close();
|
||||
};
|
||||
|
||||
onOpen = async () => {
|
||||
const { statefulSet } = this;
|
||||
this.currentReplicas = await statefulSetApi.getReplicas({
|
||||
namespace: statefulSet.getNs(),
|
||||
name: statefulSet.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 { statefulSet } = this;
|
||||
const { currentReplicas, desiredReplicas, close } = this;
|
||||
try {
|
||||
if (currentReplicas !== desiredReplicas) {
|
||||
await statefulSetApi.scale({
|
||||
name: statefulSet.getName(),
|
||||
namespace: statefulSet.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 statefulSetName = this.statefulSet ? this.statefulSet.getName() : "";
|
||||
const header = (
|
||||
<h5>
|
||||
<Trans>Scale Stateful Set <span>{statefulSetName}</span></Trans>
|
||||
</h5>
|
||||
);
|
||||
return (
|
||||
<Dialog
|
||||
{...dialogProps}
|
||||
isOpen={StatefulSetScaleDialog.isOpen}
|
||||
className={cssNames("StatefulSetScaleDialog", 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -5,7 +5,7 @@
|
||||
}
|
||||
|
||||
&.pods {
|
||||
flex-grow: 0.3;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
&.warning {
|
||||
|
||||
@ -3,21 +3,27 @@ import "./statefulsets.scss";
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { RouteComponentProps } from "react-router";
|
||||
import { Trans } from "@lingui/macro";
|
||||
import { StatefulSet } from "../../api/endpoints";
|
||||
import { t, Trans } from "@lingui/macro";
|
||||
import { StatefulSet, statefulSetApi } from "../../api/endpoints";
|
||||
import { podsStore } from "../+workloads-pods/pods.store";
|
||||
import { statefulSetStore } from "./statefulset.store";
|
||||
import { nodesStore } from "../+nodes/nodes.store";
|
||||
import { eventStore } from "../+events/event.store";
|
||||
import { KubeObjectMenuProps } from "../kube-object/kube-object-menu";
|
||||
import { KubeObjectListLayout } from "../kube-object";
|
||||
import { IStatefulSetsRouteParams } from "../+workloads";
|
||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||
import { StatefulSetScaleDialog } from "./statefulset-scale-dialog";
|
||||
import { MenuItem } from "../menu/menu";
|
||||
import { _i18n } from "../../i18n";
|
||||
import { Icon } from "../icon/icon";
|
||||
import { kubeObjectMenuRegistry } from "../../../extensions/registries/kube-object-menu-registry";
|
||||
|
||||
enum sortBy {
|
||||
name = "name",
|
||||
namespace = "namespace",
|
||||
pods = "pods",
|
||||
age = "age",
|
||||
replicas = "replicas",
|
||||
}
|
||||
|
||||
interface Props extends RouteComponentProps<IStatefulSetsRouteParams> {
|
||||
@ -25,8 +31,9 @@ interface Props extends RouteComponentProps<IStatefulSetsRouteParams> {
|
||||
|
||||
@observer
|
||||
export class StatefulSets extends React.Component<Props> {
|
||||
getPodsLength(statefulSet: StatefulSet) {
|
||||
return statefulSetStore.getChildPods(statefulSet).length;
|
||||
renderPods(statefulSet: StatefulSet) {
|
||||
const { readyReplicas, currentReplicas } = statefulSet.status;
|
||||
return `${readyReplicas || 0}/${currentReplicas || 0}`;
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -38,7 +45,7 @@ export class StatefulSets extends React.Component<Props> {
|
||||
[sortBy.name]: (statefulSet: StatefulSet) => statefulSet.getName(),
|
||||
[sortBy.namespace]: (statefulSet: StatefulSet) => statefulSet.getNs(),
|
||||
[sortBy.age]: (statefulSet: StatefulSet) => statefulSet.metadata.creationTimestamp,
|
||||
[sortBy.pods]: (statefulSet: StatefulSet) => this.getPodsLength(statefulSet),
|
||||
[sortBy.replicas]: (statefulSet: StatefulSet) => statefulSet.getReplicas(),
|
||||
}}
|
||||
searchFilters={[
|
||||
(statefulSet: StatefulSet) => statefulSet.getSearchFields(),
|
||||
@ -47,18 +54,43 @@ export class StatefulSets extends React.Component<Props> {
|
||||
renderTableHeader={[
|
||||
{ title: <Trans>Name</Trans>, className: "name", sortBy: sortBy.name },
|
||||
{ title: <Trans>Namespace</Trans>, className: "namespace", sortBy: sortBy.namespace },
|
||||
{ title: <Trans>Pods</Trans>, className: "pods", sortBy: sortBy.pods },
|
||||
{ title: <Trans>Pods</Trans>, className: "pods" },
|
||||
{ title: <Trans>Replicas</Trans>, className: "replicas", sortBy: sortBy.replicas },
|
||||
{ className: "warning" },
|
||||
{ title: <Trans>Age</Trans>, className: "age", sortBy: sortBy.age },
|
||||
]}
|
||||
renderTableContents={(statefulSet: StatefulSet) => [
|
||||
statefulSet.getName(),
|
||||
statefulSet.getNs(),
|
||||
this.getPodsLength(statefulSet),
|
||||
this.renderPods(statefulSet),
|
||||
statefulSet.getReplicas(),
|
||||
<KubeObjectStatusIcon object={statefulSet}/>,
|
||||
statefulSet.getAge(),
|
||||
]}
|
||||
renderItemMenu={(item: StatefulSet) => {
|
||||
return <StatefulSetMenu object={item}/>;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function StatefulSetMenu(props: KubeObjectMenuProps<StatefulSet>) {
|
||||
const { object, toolbar } = props;
|
||||
return (
|
||||
<>
|
||||
<MenuItem onClick={() => StatefulSetScaleDialog.open(object)}>
|
||||
<Icon material="open_with" title={_i18n._(t`Scale`)} interactive={toolbar}/>
|
||||
<span className="title"><Trans>Scale</Trans></span>
|
||||
</MenuItem>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
kubeObjectMenuRegistry.add({
|
||||
kind: "StatefulSet",
|
||||
apiVersions: ["apps/v1"],
|
||||
components: {
|
||||
MenuItem: StatefulSetMenu
|
||||
}
|
||||
});
|
||||
|
||||
@ -43,6 +43,7 @@ import { clusterSetFrameIdHandler } from "../../common/cluster-ipc";
|
||||
import { ClusterPageMenuRegistration, clusterPageMenuRegistry } from "../../extensions/registries";
|
||||
import { TabLayoutRoute, TabLayout } from "./layout/tab-layout";
|
||||
import { Trans } from "@lingui/macro";
|
||||
import {StatefulSetScaleDialog} from "./+workloads-statefulsets/statefulset-scale-dialog";
|
||||
|
||||
@observer
|
||||
export class App extends React.Component {
|
||||
@ -150,6 +151,7 @@ export class App extends React.Component {
|
||||
<KubeConfigDialog/>
|
||||
<AddRoleBindingDialog/>
|
||||
<DeploymentScaleDialog/>
|
||||
<StatefulSetScaleDialog/>
|
||||
<CronJobTriggerDialog/>
|
||||
</ErrorBoundary>
|
||||
</Router>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user