|
|
@@ -1,173 +1,280 @@
|
|
|
import React, { Component } from "react";
|
|
|
import styled from "styled-components";
|
|
|
|
|
|
-import { Section, FormElement } from "../../shared/types";
|
|
|
-import { Context } from "../../shared/Context";
|
|
|
+import { Section, FormElement } from "shared/types";
|
|
|
+import { Context } from "shared/Context";
|
|
|
+import TabRegion from "components/TabRegion";
|
|
|
+import ValuesForm from "components/values-form/ValuesForm";
|
|
|
|
|
|
import SaveButton from "../SaveButton";
|
|
|
|
|
|
type PropsType = {
|
|
|
- formTabs: any;
|
|
|
- onSubmit: (formValues: any) => void;
|
|
|
- disabled?: boolean;
|
|
|
+ showStateDebugger?: boolean;
|
|
|
+ formData: any;
|
|
|
+ onSubmit?: (formValues: any) => void;
|
|
|
saveValuesStatus?: string | null;
|
|
|
isInModal?: boolean;
|
|
|
- currentTab?: string; // For resetting state when flipping b/w tabs in ExpandedChart
|
|
|
- renderSaveButton?: boolean;
|
|
|
- overrideValues?: any;
|
|
|
+ renderTabContents?: (currentTab: string) => any;
|
|
|
+ tabOptions: any[];
|
|
|
+ // overrideValues?: any;
|
|
|
};
|
|
|
|
|
|
-type StateType = any;
|
|
|
-
|
|
|
-const providerMap: any = {
|
|
|
- gke: "gcp",
|
|
|
- eks: "aws",
|
|
|
- doks: "do",
|
|
|
+type StateType = {
|
|
|
+ currentTab: string;
|
|
|
+ tabOptions: { value: string, label: string }[];
|
|
|
+ metaState: any;
|
|
|
};
|
|
|
|
|
|
-// Manages the consolidated state of all form tabs ("metastate")
|
|
|
-export default class ValuesWrapper extends Component<PropsType, StateType> {
|
|
|
- // No need to render, so OK to set as class variable outside of state
|
|
|
- requiredFields: string[] = [];
|
|
|
+export default class FormWrapper extends Component<PropsType, StateType> {
|
|
|
+ state = {
|
|
|
+ currentTab: "",
|
|
|
+ tabOptions: null as { value: string, label: string }[],
|
|
|
+ metaState: {},
|
|
|
+ }
|
|
|
|
|
|
- updateFormState() {
|
|
|
+ updateTabs = () => {
|
|
|
+ let tabOptions = [] as { value: string, label: string }[];
|
|
|
+ let tabs = this.props.formData?.tabs;
|
|
|
+ let requiredFields = [] as string[];
|
|
|
let metaState: any = {};
|
|
|
- this.props.formTabs.forEach((tab: any, i: number) => {
|
|
|
- // TODO: reconcile tab.name and tab.value
|
|
|
- if (tab.name || (tab.value && tab.value.includes("@"))) {
|
|
|
- tab.sections.forEach((section: Section, i: number) => {
|
|
|
- section.contents.forEach((item: FormElement, i: number) => {
|
|
|
- // If no name is assigned use values.yaml variable as identifier
|
|
|
- let key = item.name || item.variable;
|
|
|
-
|
|
|
- let def =
|
|
|
- item.settings && item.settings.unit
|
|
|
- ? `${item.settings.default}${item.settings.unit}`
|
|
|
- : item.settings.default;
|
|
|
- def = (item.value && item.value[0]) || def;
|
|
|
-
|
|
|
- if (item.type === "checkbox") {
|
|
|
- def = item.value[0];
|
|
|
- }
|
|
|
-
|
|
|
- // Handle add to list of required fields
|
|
|
- if (item.required) {
|
|
|
- key && this.requiredFields.push(key);
|
|
|
- }
|
|
|
-
|
|
|
- switch (item.type) {
|
|
|
- case "checkbox":
|
|
|
- metaState[key] = def ? def : false;
|
|
|
- break;
|
|
|
- case "string-input":
|
|
|
- metaState[key] = def ? def : "";
|
|
|
- break;
|
|
|
- case "string-input-password":
|
|
|
- metaState[key] = def ? def : item.settings.default;
|
|
|
- case "array-input":
|
|
|
- metaState[key] = def ? def : [];
|
|
|
- break;
|
|
|
- case "env-key-value-array":
|
|
|
- metaState[key] = def ? def : {};
|
|
|
- break;
|
|
|
- case "key-value-array":
|
|
|
- metaState[key] = def ? def : {};
|
|
|
- break;
|
|
|
- case "number-input":
|
|
|
- metaState[key] = def.toString() ? def : "";
|
|
|
- break;
|
|
|
- case "select":
|
|
|
- metaState[key] = def ? def : item.settings.options[0].value;
|
|
|
- break;
|
|
|
- case "provider-select":
|
|
|
- def = providerMap[this.context.currentCluster.service];
|
|
|
- metaState[key] = def ? def : "aws";
|
|
|
- break;
|
|
|
- case "base-64":
|
|
|
- metaState[key] = def ? def : "";
|
|
|
- case "base-64-password":
|
|
|
- metaState[key] = def ? def : "";
|
|
|
- default:
|
|
|
- }
|
|
|
+ if (tabs) {
|
|
|
+ tabs.forEach((tab: any, i: number) => {
|
|
|
+ if (tab.name && tab.label) {
|
|
|
+
|
|
|
+ // If a tab is valid, first extract state
|
|
|
+ tab.sections.forEach((section: Section, i: number) => {
|
|
|
+ section.contents.forEach((item: FormElement, i: number) => {
|
|
|
+
|
|
|
+ // If no name is assigned use values.yaml variable as identifier
|
|
|
+ let key = item.name || item.variable;
|
|
|
+
|
|
|
+ let def =
|
|
|
+ item.settings && item.settings.unit
|
|
|
+ ? `${item.settings.default}${item.settings.unit}`
|
|
|
+ : item.settings?.default;
|
|
|
+ def = (item.value && item.value[0]) || def;
|
|
|
+
|
|
|
+ if (item.type === "checkbox") {
|
|
|
+ def = item.value && item.value[0];
|
|
|
+ }
|
|
|
+
|
|
|
+ // Handle add to list of required fields
|
|
|
+ if (item.required && key) {
|
|
|
+ requiredFields.push(key);
|
|
|
+ }
|
|
|
+
|
|
|
+ let value: any = def;
|
|
|
+ switch (item.type) {
|
|
|
+ case "checkbox":
|
|
|
+ value = def || false;
|
|
|
+ break;
|
|
|
+ case "string-input":
|
|
|
+ value = def || "";
|
|
|
+ break;
|
|
|
+ case "string-input-password":
|
|
|
+ value = def || item.settings.default;
|
|
|
+ case "array-input":
|
|
|
+ value = def || [];
|
|
|
+ break;
|
|
|
+ case "env-key-value-array":
|
|
|
+ value = def || {};
|
|
|
+ break;
|
|
|
+ case "key-value-array":
|
|
|
+ value = def || {};
|
|
|
+ break;
|
|
|
+ case "number-input":
|
|
|
+ value = def.toString() ? def : "";
|
|
|
+ break;
|
|
|
+ case "select":
|
|
|
+ value = def || item.settings.options[0].value;
|
|
|
+ break;
|
|
|
+ case "provider-select":
|
|
|
+ let providerMap: any = {
|
|
|
+ gke: "gcp",
|
|
|
+ eks: "aws",
|
|
|
+ doks: "do",
|
|
|
+ };
|
|
|
+ def = providerMap[this.context.currentCluster.service];
|
|
|
+ value = def || "aws";
|
|
|
+ break;
|
|
|
+ case "base-64":
|
|
|
+ value = def || "";
|
|
|
+ case "base-64-password":
|
|
|
+ value = def || "";
|
|
|
+ default:
|
|
|
+ }
|
|
|
+ if (value !== null && value !== undefined) {
|
|
|
+ metaState[key] = { value };
|
|
|
+ }
|
|
|
+ });
|
|
|
});
|
|
|
- });
|
|
|
- }
|
|
|
- });
|
|
|
- this.setState(metaState);
|
|
|
+ tabOptions.push({ value: tab.name, label: tab.label });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ if (this.props.tabOptions.length > 0) {
|
|
|
+ tabOptions = tabOptions.concat(this.props.tabOptions);
|
|
|
+ }
|
|
|
+ if (tabOptions.length > 0) {
|
|
|
+ this.setState({
|
|
|
+ tabOptions: tabOptions,
|
|
|
+ currentTab: tabOptions[0].value,
|
|
|
+ metaState,
|
|
|
+ });
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- // Initialize corresponding state fields for form blocks
|
|
|
componentDidMount() {
|
|
|
- this.updateFormState();
|
|
|
+ this.updateTabs();
|
|
|
}
|
|
|
|
|
|
- componentDidUpdate(prevProps: PropsType) {
|
|
|
+ componentDidUpdate(prevProps: any) {
|
|
|
if (
|
|
|
- this.props.formTabs !== prevProps.formTabs ||
|
|
|
- this.props.currentTab !== prevProps.currentTab
|
|
|
+ prevProps.tabOptions !== this.props.tabOptions ||
|
|
|
+ prevProps.formData !== this.props.formData
|
|
|
) {
|
|
|
- this.updateFormState();
|
|
|
- }
|
|
|
- if (this.props.overrideValues !== prevProps.overrideValues) {
|
|
|
- this.setState({ ...this.props.overrideValues });
|
|
|
+ this.updateTabs();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // Checks if all required fields are set
|
|
|
- isDisabled = (): boolean => {
|
|
|
- let valueIndicators: any[] = [];
|
|
|
- this.requiredFields.forEach((field: string, i: number) => {
|
|
|
- valueIndicators.push(this.state[field] && true);
|
|
|
- });
|
|
|
- return valueIndicators.includes(false) || valueIndicators.includes("");
|
|
|
- };
|
|
|
-
|
|
|
- renderButton = () => {
|
|
|
- if (this.props.renderSaveButton) {
|
|
|
- let { formTabs, currentTab } = this.props;
|
|
|
- let tab = formTabs.find(
|
|
|
- (t: any) => t.name === currentTab || t.value === currentTab
|
|
|
- );
|
|
|
- if (tab && tab.context && tab.context.type === "helm/values") {
|
|
|
+ isDisabled = () => {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ renderTabContents = () => {
|
|
|
+ let tabs = this.props.formData?.tabs;
|
|
|
+ if (tabs) {
|
|
|
+ let matchedTab = null as any;
|
|
|
+ tabs.forEach((tab: any, i: number) => {
|
|
|
+ if (tab.name === this.state.currentTab) {
|
|
|
+ matchedTab = tab;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ if (matchedTab) {
|
|
|
return (
|
|
|
- <SaveButton
|
|
|
- disabled={this.isDisabled() || this.props.disabled}
|
|
|
- text="Deploy"
|
|
|
- onClick={() => this.props.onSubmit(this.state)}
|
|
|
- status={
|
|
|
- this.isDisabled()
|
|
|
- ? "Missing required fields"
|
|
|
- : this.props.saveValuesStatus
|
|
|
- }
|
|
|
- makeFlush={true}
|
|
|
+ <ValuesForm
|
|
|
+ metaState={this.state.metaState}
|
|
|
+ setMetaState={(key: string, value: any) => {
|
|
|
+ let metaState: any = this.state.metaState;
|
|
|
+ metaState[key] = { value };
|
|
|
+ this.setState({ metaState });
|
|
|
+ }}
|
|
|
+ sections={matchedTab.sections}
|
|
|
/>
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
- };
|
|
|
|
|
|
- render() {
|
|
|
- let renderFunc: any = this.props.children;
|
|
|
- if (this.props.isInModal) {
|
|
|
+ // If no form tabs match, check against external tabs
|
|
|
+ if (this.props.renderTabContents) {
|
|
|
+ return this.props.renderTabContents(this.state.currentTab);
|
|
|
+ } else {
|
|
|
+ return <h1>nothin</h1>
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ renderStateDebugger = () => {
|
|
|
+ if (this.props.showStateDebugger) {
|
|
|
return (
|
|
|
- <StyledValuesWrapper>
|
|
|
- {renderFunc(this.state, (x: any) => this.setState(x))}
|
|
|
- {this.renderButton()}
|
|
|
- </StyledValuesWrapper>
|
|
|
- );
|
|
|
+ <>
|
|
|
+ <StateDisplay>
|
|
|
+ <Header>FormWrapper State</Header>
|
|
|
+ <ScrollWrapper>
|
|
|
+ {JSON.stringify(this.state.metaState, undefined, 2)}
|
|
|
+ </ScrollWrapper>
|
|
|
+ </StateDisplay>
|
|
|
+ </>
|
|
|
+ )
|
|
|
}
|
|
|
+ }
|
|
|
+
|
|
|
+ renderContents = () => {
|
|
|
return (
|
|
|
- <PaddedWrapper>
|
|
|
- <StyledValuesWrapper>
|
|
|
- {renderFunc(this.state, (x: any) => this.setState(x))}
|
|
|
- {this.renderButton()}
|
|
|
- </StyledValuesWrapper>
|
|
|
- </PaddedWrapper>
|
|
|
+ <>
|
|
|
+ <TabWrapper>
|
|
|
+ <TabRegion
|
|
|
+ options={this.state.tabOptions}
|
|
|
+ currentTab={this.state.currentTab}
|
|
|
+ setCurrentTab={(x: string) => this.setState({ currentTab: x })}
|
|
|
+ >
|
|
|
+ {this.renderTabContents()}
|
|
|
+ </TabRegion>
|
|
|
+ </TabWrapper>
|
|
|
+ <SaveButton
|
|
|
+ disabled={this.isDisabled()}
|
|
|
+ text="Deploy"
|
|
|
+ onClick={() => this.props.onSubmit(this.state)}
|
|
|
+ status={
|
|
|
+ this.isDisabled()
|
|
|
+ ? "Missing required fields"
|
|
|
+ : this.props.saveValuesStatus
|
|
|
+ }
|
|
|
+ makeFlush={true}
|
|
|
+ />
|
|
|
+ {this.renderStateDebugger()}
|
|
|
+ </>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ render() {
|
|
|
+ return (
|
|
|
+ <>
|
|
|
+ {
|
|
|
+ this.props.isInModal ? (
|
|
|
+ <StyledValuesWrapper>
|
|
|
+ {this.renderContents()}
|
|
|
+ </StyledValuesWrapper>
|
|
|
+ ) : (
|
|
|
+ <PaddedWrapper>
|
|
|
+ <StyledValuesWrapper>
|
|
|
+ {this.renderContents()}
|
|
|
+ </StyledValuesWrapper>
|
|
|
+ </PaddedWrapper>
|
|
|
+ )
|
|
|
+ }
|
|
|
+ </>
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-ValuesWrapper.contextType = Context;
|
|
|
+FormWrapper.contextType = Context;
|
|
|
+
|
|
|
+const TabWrapper = styled.div`
|
|
|
+ min-height: 100px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+`;
|
|
|
+
|
|
|
+const ScrollWrapper = styled.div`
|
|
|
+ padding: 20px;
|
|
|
+ overflow-y: auto;
|
|
|
+ max-height: 400px;
|
|
|
+ padding-top: 15px;
|
|
|
+`;
|
|
|
+
|
|
|
+const Header = styled.div`
|
|
|
+ width: 100%;
|
|
|
+ height: 40px;
|
|
|
+ color: #ffffff;
|
|
|
+ font-weight: 500;
|
|
|
+ padding-left: 17px;
|
|
|
+ background: #00000022;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+`;
|
|
|
+
|
|
|
+const StateDisplay = styled.pre`
|
|
|
+ width: 100%;
|
|
|
+ font-size: 13px;
|
|
|
+ display:
|
|
|
+ overflow: hidden;
|
|
|
+ border-radius: 5px;
|
|
|
+ position: relative;
|
|
|
+ line-height: 1.5em;
|
|
|
+ color: #aaaabb;
|
|
|
+ background: #ffffff11;
|
|
|
+`;
|
|
|
|
|
|
const StyledValuesWrapper = styled.div`
|
|
|
width: 100%;
|