-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Whoops...
+
+
+
+
+
+
+ Looks like you took a wrong turn
+
+
+
+
+
+
+
+
+
+ < 404 Not Found >
+
+
+
-
-
-
-
-
-
-
-
-
-
- Whoops...
-
-
-
-
-
-
- Looks like you took a wrong turn
-
-
-
-
-
-
-
-
-
- < 404 Not Found >
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
"
`;
diff --git a/frontend/packages/core/src/tests/button.test.tsx b/frontend/packages/core/src/tests/button.test.tsx
index d8bfcb9533..d8f0a40d1c 100644
--- a/frontend/packages/core/src/tests/button.test.tsx
+++ b/frontend/packages/core/src/tests/button.test.tsx
@@ -1,13 +1,13 @@
import React from "react";
import { shallow } from "enzyme";
-import { AdvanceButton, Button, DestructiveButton } from "../button";
+import { Button } from "../button";
-describe("Advance Button component", () => {
+describe("Primary Button component", () => {
let component;
beforeAll(() => {
- component = shallow(
);
+ component = shallow(
);
});
it("renders correctly", () => {
@@ -15,11 +15,11 @@ describe("Advance Button component", () => {
});
});
-describe("Button component", () => {
+describe("Neutral Button component", () => {
let component;
beforeAll(() => {
- component = shallow(
);
+ component = shallow(
);
});
it("renders correctly", () => {
@@ -31,7 +31,7 @@ describe("Destructive Button component", () => {
let component;
beforeAll(() => {
- component = shallow(
);
+ component = shallow(
);
});
it("renders correctly", () => {
diff --git a/frontend/packages/data-layout/package.json b/frontend/packages/data-layout/package.json
index b4ae8a7c88..f77b1d5650 100644
--- a/frontend/packages/data-layout/package.json
+++ b/frontend/packages/data-layout/package.json
@@ -1,6 +1,6 @@
{
"name": "@clutch-sh/data-layout",
- "version": "0.0.0-beta",
+ "version": "1.0.0-beta",
"description": "Data Layout manager for clutch",
"homepage": "https://clutch.sh/docs/development/frontend#clutch-shdata-layout",
"license": "Apache-2.0",
@@ -31,6 +31,6 @@
"react-hook-thunk-reducer": "^0.2.1"
},
"devDependencies": {
- "@clutch-sh/tools": "^0.0.0-beta"
+ "@clutch-sh/tools": "^1.0.0-beta"
}
}
diff --git a/frontend/packages/tools/package.json b/frontend/packages/tools/package.json
index a6b12c10cc..d916e778fb 100644
--- a/frontend/packages/tools/package.json
+++ b/frontend/packages/tools/package.json
@@ -1,6 +1,6 @@
{
"name": "@clutch-sh/tools",
- "version": "0.0.0-beta",
+ "version": "1.0.0-beta",
"description": "Tools for testing and building clutch components",
"homepage": "https://clutch.sh/docs/development/frontend#clutch-shtools",
"license": "Apache-2.0",
diff --git a/frontend/packages/wizard/package.json b/frontend/packages/wizard/package.json
index eb36cabc08..ffaaf29b87 100644
--- a/frontend/packages/wizard/package.json
+++ b/frontend/packages/wizard/package.json
@@ -1,6 +1,6 @@
{
"name": "@clutch-sh/wizard",
- "version": "0.0.0-beta",
+ "version": "1.0.0-beta",
"description": "Wizard Components to drive frontend workflows",
"homepage": "https://clutch.sh/docs/development/frontend#clutch-shwizard",
"license": "Apache-2.0",
@@ -25,17 +25,18 @@
"test:watch": "yarn run test --watch"
},
"dependencies": {
- "@clutch-sh/core": "^0.0.0-beta",
- "@clutch-sh/data-layout": "^0.0.0-beta",
+ "@clutch-sh/core": "^1.0.0-beta",
+ "@clutch-sh/data-layout": "^1.0.0-beta",
+ "@emotion/react": "^11.0.0",
+ "@emotion/styled": "^11.0.0",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"clsx": "^1.1.1",
"react": "^16.8.0",
"react-dom": "^16.8.0",
- "react-is": "^16.8.0",
- "styled-components": "^5.1.1"
+ "react-is": "^16.8.0"
},
"devDependencies": {
- "@clutch-sh/tools": "^0.0.0-beta"
+ "@clutch-sh/tools": "^1.0.0-beta"
}
}
diff --git a/frontend/packages/wizard/src/step.tsx b/frontend/packages/wizard/src/step.tsx
index 5ae762682f..a42efcc2a4 100644
--- a/frontend/packages/wizard/src/step.tsx
+++ b/frontend/packages/wizard/src/step.tsx
@@ -1,12 +1,14 @@
import React from "react";
import { Error, Loadable, useWizardContext } from "@clutch-sh/core";
-import { Grid } from "@material-ui/core";
-import styled from "styled-components";
+import styled from "@emotion/styled";
+import { Grid as MuiGrid } from "@material-ui/core";
-const SizedGrid = styled(Grid)`
- width: 100%;
- padding: 0 5%;
-`;
+const Grid = styled(MuiGrid)({
+ width: "100%",
+ "> *": {
+ margin: "8px 0",
+ },
+});
export interface WizardStepProps {
isLoading: boolean;
@@ -20,15 +22,18 @@ const WizardStep: React.FC
= ({ isLoading, error, children }) =
React.useEffect(() => {
wizardContext.setIsLoading(showLoading);
}, [showLoading]);
+ React.useEffect(() => {
+ wizardContext.setHasError(hasError);
+ }, [error]);
if (showLoading) {
return {children};
}
return hasError ? (
) : (
-
+
{children}
-
+
);
};
diff --git a/frontend/packages/wizard/src/wizard.tsx b/frontend/packages/wizard/src/wizard.tsx
index 79faa6f0de..d901c5b3ff 100644
--- a/frontend/packages/wizard/src/wizard.tsx
+++ b/frontend/packages/wizard/src/wizard.tsx
@@ -1,120 +1,23 @@
import React from "react";
-import { Button, Warning, WizardContext } from "@clutch-sh/core";
+import { Button, ButtonGroup, Step, Stepper, Warning, WizardContext } from "@clutch-sh/core";
import type { ManagerLayout } from "@clutch-sh/data-layout";
import { DataLayoutContext, useDataLayoutManager } from "@clutch-sh/data-layout";
-import type { ContainerProps } from "@material-ui/core";
-import {
- Container,
- Grid,
- Step,
- StepConnector as MUIStepConnector,
- StepContent,
- StepLabel,
- Stepper,
- Typography,
-} from "@material-ui/core";
-import { makeStyles, withStyles } from "@material-ui/core/styles";
-import Check from "@material-ui/icons/Check";
-import ErrorOutlineIcon from "@material-ui/icons/ErrorOutline";
-import FirstPageIcon from "@material-ui/icons/FirstPage";
-import clsx from "clsx";
-import styled from "styled-components";
+import styled from "@emotion/styled";
+import { Container as MuiContainer, Grid, Paper as MuiPaper, Typography } from "@material-ui/core";
import { useWizardState, WizardAction } from "./state";
import type { WizardStepProps } from "./step";
-const Heading = styled(Typography)`
- padding-left: 1.25rem;
-`;
-
-const StepConnector = withStyles({
- alternativeLabel: {
- top: 10,
- left: "calc(-50% + 16px)",
- right: "calc(50% + 16px)",
- },
- active: {
- "& $line": {
- borderColor: "#02acbe",
- },
- },
- completed: {
- "& $line": {
- borderColor: "#02acbe",
- },
- },
- line: {
- borderColor: "#eaeaf0",
- borderTopWidth: 3,
- borderRadius: 1,
- },
-})(MUIStepConnector);
-
-const useQontoStepIconStyles = makeStyles({
- root: {
- color: "#eaeaf0",
- display: "flex",
- height: 22,
- alignItems: "center",
- },
- active: {
- color: "#02acbe",
- },
+const Heading = styled(Typography)({
+ paddingBottom: "16px",
+ fontWeight: 700,
+ fontSize: "26px",
});
-const CircleIcon = styled.div`
- width: 8px;
- height: 8px;
- border-radius: 50%;
- background-color: currentColor;
-`;
-
-const CheckmarkIcon = styled(Check)`
- ${({ theme }) => `
- color: ${theme.palette.accent.main};
- z-index: 1;
- font-size: 18px;
- `}
-`;
-
-interface StepIconProps {
- active: boolean;
- completed: boolean;
- error: boolean;
-}
-
-const StepIcon: React.FC = ({ active, completed, error }) => {
- const classes = useQontoStepIconStyles();
-
- return (
-
- {completed ? : error ? : }
-
- );
-};
-
-const StyledStepper = styled(Stepper)`
- background-color: transparent;
-`;
-
-interface SpacerProps {
- margin?: string;
-}
-
-const Spacer = styled.div`
- ${({ margin }) => `
- margin: ${Number(margin || 1) * 10}px;
- `}
-`;
-
interface WizardProps {
heading?: string;
dataLayout: ManagerLayout;
- maxWidth?: ContainerProps["maxWidth"];
+ children: React.ReactElement | React.ReactElement[];
}
export interface WizardChild {
@@ -129,15 +32,17 @@ interface WizardStepData {
[index: string]: any;
}
-const SizedContainer = styled(Grid)`
- display: inline;
-`;
+const Container = styled(MuiContainer)({
+ padding: "32px",
+ maxWidth: "800px",
+});
-const StartOverIcon = styled(FirstPageIcon)`
- transform: rotate(90deg);
-`;
+const Paper = styled(MuiPaper)({
+ boxShadow: "0px 5px 15px rgba(53, 72, 212, 0.2)",
+ padding: "32px",
+});
-const Wizard: React.FC = ({ heading, dataLayout, children, maxWidth }) => {
+const Wizard = ({ heading, dataLayout, children }: WizardProps) => {
const [state, dispatch] = useWizardState();
const [wizardStepData, setWizardStepData] = React.useState({});
const [globalWarnings, setGlobalWarnings] = React.useState([]);
@@ -167,6 +72,9 @@ const Wizard: React.FC = ({ heading, dataLayout, children, maxWidth
setIsLoading: (isLoading: boolean) => {
updateStepData(child.type.name, { isLoading });
},
+ setHasError: (hasError: boolean) => {
+ updateStepData(child.type.name, { hasError });
+ },
displayWarnings: (warnings: string[]) => {
setGlobalWarnings(warnings);
},
@@ -180,36 +88,26 @@ const Wizard: React.FC = ({ heading, dataLayout, children, maxWidth
const lastStepIndex = React.Children.count(children) - 1;
// If our wizard only has 1 step, it doesn't make sense to put a restart button
const isMultistep = lastStepIndex > 0;
- const steps = React.Children.map(children, (child: WizardChildren, idx: number) => {
- const hasError = (wizardStepData[child.type.name]?.errors?.length || 0) !== 0;
+ const steps = React.Children.map(children, (child: WizardChildren) => {
const isLoading = wizardStepData[child.type.name]?.isLoading || false;
+ const hasError = wizardStepData[child.type.name]?.hasError;
return (
-
-
- {child.props.name}
-
-
-
- context(child)}>
-
- {child}
-
-
-
-
- {state.activeStep === lastStepIndex && !isLoading && isMultistep && (
-
-
-
+ <>
+
+ context(child)}>
+
+ {child}
+
+
+
+
+ {((state.activeStep === lastStepIndex && !isLoading && isMultistep) || hasError) && (
+
+
+ )}
+
+ >
);
});
@@ -218,35 +116,30 @@ const Wizard: React.FC = ({ heading, dataLayout, children, maxWidth
};
return (
-
-
- {heading && (
-
- {heading}
-
- )}
-
-
- }
- >
- {steps}
-
-
-
- {globalWarnings.map(error => (
- removeWarning(error)} />
- ))}
-
-
+
+
+ {heading && {heading}}
+
+
+ {React.Children.map(children, (child: WizardChildren) => {
+ const { name } = child.props;
+ const hasError = wizardStepData[child.type.name]?.hasError;
+ return ;
+ })}
+
+ {steps[state.activeStep]}
+
+
+ {globalWarnings.map(error => (
+ removeWarning(error)} />
+ ))}
+
);
};
diff --git a/frontend/workflows/ec2/package.json b/frontend/workflows/ec2/package.json
index 3dfb6fb475..f0c89fca84 100644
--- a/frontend/workflows/ec2/package.json
+++ b/frontend/workflows/ec2/package.json
@@ -1,6 +1,6 @@
{
"name": "@clutch-sh/ec2",
- "version": "0.0.0-beta",
+ "version": "1.0.0-beta",
"description": "Clutch EC2 Workflows",
"license": "Apache-2.0",
"author": "clutch@lyft.com",
@@ -23,9 +23,9 @@
"test:watch": "yarn run test --watch"
},
"dependencies": {
- "@clutch-sh/core": "^0.0.0-beta",
- "@clutch-sh/data-layout": "^0.0.0-beta",
- "@clutch-sh/wizard": "^0.0.0-beta",
+ "@clutch-sh/core": "^1.0.0-beta",
+ "@clutch-sh/data-layout": "^1.0.0-beta",
+ "@clutch-sh/wizard": "^1.0.0-beta",
"@material-ui/core": "^4.11.0",
"react": "^16.8.0",
"react-dom": "^16.8.0",
@@ -33,6 +33,6 @@
"yup": "^0.31.0"
},
"devDependencies": {
- "@clutch-sh/tools": "0.0.0-beta"
+ "@clutch-sh/tools": "^1.0.0-beta"
}
}
diff --git a/frontend/workflows/ec2/src/reboot-instance.tsx b/frontend/workflows/ec2/src/reboot-instance.tsx
index 9ce7f5d065..084c1b7c7e 100644
--- a/frontend/workflows/ec2/src/reboot-instance.tsx
+++ b/frontend/workflows/ec2/src/reboot-instance.tsx
@@ -1,5 +1,6 @@
import React from "react";
import {
+ Button,
ButtonGroup,
client,
Confirmation,
@@ -51,19 +52,10 @@ const InstanceDetails: React.FC = () => {
return (
-
+
+
+
+
);
};
@@ -96,7 +88,7 @@ const RebootInstance: React.FC = ({ heading, resolverType, notes
return (
-
+
);
diff --git a/frontend/workflows/ec2/src/resize-asg.tsx b/frontend/workflows/ec2/src/resize-asg.tsx
index 1fefb2d237..0f6502ae36 100644
--- a/frontend/workflows/ec2/src/resize-asg.tsx
+++ b/frontend/workflows/ec2/src/resize-asg.tsx
@@ -1,5 +1,6 @@
import React from "react";
import {
+ Button,
ButtonGroup,
client,
Confirmation,
@@ -11,8 +12,7 @@ import {
import { useDataLayout } from "@clutch-sh/data-layout";
import type { WizardChild } from "@clutch-sh/wizard";
import { Wizard, WizardStep } from "@clutch-sh/wizard";
-import { Paper } from "@material-ui/core";
-import { number } from "yup";
+import { number, ref } from "yup";
import type { ConfirmChild, ResolverChild, WorkflowProps } from ".";
@@ -39,6 +39,7 @@ const GroupDetails: React.FC = () => {
return (
+ ASG Details
= () => {
{
name: "Max Size",
value: group.size.max,
- input: { type: "number", key: "size.max" },
+ input: {
+ type: "number",
+ key: "size.max",
+ validation: number().integer().moreThan(ref("Min Size")),
+ },
},
{
name: "Desired Size",
value: group.size.desired,
- input: { type: "number", key: "size.desired" },
+ input: {
+ type: "number",
+ key: "size.desired",
+ validation: number().integer().moreThan(ref("Min Size")),
+ },
},
{ name: "Availability Zone", value: group.zones },
]}
/>
-
+
+
+
+
);
};
+// TODO (sperry): possibly show the previous size values
const Confirm: React.FC = ({ notes }) => {
const group = useDataLayout("groupData").displayValue();
const resizeData = useDataLayout("resizeData");
@@ -91,16 +92,14 @@ const Confirm: React.FC = ({ notes }) => {
return (
-
-
-
+
);
diff --git a/frontend/workflows/ec2/src/terminate-instance.tsx b/frontend/workflows/ec2/src/terminate-instance.tsx
index 52884b562b..67fa9be307 100644
--- a/frontend/workflows/ec2/src/terminate-instance.tsx
+++ b/frontend/workflows/ec2/src/terminate-instance.tsx
@@ -1,5 +1,8 @@
import React from "react";
import {
+ Accordion,
+ AccordionDetails,
+ Button,
ButtonGroup,
client,
Confirmation,
@@ -42,38 +45,45 @@ const InstanceDetails: React.FC = () => {
{ name: "Availability Zone", value: instance.availabilityZone },
];
+ const metadata = [];
if (instance.tags) {
Object.keys(instance.tags).forEach(key => {
- data.push({ name: key, value: instance.tags[key] });
+ metadata.push({ name: key, value: instance.tags[key] });
});
}
return (
+ Instance Details
-
+ {metadata.length > 0 && (
+
+
+
+
+
+ )}
+
+
+
+
);
};
const Confirm: React.FC = ({ notes }) => {
const terminationData = useDataLayout("terminationData");
+ const instance = useDataLayout("resourceData").displayValue();
+
+ const data = [
+ { name: "Instance ID", value: instance.instanceId },
+ { name: "Region", value: instance.region },
+ ];
return (
+
);
@@ -96,7 +106,7 @@ const TerminateInstance: React.FC = ({ heading, resolverType, not
return (
-
+
);
diff --git a/frontend/workflows/ec2/src/tests/__snapshots__/terminate-instance.test.tsx.snap b/frontend/workflows/ec2/src/tests/__snapshots__/terminate-instance.test.tsx.snap
index 18d69264ec..d8dc3d2727 100644
--- a/frontend/workflows/ec2/src/tests/__snapshots__/terminate-instance.test.tsx.snap
+++ b/frontend/workflows/ec2/src/tests/__snapshots__/terminate-instance.test.tsx.snap
@@ -3,7 +3,7 @@
exports[`Terminate Instance workflow renders correctly 1`] = `
"
-
+
"
`;
diff --git a/frontend/workflows/envoy/package.json b/frontend/workflows/envoy/package.json
index 4bab714d68..1887acf3cd 100644
--- a/frontend/workflows/envoy/package.json
+++ b/frontend/workflows/envoy/package.json
@@ -1,6 +1,6 @@
{
"name": "@clutch-sh/envoy",
- "version": "0.0.0-beta",
+ "version": "1.0.0-beta",
"description": "Clutch Envoy Workflows",
"license": "Apache-2.0",
"author": "clutch@lyft.com",
@@ -22,11 +22,14 @@
"test:watch": "yarn test --watch"
},
"dependencies": {
- "@clutch-sh/api": "^0.0.0-beta",
- "@clutch-sh/core": "^0.0.0-beta",
- "@clutch-sh/data-layout": "^0.0.0-beta",
- "@clutch-sh/wizard": "^0.0.0-beta",
+ "@clutch-sh/api": "^1.0.0-beta",
+ "@clutch-sh/core": "^1.0.0-beta",
+ "@clutch-sh/data-layout": "^1.0.0-beta",
+ "@clutch-sh/wizard": "^1.0.0-beta",
+ "@emotion/styled": "^11.0.0",
"@material-ui/core": "^4.11.0",
+ "@nivo/core": "0.64.0",
+ "@nivo/pie": "0.64.0",
"lodash": "^4.17.15",
"react": "^16.8.0",
"react-dom": "^16.8.0",
@@ -34,6 +37,6 @@
"yup": "^0.31.0"
},
"devDependencies": {
- "@clutch-sh/tools": "^0.0.0-beta"
+ "@clutch-sh/tools": "^1.0.0-beta"
}
}
diff --git a/frontend/workflows/envoy/src/clusters.tsx b/frontend/workflows/envoy/src/clusters.tsx
deleted file mode 100644
index b9e4a38eec..0000000000
--- a/frontend/workflows/envoy/src/clusters.tsx
+++ /dev/null
@@ -1,99 +0,0 @@
-import React from "react";
-import type { clutch as IClutch } from "@clutch-sh/api";
-import { ExpandableRow, ExpandableTable, ExpansionPanel, Status, StatusRow } from "@clutch-sh/core";
-import { Grid } from "@material-ui/core";
-
-interface RatioStatusProps {
- succeeded: boolean;
- failed: boolean;
- align?: "right" | "center";
-}
-
-const RatioStatus: React.FC = ({ succeeded, failed, ...props }) => (
-
- {succeeded ? (
-
-
- {succeeded}
-
-
- ) : null}
- {succeeded && failed ? / : null}
- {failed ? (
-
-
- {failed}
-
-
- ) : null}
-
-);
-
-const clusterStatuses = (data: IClutch.envoytriage.v1.IClusters) => {
- return data.clusterStatuses.map(clusterStatus => {
- const healthyCount = clusterStatus.hostStatuses.filter(hostStatus => hostStatus.healthy).length;
- const unhealthyCount = clusterStatus.hostStatuses.length - healthyCount;
- return {
- name: clusterStatus.name,
- healthyCount,
- unhealthyCount,
- hosts: clusterStatus.hostStatuses,
- };
- });
-};
-
-interface ClustersProps {
- clusters: IClutch.envoytriage.v1.IClusters;
-}
-
-const Clusters: React.FC = ({ clusters }) => {
- const [statuses, setStatuses] = React.useState([]);
- const [summary, setSummary] = React.useState("");
-
- React.useEffect(() => {
- setStatuses(clusterStatuses(clusters));
- }, [clusters]);
-
- React.useEffect(() => {
- const healthyHostCount = statuses
- .map(cluster => cluster.healthyCount)
- .reduce((a, b) => a + b, 0);
- const totalHostCount = statuses.map(cluster => cluster.hosts.length).reduce((a, b) => a + b, 0);
- setSummary(`(${healthyHostCount}/${totalHostCount} healthy)`);
- }, [statuses]);
-
- return (
-
-
- {statuses.map(cluster => (
- 0
- ) : (
-
- )
- }
- >
- {cluster.hosts.map(host => {
- const hostData = { ...host };
- const { healthy } = hostData;
- delete hostData.healthy;
- return (
-
- );
- })}
-
- ))}
-
-
- );
-};
-
-export default Clusters;
diff --git a/frontend/workflows/envoy/src/listeners.tsx b/frontend/workflows/envoy/src/listeners.tsx
deleted file mode 100644
index 31ed39d19a..0000000000
--- a/frontend/workflows/envoy/src/listeners.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import React from "react";
-import type { clutch as IClutch } from "@clutch-sh/api";
-import { ExpansionPanel, Row, Table } from "@clutch-sh/core";
-
-interface ListenersProps {
- listeners: IClutch.envoytriage.v1.IListeners;
-}
-
-const Listeners: React.FC = ({ listeners }) => {
- const [statuses, setStatuses] = React.useState([]);
- const [summary, setSummary] = React.useState("");
-
- React.useEffect(() => {
- setStatuses(listeners.listenerStatuses);
- }, [listeners]);
-
- React.useEffect(() => {
- setSummary(`(${statuses.length} found)`);
- }, [statuses]);
-
- return (
-
-
- {statuses.map(listener => (
-
- ))}
-
-
- );
-};
-
-export default Listeners;
diff --git a/frontend/workflows/envoy/src/remote-triage/clusters.tsx b/frontend/workflows/envoy/src/remote-triage/clusters.tsx
new file mode 100644
index 0000000000..8041dab394
--- /dev/null
+++ b/frontend/workflows/envoy/src/remote-triage/clusters.tsx
@@ -0,0 +1,111 @@
+import React from "react";
+import type { clutch as IClutch } from "@clutch-sh/api";
+import { AccordionRow, StatusIcon, Table, TableRow } from "@clutch-sh/core";
+import styled from "@emotion/styled";
+import _ from "lodash";
+
+const BarContainer = styled.rect(
+ {
+ height: "12px",
+ },
+ props => ({
+ width: props.width,
+ fill: props.fill,
+ strokeWidth: props.fill === "transparent" ? "1px" : "0",
+ stroke: "#C2C8F2",
+ })
+);
+
+const Bar = ({ fill, width }) => (
+
+);
+
+interface RatioStatusProps {
+ succeeded: number;
+ failed: number;
+}
+
+const RatioStatus: React.FC = ({ succeeded, failed }) => {
+ const total = succeeded + failed;
+ return (
+ <>
+ {succeeded !== 0 && }
+ {failed !== 0 && }
+ >
+ );
+};
+
+const clusterStatuses = (data: IClutch.envoytriage.v1.IClusters) => {
+ return data.clusterStatuses.map(clusterStatus => {
+ const healthyCount = clusterStatus.hostStatuses.filter(hostStatus => hostStatus.healthy).length;
+ const unhealthyCount = clusterStatus.hostStatuses.length - healthyCount;
+ return {
+ name: clusterStatus.name,
+ healthyCount,
+ unhealthyCount,
+ hosts: clusterStatus.hostStatuses,
+ };
+ });
+};
+
+interface StatusRowProps {
+ success: boolean;
+ data: any[];
+}
+
+export const StatusRow = ({ success, data }: StatusRowProps) => {
+ const displayData = [...data];
+ const headerValue = displayData.shift();
+ const variant = success ? "success" : "failure";
+ return (
+
+ {headerValue}
+
+
+ );
+};
+
+interface ClustersProps {
+ clusters: IClutch.envoytriage.v1.IClusters;
+}
+
+const Clusters: React.FC = ({ clusters }) => {
+ const [statuses, setStatuses] = React.useState([]);
+
+ React.useEffect(() => {
+ setStatuses(clusterStatuses(clusters));
+ }, [clusters]);
+
+ return (
+
+
+ {_.sortBy(statuses, ["name"]).map(cluster => (
+
+ ) : (
+
+ ),
+ ]}
+ >
+ {cluster.hosts.map(host => {
+ const hostData = { ...host };
+ const { healthy } = hostData;
+ delete hostData.healthy;
+ return (
+
+ );
+ })}
+
+ ))}
+
+
+ );
+};
+
+export default Clusters;
diff --git a/frontend/workflows/envoy/src/remote-triage/dashboard.tsx b/frontend/workflows/envoy/src/remote-triage/dashboard.tsx
new file mode 100644
index 0000000000..99d805be35
--- /dev/null
+++ b/frontend/workflows/envoy/src/remote-triage/dashboard.tsx
@@ -0,0 +1,136 @@
+import React from "react";
+import type { clutch as IClutch } from "@clutch-sh/api";
+import { Grid, MetadataTable, Paper } from "@clutch-sh/core";
+import styled from "@emotion/styled";
+import { Pie } from "@nivo/pie";
+
+const SummaryCardTitle = styled.div({
+ fontWeight: 600,
+ fontSize: "14px",
+ color: "#0D1030",
+});
+
+const SummaryCardBody = styled.div(
+ {
+ fontWeight: "bold",
+ fontSize: "20px",
+ },
+ props => ({
+ color: props.color ? props.color : "#3548D4",
+ })
+);
+
+const FeaturedSummaryContainer = styled(Grid)({
+ flexBasis: "60%",
+});
+
+const PieContainer = styled.div({
+ display: "flex",
+ justifyContent: "space-evenly",
+});
+
+const PieLegendContainer = styled.div({
+ display: "flex",
+ textAlign: "center",
+ justifyContent: "space-evenly",
+ flexDirection: "column",
+});
+
+interface FeaturedSummaryProps {
+ name: string;
+ data: {
+ id: string;
+ value: number;
+ color: string;
+ }[];
+}
+
+const FeaturedSummary = ({ summary }: { summary: FeaturedSummaryProps }) => {
+ const values = summary?.data?.map(d => d.value);
+ const total = values.reduce((t, value) => t + value);
+ return (
+
+
+ {summary.name}
+
+
+
+
+ Total
+ {total}
+
+ {summary?.data?.map(d => (
+
+ {d.id}
+ {d.value}
+
+ ))}
+
+
+
+
+ );
+};
+
+const SummariesContainer = styled(Grid)({
+ textAlign: "center",
+ flexBasis: "40%",
+});
+
+const InformationContainer = styled.div({
+ padding: "16px 0",
+});
+
+interface DashboardProps {
+ serverInfo: IClutch.envoytriage.v1.IServerInfo;
+ featuredSummary: FeaturedSummaryProps;
+ summaries?: {
+ name: string;
+ value: number;
+ }[];
+}
+
+const Dashboard = ({ serverInfo, featuredSummary, summaries }: DashboardProps) => {
+ const INFORMATION_KEYS = [
+ "hot_restart_version",
+ "uptime_all_epochs",
+ "uptime_current_epoch",
+ "version",
+ ];
+
+ const serverData = INFORMATION_KEYS.map(key => {
+ return { name: key, value: serverInfo.value?.[key] };
+ });
+
+ return (
+
+
+
+
+ {summaries.map(summary => (
+
+
+ {summary.name}
+ {summary.value}
+
+
+ ))}
+
+
+
+
+
+
+ );
+};
+
+export default Dashboard;
diff --git a/frontend/workflows/envoy/src/remote-triage.tsx b/frontend/workflows/envoy/src/remote-triage/index.tsx
similarity index 53%
rename from frontend/workflows/envoy/src/remote-triage.tsx
rename to frontend/workflows/envoy/src/remote-triage/index.tsx
index 8b5be39794..d28fad5ced 100644
--- a/frontend/workflows/envoy/src/remote-triage.tsx
+++ b/frontend/workflows/envoy/src/remote-triage/index.tsx
@@ -1,13 +1,22 @@
import React from "react";
import type { clutch as IClutch } from "@clutch-sh/api";
import type { BaseWorkflowProps } from "@clutch-sh/core";
-import { Button, client, MetadataTable, TextField, useWizardContext } from "@clutch-sh/core";
+import {
+ Button,
+ ButtonGroup,
+ client,
+ MetadataTable,
+ Tab,
+ Tabs,
+ TextField,
+ useWizardContext,
+} from "@clutch-sh/core";
import { useDataLayout } from "@clutch-sh/data-layout";
import type { WizardChild } from "@clutch-sh/wizard";
import { Wizard, WizardStep } from "@clutch-sh/wizard";
-import { Typography } from "@material-ui/core";
import Clusters from "./clusters";
+import Dashboard from "./dashboard";
import Listeners from "./listeners";
import Runtime from "./runtime";
import ServerInfo from "./server-info";
@@ -34,7 +43,9 @@ const TriageIdentifier: React.FC = () => {
onChange={e => resourceData.updateData("host", e.target.value)}
onReturn={onSubmit}
/>
-
+
+
+
>
);
};
@@ -42,27 +53,69 @@ const TriageIdentifier: React.FC = () => {
const TriageDetails: React.FC = () => {
const remoteData = useDataLayout("remoteData");
const metadata = remoteData.value.nodeMetadata as IClutch.envoytriage.v1.NodeMetadata;
- const details = remoteData.value.output as IClutch.envoytriage.v1.Result.Output;
+ const { clusters, listeners, runtime, stats, serverInfo } =
+ (remoteData.value?.output as IClutch.envoytriage.v1.Result.Output) || {};
+
+ const failingClusterCount = clusters?.clusterStatuses.filter(
+ cluster => cluster.hostStatuses.filter(host => !host.healthy).length > 0
+ ).length;
+ const healthyClusterCount = clusters?.clusterStatuses.length - failingClusterCount;
+
+ const data = [
+ {
+ id: "Running",
+ value: healthyClusterCount,
+ color: "#69F0AE",
+ },
+ {
+ id: "Failing",
+ value: failingClusterCount,
+ color: "#FF8A80",
+ },
+ ];
+ const dashboardFeaturedSummary = { name: "Clusters", data };
+ const dashboardSummary = [
+ { name: "Listeners", value: listeners?.listenerStatuses?.length || 0 },
+ { name: "Runtime Keys", value: runtime?.entries?.length || 0 },
+ { name: "Stats", value: stats?.stats?.length || 0 },
+ ];
return (
-
-
- {remoteData.value.address?.host}:{remoteData.value.address?.port}
-
-
- {details?.clusters && }
- {details?.listeners && }
- {details?.runtime && }
- {details?.stats && }
- {details?.serverInfo && }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
};
@@ -90,7 +143,7 @@ const RemoteTriage: React.FC = ({ heading }) => {
};
return (
-
+
diff --git a/frontend/workflows/envoy/src/remote-triage/listeners.tsx b/frontend/workflows/envoy/src/remote-triage/listeners.tsx
new file mode 100644
index 0000000000..ea1ef32061
--- /dev/null
+++ b/frontend/workflows/envoy/src/remote-triage/listeners.tsx
@@ -0,0 +1,29 @@
+import React from "react";
+import type { clutch as IClutch } from "@clutch-sh/api";
+import { Table, TableRow } from "@clutch-sh/core";
+import _ from "lodash";
+
+interface ListenersProps {
+ listeners: IClutch.envoytriage.v1.IListeners;
+}
+
+const Listeners: React.FC = ({ listeners }) => {
+ const [statuses, setStatuses] = React.useState([]);
+
+ React.useEffect(() => {
+ setStatuses(listeners.listenerStatuses);
+ }, [listeners]);
+
+ return (
+
+ {_.sortBy(statuses, ["name"]).map(listener => (
+
+ {listener.name}
+ {listener.localAddress}
+
+ ))}
+
+ );
+};
+
+export default Listeners;
diff --git a/frontend/workflows/envoy/src/runtime.tsx b/frontend/workflows/envoy/src/remote-triage/runtime.tsx
similarity index 60%
rename from frontend/workflows/envoy/src/runtime.tsx
rename to frontend/workflows/envoy/src/remote-triage/runtime.tsx
index 6938441212..5cf162fd50 100644
--- a/frontend/workflows/envoy/src/runtime.tsx
+++ b/frontend/workflows/envoy/src/remote-triage/runtime.tsx
@@ -1,6 +1,6 @@
import React from "react";
import type { clutch as IClutch } from "@clutch-sh/api";
-import { ExpansionPanel, TreeTable } from "@clutch-sh/core";
+import { TreeTable } from "@clutch-sh/core";
import _ from "lodash";
interface RuntimeProps {
@@ -9,17 +9,11 @@ interface RuntimeProps {
const Runtime: React.FC = ({ runtime }) => {
const structuredEntries = {};
- let status = "";
runtime.entries.forEach(entry => {
_.setWith(structuredEntries, entry.key, entry.value, Object);
});
- status = `(${runtime.entries.length} total)`;
- return (
-
-
-
- );
+ return ;
};
export default Runtime;
diff --git a/frontend/workflows/envoy/src/server-info.tsx b/frontend/workflows/envoy/src/remote-triage/server-info.tsx
similarity index 55%
rename from frontend/workflows/envoy/src/server-info.tsx
rename to frontend/workflows/envoy/src/remote-triage/server-info.tsx
index 47b56b59ee..c7481f2be0 100644
--- a/frontend/workflows/envoy/src/server-info.tsx
+++ b/frontend/workflows/envoy/src/remote-triage/server-info.tsx
@@ -1,7 +1,19 @@
import React from "react";
import type { clutch as IClutch } from "@clutch-sh/api";
-import { ExpansionPanel, MetadataTable } from "@clutch-sh/core";
-import { TableCell, TableRow } from "@material-ui/core";
+import { MetadataTable } from "@clutch-sh/core";
+import styled from "@emotion/styled";
+
+const Container = styled.div({
+ "> *": {
+ padding: "8px 0",
+ },
+});
+const Title = styled.div({
+ fontWeight: "bold",
+ fontSize: "20px",
+ color: "#0D1030",
+ textTransform: "capitalize",
+});
interface ServerInformation {
// eslint-disable-next-line camelcase
@@ -26,30 +38,22 @@ const ServerInfo: React.FC<{ info: IClutch.envoytriage.v1.IServerInfo }> = ({ in
return localInfo;
}, {});
const cliOptions = rawServerInfo.command_line_options;
- const status = `(${rawServerInfo.state.toLowerCase()})`;
+ const status = `${rawServerInfo.state.toLowerCase()}`;
const serverData = Object.keys(information).map(key => {
return { name: key, value: information[key] };
});
- serverData.push({ name: "Command Line Options", value: "" });
const cliOptionMetadata = Object.keys(cliOptions).map(key => {
return { name: key, value: cliOptions[key] };
});
- const midPoint = Math.floor(cliOptionMetadata.length / 2);
- const variant = "small";
return (
-
-
-
-
-
-
-
-
-
-
-
-
+
+ {status}
+ Stats
+
+ Command Line Options
+
+
);
};
diff --git a/frontend/workflows/envoy/src/stats.tsx b/frontend/workflows/envoy/src/remote-triage/stats.tsx
similarity index 63%
rename from frontend/workflows/envoy/src/stats.tsx
rename to frontend/workflows/envoy/src/remote-triage/stats.tsx
index e0e1aeeb47..f5db9ec7ce 100644
--- a/frontend/workflows/envoy/src/stats.tsx
+++ b/frontend/workflows/envoy/src/remote-triage/stats.tsx
@@ -1,6 +1,6 @@
import React from "react";
import type { clutch as IClutch } from "@clutch-sh/api";
-import { ExpansionPanel, TreeTable } from "@clutch-sh/core";
+import { TreeTable } from "@clutch-sh/core";
import _ from "lodash";
interface StatsProps {
@@ -15,12 +15,7 @@ const Stats: React.FC = ({ stats }) => {
}
});
- const status = `(${stats.stats.length} total)`;
- return (
-
-
-
- );
+ return ;
};
export default Stats;
diff --git a/frontend/workflows/envoy/src/tests/__snapshots__/remote-triage.test.tsx.snap b/frontend/workflows/envoy/src/tests/__snapshots__/remote-triage.test.tsx.snap
index c10aba927b..9dbef92b0c 100644
--- a/frontend/workflows/envoy/src/tests/__snapshots__/remote-triage.test.tsx.snap
+++ b/frontend/workflows/envoy/src/tests/__snapshots__/remote-triage.test.tsx.snap
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Remote Triage workflow renders correctly 1`] = `
-"
+"
"
diff --git a/frontend/workflows/experimentation/package.json b/frontend/workflows/experimentation/package.json
index 77f36e9190..70f0bc103e 100644
--- a/frontend/workflows/experimentation/package.json
+++ b/frontend/workflows/experimentation/package.json
@@ -1,6 +1,6 @@
{
"name": "@clutch-sh/experimentation",
- "version": "0.0.0-beta",
+ "version": "1.0.0-beta",
"description": "Clutch Experimentation Workflows",
"license": "Apache-2.0",
"author": "clutch@lyft.com",
@@ -19,19 +19,19 @@
"test:watch": "yarn test --watch"
},
"dependencies": {
- "@clutch-sh/api": "^0.0.0-beta",
- "@clutch-sh/core": "^0.0.0-beta",
- "@clutch-sh/data-layout": "^0.0.0-beta",
+ "@clutch-sh/api": "^1.0.0-beta",
+ "@clutch-sh/core": "^1.0.0-beta",
+ "@clutch-sh/data-layout": "^1.0.0-beta",
+ "@emotion/styled": "^11.0.0",
"@material-ui/core": "^4.11.0",
"history": "^5.0.0",
"react": "^16.8.0",
"react-dom": "^16.8.0",
"react-is": "^16.8.0",
"react-router-dom": "^6.0.0-beta",
- "styled-components": "^5.1.1",
"yup": "^0.31.0"
},
"devDependencies": {
- "@clutch-sh/tools": "^0.0.0-beta"
+ "@clutch-sh/tools": "^1.0.0-beta"
}
}
diff --git a/frontend/workflows/experimentation/src/core/page-layout.tsx b/frontend/workflows/experimentation/src/core/page-layout.tsx
index a1a83ad061..6802c8c4a5 100644
--- a/frontend/workflows/experimentation/src/core/page-layout.tsx
+++ b/frontend/workflows/experimentation/src/core/page-layout.tsx
@@ -1,20 +1,17 @@
import React from "react";
import { Error } from "@clutch-sh/core";
+import styled from "@emotion/styled";
import { Container, Grid, Typography } from "@material-ui/core";
-import styled from "styled-components";
-const Heading = styled(Typography)`
- padding-left: 1.25rem;
-`;
+const PageContainer = styled.div({
+ display: "flex",
+ flex: "1 auto",
+ margin: "30px",
+});
-const Spacer = styled.div`
- margin: 30px;
-`;
-
-const SizedGrid = styled(Grid)`
- width: 100%;
- padding: 24px;
-`;
+const Heading = styled(Typography)({
+ padding: "16px 0",
+});
interface PageLayoutProps {
heading: string;
@@ -24,15 +21,15 @@ interface PageLayoutProps {
const PageLayout: React.FC = ({ heading, error, children }) => {
const hasError = error !== undefined && error !== "" && error !== null;
return (
-
- {hasError && }
-
+
+
{heading}
- {children}
+ {hasError && }
+ {children}
-
+
);
};
diff --git a/frontend/workflows/experimentation/src/list-experiments.tsx b/frontend/workflows/experimentation/src/list-experiments.tsx
index a0b4afcbdc..1b1b560a7b 100644
--- a/frontend/workflows/experimentation/src/list-experiments.tsx
+++ b/frontend/workflows/experimentation/src/list-experiments.tsx
@@ -1,7 +1,7 @@
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import type { clutch as IClutch } from "@clutch-sh/api";
-import { BaseWorkflowProps, ButtonGroup, client } from "@clutch-sh/core";
+import { BaseWorkflowProps, Button, ButtonGroup, client } from "@clutch-sh/core";
import PageLayout from "./core/page-layout";
import { Column, ListView } from "./list-view";
@@ -39,16 +39,15 @@ const ListExperiments: React.FC = ({ heading, columns, lin
});
}, []);
- const buttons = links.map(link => {
- return {
- text: link.displayName,
- onClick: () => navigate(link.path),
- };
- });
+ const buttons = links.map(link => (
+