|
|
@@ -1,519 +0,0 @@
|
|
|
-import React, { Component } from "react";
|
|
|
-import styled from "styled-components";
|
|
|
-import _ from "lodash";
|
|
|
-
|
|
|
-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 = {
|
|
|
- formData: any;
|
|
|
- onSubmit?: (formValues: any) => void;
|
|
|
- saveValuesStatus?: string | null;
|
|
|
- saveButtonText?: string | null;
|
|
|
-
|
|
|
- // Handle additional non-form tabs
|
|
|
- // TODO: find cleaner way to share submitValues w/ rerun jobs button
|
|
|
- renderTabContents?: (currentTab: string, submitValues?: any) => any;
|
|
|
- tabOptions?: any[];
|
|
|
- tabOptionsOnly?: boolean;
|
|
|
-
|
|
|
- // Allow external control of state
|
|
|
- valuesToOverride?: any;
|
|
|
- clearValuesToOverride?: () => void;
|
|
|
-
|
|
|
- // External values made available to all child components
|
|
|
- externalValues?: any;
|
|
|
-
|
|
|
- // Display and debugger settings
|
|
|
- isInModal?: boolean;
|
|
|
- isReadOnly?: boolean;
|
|
|
- showStateDebugger?: boolean;
|
|
|
-
|
|
|
- // TabRegion props to pass through
|
|
|
- color?: string;
|
|
|
- addendum?: any;
|
|
|
-};
|
|
|
-
|
|
|
-type StateType = {
|
|
|
- metaState: any;
|
|
|
- requiredFields: string[];
|
|
|
- currentTab: string;
|
|
|
- tabOptions: { value: string; label: string }[];
|
|
|
-};
|
|
|
-
|
|
|
-/**
|
|
|
- * Renders from raw JSON form data and manages form state.
|
|
|
- *
|
|
|
- * To control values using external state prop in "valuesToOverride" (refer to
|
|
|
- * FormDebugger or LaunchTemplate for example usage).
|
|
|
- *
|
|
|
- * TODO: Handle passing in valuesToOverride at same time as formData
|
|
|
- */
|
|
|
-export default class FormWrapper extends Component<PropsType, StateType> {
|
|
|
- state = {
|
|
|
- metaState: {} as any,
|
|
|
- requiredFields: [] as string[],
|
|
|
- currentTab: "",
|
|
|
- tabOptions: [] as { value: string; label: string }[],
|
|
|
- };
|
|
|
-
|
|
|
- updateTabs = (resetState?: boolean, callback?: any) => {
|
|
|
- if (resetState) {
|
|
|
- let tabOptions = [] as { value: string; label: string }[];
|
|
|
- let tabs = this.props.formData?.tabs;
|
|
|
- let requiredFields = [] as string[];
|
|
|
- let metaState: any = {
|
|
|
- "currentCluster.service.is_gcp": {
|
|
|
- value: this.context.currentCluster.service == "gke",
|
|
|
- },
|
|
|
- "currentCluster.service.is_aws": {
|
|
|
- value: this.context.currentCluster.service == "eks",
|
|
|
- },
|
|
|
- "currentCluster.service.is_do": {
|
|
|
- value: this.context.currentCluster.service == "doks",
|
|
|
- },
|
|
|
- };
|
|
|
- tabs?.forEach((tab: any, i: number) => {
|
|
|
- // Exclude value if omitFromLaunch is set
|
|
|
- let omit =
|
|
|
- tab.settings?.omitFromLaunch && this.props.externalValues?.isLaunch;
|
|
|
- if (tab?.name && tab.label && !omit) {
|
|
|
- // If a tab is valid, extract state
|
|
|
- tab.sections?.forEach((section: Section, i: number) => {
|
|
|
- section?.contents?.forEach((item: FormElement, i: number) => {
|
|
|
- if (item === null || item === undefined) {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- if (
|
|
|
- item.type === "variable" &&
|
|
|
- item.variable &&
|
|
|
- item.settings?.default
|
|
|
- ) {
|
|
|
- metaState[item.variable] = { value: item.settings.default };
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // 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.omitUnitFromValue
|
|
|
- ? `${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 };
|
|
|
- }
|
|
|
- });
|
|
|
- });
|
|
|
- if (!this.props.tabOptionsOnly) {
|
|
|
- tabOptions.push({ value: tab.name, label: tab.label });
|
|
|
- }
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- if (this.props.tabOptions?.length > 0) {
|
|
|
- let prependTabs = [] as { value: string; label: string }[];
|
|
|
- let appendTabs = [] as { value: string; label: string }[];
|
|
|
- this.props.tabOptions.forEach(
|
|
|
- (tab: { value: string; label: string }) => {
|
|
|
- if (tab.value === "status" || tab.value === "metrics") {
|
|
|
- prependTabs.push(tab);
|
|
|
- } else {
|
|
|
- appendTabs.push(tab);
|
|
|
- }
|
|
|
- }
|
|
|
- );
|
|
|
- tabOptions = prependTabs.concat(tabOptions.concat(appendTabs));
|
|
|
- }
|
|
|
-
|
|
|
- if (tabOptions.length > 0) {
|
|
|
- this.setState(
|
|
|
- {
|
|
|
- tabOptions: tabOptions,
|
|
|
- currentTab:
|
|
|
- this.state.currentTab === ""
|
|
|
- ? tabOptions[0].value
|
|
|
- : this.state.currentTab,
|
|
|
- metaState,
|
|
|
- requiredFields: requiredFields,
|
|
|
- },
|
|
|
- callback
|
|
|
- );
|
|
|
- } else {
|
|
|
- this.setState({ tabOptions }, callback);
|
|
|
- }
|
|
|
- } else {
|
|
|
- // TODO: refactor by consolidating w/ above
|
|
|
- // Handle change only to external tabs (e.g. DevOps mode toggle)
|
|
|
- let tabOptions = [] as { value: string; label: string }[];
|
|
|
- let tabs = this.props.formData?.tabs;
|
|
|
- tabs?.forEach((tab: any, i: number) => {
|
|
|
- if (tab?.name && tab.label) {
|
|
|
- tabOptions.push({ value: tab.name, label: tab.label });
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- if (this.props.tabOptions?.length > 0) {
|
|
|
- let prependTabs = [] as { value: string; label: string }[];
|
|
|
- let appendTabs = [] as { value: string; label: string }[];
|
|
|
- this.props.tabOptions.forEach(
|
|
|
- (tab: { value: string; label: string }) => {
|
|
|
- if (tab.value === "status" || tab.value === "metrics") {
|
|
|
- prependTabs.push(tab);
|
|
|
- } else {
|
|
|
- appendTabs.push(tab);
|
|
|
- }
|
|
|
- }
|
|
|
- );
|
|
|
- tabOptions = prependTabs.concat(tabOptions.concat(appendTabs));
|
|
|
- }
|
|
|
- this.setState({ tabOptions }, callback);
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- componentDidMount() {
|
|
|
- this.updateTabs(true, () => {
|
|
|
- this.setState(
|
|
|
- {
|
|
|
- metaState: {
|
|
|
- ...this.state.metaState,
|
|
|
- ...this.props.valuesToOverride,
|
|
|
- },
|
|
|
- },
|
|
|
- () => {
|
|
|
- this.props.clearValuesToOverride &&
|
|
|
- this.props.clearValuesToOverride();
|
|
|
- }
|
|
|
- );
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- componentDidUpdate(prevProps: any) {
|
|
|
- // Override metaState values set from outside FormWrapper
|
|
|
- if (
|
|
|
- this.props.valuesToOverride &&
|
|
|
- !_.isEmpty(this.props.valuesToOverride) &&
|
|
|
- !_.isEqual(prevProps.valuesToOverride, this.props.valuesToOverride)
|
|
|
- ) {
|
|
|
- this.setState(
|
|
|
- {
|
|
|
- metaState: {
|
|
|
- ...this.state.metaState,
|
|
|
- ...this.props.valuesToOverride,
|
|
|
- },
|
|
|
- },
|
|
|
- () => {
|
|
|
- // Seems redundant with below but need to ensure no leaked state updates
|
|
|
- if (
|
|
|
- !_.isEqual(prevProps.tabOptions, this.props.tabOptions) ||
|
|
|
- !_.isEqual(prevProps.formData, this.props.formData)
|
|
|
- ) {
|
|
|
- let formHasChanged = !_.isEqual(
|
|
|
- prevProps.formData,
|
|
|
- this.props.formData
|
|
|
- );
|
|
|
- this.updateTabs(formHasChanged);
|
|
|
- }
|
|
|
- this.props.clearValuesToOverride &&
|
|
|
- this.props.clearValuesToOverride();
|
|
|
- }
|
|
|
- );
|
|
|
- } else if (
|
|
|
- !_.isEqual(prevProps.tabOptions, this.props.tabOptions) ||
|
|
|
- !_.isEqual(prevProps.formData, this.props.formData)
|
|
|
- ) {
|
|
|
- if (
|
|
|
- prevProps.tabOptions?.length === 0 &&
|
|
|
- !_.isEqual(prevProps.tabOptions, this.props.tabOptions)
|
|
|
- ) {
|
|
|
- this.setState({ currentTab: "status" });
|
|
|
- }
|
|
|
- let formHasChanged = !_.isEqual(prevProps.formData, this.props.formData);
|
|
|
- this.updateTabs(formHasChanged);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- isSet = (value: any) => {
|
|
|
- if (
|
|
|
- value === null ||
|
|
|
- value === undefined ||
|
|
|
- value === "" ||
|
|
|
- value === false
|
|
|
- ) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- return true;
|
|
|
- };
|
|
|
-
|
|
|
- isDisabled = () => {
|
|
|
- if (this.props.saveValuesStatus == "loading") {
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- let requiredMissing = false;
|
|
|
- this.state.requiredFields?.forEach((requiredKey: string, i: number) => {
|
|
|
- if (!this.isSet(this.state.metaState[requiredKey]?.value)) {
|
|
|
- requiredMissing = true;
|
|
|
- }
|
|
|
- });
|
|
|
- return requiredMissing;
|
|
|
- };
|
|
|
-
|
|
|
- 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 (
|
|
|
- <ValuesForm
|
|
|
- externalValues={this.props.externalValues}
|
|
|
- disabled={this.props.isReadOnly}
|
|
|
- metaState={this.state.metaState}
|
|
|
- setMetaState={(key: string, value: any) => {
|
|
|
- let metaState: any = this.state.metaState;
|
|
|
- metaState[key] = { value };
|
|
|
- this.setState({ metaState });
|
|
|
- }}
|
|
|
- sections={matchedTab.sections}
|
|
|
- />
|
|
|
- );
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // If no form tabs match, check against external tabs
|
|
|
- if (this.props.renderTabContents) {
|
|
|
- // TODO: find a cleaner way to share submissionValues w/ rerun button
|
|
|
- let submissionValues: any = {};
|
|
|
- Object.keys(this.state.metaState)?.forEach((key: string, i: number) => {
|
|
|
- submissionValues[key] = this.state.metaState[key]?.value;
|
|
|
- });
|
|
|
-
|
|
|
- return this.props.renderTabContents(
|
|
|
- this.state.currentTab,
|
|
|
- submissionValues
|
|
|
- );
|
|
|
- }
|
|
|
- return <div>No matched tabs found.</div>;
|
|
|
- };
|
|
|
-
|
|
|
- renderStateDebugger = () => {
|
|
|
- if (this.props.showStateDebugger) {
|
|
|
- return (
|
|
|
- <>
|
|
|
- <StateDisplay>
|
|
|
- <Header>FormWrapper State</Header>
|
|
|
- <ScrollWrapper>
|
|
|
- {JSON.stringify(this.state.metaState, undefined, 2)}
|
|
|
- </ScrollWrapper>
|
|
|
- </StateDisplay>
|
|
|
- </>
|
|
|
- );
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- handleSubmit = () => {
|
|
|
- // Extract metaState values
|
|
|
- let submissionValues: any = {};
|
|
|
- Object.keys(this.state.metaState)?.forEach((key: string, i: number) => {
|
|
|
- submissionValues[key] = this.state.metaState[key]?.value;
|
|
|
- });
|
|
|
-
|
|
|
- this.props.onSubmit && this.props.onSubmit(submissionValues);
|
|
|
- };
|
|
|
-
|
|
|
- showSaveButton = (): boolean => {
|
|
|
- if (this.props.isReadOnly || this.state.tabOptions?.length === 0) {
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- 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 true;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // Check if current tab is among non-form tab options
|
|
|
- let nonFormTabValues = this.props.tabOptions?.map((tab: any, i: number) => {
|
|
|
- return tab.value;
|
|
|
- });
|
|
|
- if (nonFormTabValues && nonFormTabValues.includes(this.state.currentTab)) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- return true;
|
|
|
- };
|
|
|
-
|
|
|
- renderContents = (showSave: boolean) => {
|
|
|
- return (
|
|
|
- <>
|
|
|
- <TabRegion
|
|
|
- options={this.state.tabOptions}
|
|
|
- currentTab={this.state.currentTab}
|
|
|
- setCurrentTab={(x: string) => this.setState({ currentTab: x })}
|
|
|
- addendum={this.props.addendum}
|
|
|
- color={this.props.color}
|
|
|
- >
|
|
|
- {this.renderTabContents()}
|
|
|
- </TabRegion>
|
|
|
- {showSave && (
|
|
|
- <SaveButton
|
|
|
- disabled={this.isDisabled()}
|
|
|
- text={this.props.saveButtonText || "Deploy"}
|
|
|
- onClick={this.handleSubmit}
|
|
|
- status={
|
|
|
- this.isDisabled() && this.props.saveValuesStatus != "loading"
|
|
|
- ? "Missing required fields"
|
|
|
- : this.props.saveValuesStatus
|
|
|
- }
|
|
|
- makeFlush={!this.props.isInModal}
|
|
|
- />
|
|
|
- )}
|
|
|
- {this.renderStateDebugger()}
|
|
|
- </>
|
|
|
- );
|
|
|
- };
|
|
|
-
|
|
|
- render() {
|
|
|
- let showSave = this.showSaveButton();
|
|
|
- return (
|
|
|
- <>
|
|
|
- {this.props.isInModal || !showSave ? (
|
|
|
- <StyledValuesWrapper showSave={showSave}>
|
|
|
- {this.renderContents(showSave)}
|
|
|
- </StyledValuesWrapper>
|
|
|
- ) : (
|
|
|
- <PaddedWrapper>
|
|
|
- <StyledValuesWrapper showSave={showSave}>
|
|
|
- {this.renderContents(showSave)}
|
|
|
- </StyledValuesWrapper>
|
|
|
- </PaddedWrapper>
|
|
|
- )}
|
|
|
- </>
|
|
|
- );
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-FormWrapper.contextType = Context;
|
|
|
-
|
|
|
-const Spacer = styled.div`
|
|
|
- width: 100%;
|
|
|
- height: 200px;
|
|
|
- background: red;
|
|
|
- position: relative;
|
|
|
-`;
|
|
|
-
|
|
|
-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: 300px;
|
|
|
- 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<{ showSave: boolean }>`
|
|
|
- width: 100%;
|
|
|
- padding: 0;
|
|
|
- height: ${(props) => (props.showSave ? "calc(100% - 55px)" : "100%")};
|
|
|
-`;
|
|
|
-
|
|
|
-const PaddedWrapper = styled.div`
|
|
|
- padding-bottom: 65px;
|
|
|
- position: relative;
|
|
|
-`;
|