Lens restructure (#540)

Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
Roman authored and GitHub committed 2020-06-30 14:35:16 +03:00
1 parent e810eab39f
commit b7974827d2
786 files changed
+14567 -24066

No files matched your search

@@ -0,0 +1,20 @@
.DeploymentDetails {
.conditions {
.Badge {
&.available {
color: white;
background-color: $deployment-available;
}
&.progressing {
color: white;
background-color: $deployment-progressing;
}
&.replica-failure {
color: white;
background-color: $deployment-replicafailure;
}
}
}
}
@@ -0,0 +1,127 @@
import "./deployment-details.scss";
import React from "react";
import kebabCase from "lodash/kebabCase";
import { disposeOnUnmount, observer } from "mobx-react";
import { t, Trans } from "@lingui/macro";
import { DrawerItem } from "../drawer";
import { Badge } from "../badge";
import { Deployment, deploymentApi } from "../../api/endpoints";
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";
import { ResourceMetrics, ResourceMetricsText } from "../resource-metrics";
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 { apiManager } from "../../api/api-manager";
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
interface Props extends KubeObjectDetailsProps<Deployment> {
}
@observer
export class DeploymentDetails extends React.Component<Props> {
@disposeOnUnmount
clean = reaction(() => this.props.object, () => {
deploymentStore.reset();
});
componentDidMount() {
if (!podsStore.isLoaded) {
podsStore.loadAll();
}
if (!replicaSetStore.isLoaded) {
replicaSetStore.loadAll();
}
}
componentWillUnmount() {
deploymentStore.reset();
}
render() {
const { object: deployment } = this.props;
if (!deployment) return null
const { status, spec } = deployment
const nodeSelector = deployment.getNodeSelectors()
const selectors = deployment.getSelectors();
const childPods = deploymentStore.getChildPods(deployment)
const replicaSets = replicaSetStore.getReplicaSetsByOwner(deployment)
const metrics = deploymentStore.metrics
return (
<div className="DeploymentDetails">
{podsStore.isLoaded && (
<ResourceMetrics
loader={() => deploymentStore.loadMetrics(deployment)}
tabs={podMetricTabs} object={deployment} params={{ metrics }}
>
<PodCharts/>
</ResourceMetrics>
)}
<KubeObjectMeta object={deployment}/>
<DrawerItem name={<Trans>Replicas</Trans>}>
{_i18n._(t`${spec.replicas} desired, ${status.updatedReplicas || 0} updated`)},{" "}
{_i18n._(t`${status.replicas || 0} total, ${status.availableReplicas || 0} available`)},{" "}
{_i18n._(t`${status.unavailableReplicas || 0} unavailable`)}
</DrawerItem>
{selectors.length > 0 &&
<DrawerItem name={<Trans>Selector</Trans>} labelsOnly>
{
selectors.map(label => <Badge key={label} label={label}/>)
}
</DrawerItem>
}
{nodeSelector.length > 0 &&
<DrawerItem name={<Trans>Node Selector</Trans>}>
{
nodeSelector.map(label => (
<Badge key={label} label={label}/>
))
}
</DrawerItem>
}
<DrawerItem name={<Trans>Strategy Type</Trans>}>
{spec.strategy.type}
</DrawerItem>
<DrawerItem name={<Trans>Conditions</Trans>} className="conditions" labelsOnly>
{
deployment.getConditions().map(condition => {
const { type, message, lastTransitionTime, status } = condition
return (
<Badge
key={type}
label={type}
className={cssNames({ disabled: status === "False" }, kebabCase(type))}
tooltip={(
<>
<p>{message}</p>
<p><Trans>Last transition time: {lastTransitionTime}</Trans></p>
</>
)}
/>
);
})
}
</DrawerItem>
<PodDetailsTolerations workload={deployment}/>
<PodDetailsAffinities workload={deployment}/>
<ResourceMetricsText metrics={metrics}/>
<ReplicaSets replicaSets={replicaSets}/>
<PodDetailsList pods={childPods} owner={deployment}/>
<KubeEventDetails object={deployment}/>
</div>
)
}
}
apiManager.registerViews(deploymentApi, {
Details: DeploymentDetails
})
@@ -0,0 +1,42 @@
.DeploymentScaleDialog {
.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 0;
}
.slider-container {
flex: 1.3 0;
}
.warning {
color: $colorSoftError;
font-size: small;
display: flex;
align-items: center;
.Icon {
margin: 0;
margin-right: $margin;
}
}
}
}
@@ -0,0 +1,142 @@
import "./deployment-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 { Deployment, deploymentApi } from "../../api/endpoints";
import { Icon } from "../icon";
import { Slider } from "../slider";
import { Notifications } from "../notifications";
import { cssNames } from "../../utils";
interface Props extends Partial<DialogProps> {
}
@observer
export class DeploymentScaleDialog extends Component<Props> {
@observable static isOpen = false;
@observable static data: Deployment = null;
@observable ready = false;
@observable currentReplicas = 0;
@observable desiredReplicas = 0;
static open(deployment: Deployment) {
DeploymentScaleDialog.isOpen = true;
DeploymentScaleDialog.data = deployment;
}
static close() {
DeploymentScaleDialog.isOpen = false;
}
get deployment() {
return DeploymentScaleDialog.data;
}
close = () => {
DeploymentScaleDialog.close();
}
@computed get scaleMax() {
const { currentReplicas } = this;
const defaultMax = 50;
return currentReplicas <= defaultMax
? defaultMax * 2
: currentReplicas * 2;
}
onOpen = async () => {
const { deployment } = this;
this.currentReplicas = await deploymentApi.getReplicas({
namespace: deployment.getNs(),
name: deployment.getName(),
});
this.desiredReplicas = this.currentReplicas;
this.ready = true;
}
onClose = () => {
this.ready = false;
}
onChange = (evt: React.ChangeEvent, value: number) => {
this.desiredReplicas = value;
}
scale = async () => {
const { deployment } = this;
const { currentReplicas, desiredReplicas, close } = this;
try {
if (currentReplicas !== desiredReplicas) {
await deploymentApi.scale({
name: deployment.getName(),
namespace: deployment.getNs(),
}, desiredReplicas);
}
close();
} catch (err) {
Notifications.error(err);
}
}
renderContents() {
const { currentReplicas, desiredReplicas, onChange, scaleMax } = this;
const warning = currentReplicas < 10 && desiredReplicas > 90;
return (
<>
<div className="current-scale">
<Trans>Current replica scale: {currentReplicas}</Trans>
</div>
<div className="flex gaps align-center">
<div className="desired-scale">
<Trans>Desired number of replicas</Trans>: {desiredReplicas}
</div>
<div className="slider-container">
<Slider value={desiredReplicas} max={scaleMax} onChange={onChange as any /** see: https://github.com/mui-org/material-ui/issues/20191 */}/>
</div>
</div>
{warning &&
<div className="warning">
<Icon material="warning"/>
<Trans>High number of replicas may cause cluster performance issues</Trans>
</div>
}
</>
)
}
render() {
const { className, ...dialogProps } = this.props;
const deploymentName = this.deployment ? this.deployment.getName() : "";
const header = (
<h5>
<Trans>Scale Deployment <span>{deploymentName}</span></Trans>
</h5>
);
return (
<Dialog
{...dialogProps}
isOpen={DeploymentScaleDialog.isOpen}
className={cssNames("DeploymentScaleDialog", 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>
);
}
}
@@ -0,0 +1,41 @@
@import "../+workloads/workloads-mixins";
.Deployments {
.TableCell {
&.name {
flex-grow: 1.5;
}
&.namespace {
flex-grow: 1.5;
}
&.pods {
flex-grow: 1;
}
&.warning {
@include table-cell-warning;
}
&.conditions {
flex-grow: 1.5;
.condition {
margin-right: $margin;
&.available {
color: $deployment-available;
}
&.progressing {
color: $deployment-progressing;
}
&.replica-failure {
color: $deployment-replicafailure;
}
}
}
}
}
@@ -0,0 +1,55 @@
import { observable } from "mobx";
import { Deployment, deploymentApi, IPodMetrics, podsApi, PodStatus } from "../../api/endpoints";
import { KubeObjectStore } from "../../kube-object.store";
import { autobind } from "../../utils";
import { podsStore } from "../+workloads-pods/pods.store";
import { apiManager } from "../../api/api-manager";
@autobind()
export class DeploymentStore extends KubeObjectStore<Deployment> {
api = deploymentApi
@observable metrics: IPodMetrics = null;
protected sortItems(items: Deployment[]) {
return super.sortItems(items, [
item => item.getReplicas(),
], "desc");
}
loadMetrics(deployment: Deployment) {
const pods = this.getChildPods(deployment);
return podsApi.getMetrics(pods, deployment.getNs(), "").then(metrics =>
this.metrics = metrics
);
}
getStatuses(deployments?: Deployment[]) {
const status = { failed: 0, pending: 0, running: 0 }
deployments.forEach(deployment => {
const pods = this.getChildPods(deployment);
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
}
getChildPods(deployment: Deployment) {
return podsStore
.getByLabel(deployment.getTemplateLabels())
.filter(pod => pod.getNs() === deployment.getNs())
}
reset() {
this.metrics = null;
}
}
export const deploymentStore = new DeploymentStore();
apiManager.registerStore(deploymentApi, deploymentStore);
@@ -0,0 +1,110 @@
import "./deployments.scss"
import React from "react";
import { observer } from "mobx-react";
import { RouteComponentProps } from "react-router";
import { t, Trans } from "@lingui/macro";
import { Deployment, deploymentApi } from "../../api/endpoints";
import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object/kube-object-menu";
import { MenuItem } from "../menu";
import { Icon } from "../icon";
import { DeploymentScaleDialog } from "./deployment-scale-dialog";
import { deploymentStore } from "./deployments.store";
import { replicaSetStore } from "../+workloads-replicasets/replicasets.store";
import { podsStore } from "../+workloads-pods/pods.store";
import { nodesStore } from "../+nodes/nodes.store";
import { eventStore } from "../+events/event.store";
import { KubeObjectListLayout } from "../kube-object";
import { IDeploymentsRouteParams } from "../+workloads";
import { _i18n } from "../../i18n";
import { cssNames } from "../../utils";
import kebabCase from "lodash/kebabCase";
import orderBy from "lodash/orderBy";
import { KubeEventIcon } from "../+events/kube-event-icon";
import { apiManager } from "../../api/api-manager";
enum sortBy {
name = "name",
namespace = "namespace",
replicas = "replicas",
age = "age",
condition = "condition",
}
interface Props extends RouteComponentProps<IDeploymentsRouteParams> {
}
@observer
export class Deployments extends React.Component<Props> {
renderPods(deployment: Deployment) {
const { replicas, availableReplicas } = deployment.status
return `${availableReplicas || 0}/${replicas || 0}`
}
renderConditions(deployment: Deployment) {
const conditions = orderBy(deployment.getConditions(true), "type", "asc")
return conditions.map(({ type, message }) => (
<span key={type} className={cssNames("condition", kebabCase(type))} title={message}>
{type}
</span>
))
}
render() {
return (
<KubeObjectListLayout
className="Deployments" store={deploymentStore}
dependentStores={[replicaSetStore, podsStore, nodesStore, eventStore]}
sortingCallbacks={{
[sortBy.name]: (deployment: Deployment) => deployment.getName(),
[sortBy.namespace]: (deployment: Deployment) => deployment.getNs(),
[sortBy.replicas]: (deployment: Deployment) => deployment.getReplicas(),
[sortBy.age]: (deployment: Deployment) => deployment.metadata.creationTimestamp,
[sortBy.condition]: (deployment: Deployment) => deployment.getConditionsText(),
}}
searchFilters={[
(deployment: Deployment) => deployment.getSearchFields(),
(deployment: Deployment) => deployment.getConditionsText(),
]}
renderHeaderTitle={<Trans>Deployments</Trans>}
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" },
{ title: <Trans>Replicas</Trans>, className: "replicas", sortBy: sortBy.replicas },
{ className: "warning" },
{ title: <Trans>Age</Trans>, className: "age", sortBy: sortBy.age },
{ title: <Trans>Conditions</Trans>, className: "conditions", sortBy: sortBy.condition },
]}
renderTableContents={(deployment: Deployment) => [
deployment.getName(),
deployment.getNs(),
this.renderPods(deployment),
deployment.getReplicas(),
<KubeEventIcon object={deployment}/>,
deployment.getAge(),
this.renderConditions(deployment),
]}
renderItemMenu={(item: Deployment) => {
return <DeploymentMenu object={item}/>
}}
/>
)
}
}
export function DeploymentMenu(props: KubeObjectMenuProps<Deployment>) {
const { object, toolbar } = props;
return (
<KubeObjectMenu {...props}>
<MenuItem onClick={() => DeploymentScaleDialog.open(object)}>
<Icon material="control_camera" title={_i18n._(t`Scale`)} interactive={toolbar}/>
<span className="title"><Trans>Scale</Trans></span>
</MenuItem>
</KubeObjectMenu>
)
}
apiManager.registerViews(deploymentApi, {
Menu: DeploymentMenu,
});
@@ -0,0 +1,2 @@
export * from "./deployments"
export * from "./deployment-details"