github.com/replicatedhq/ship@v0.55.0/web/init/src/components/config_only/ConfigOnly.jsx (about)

     1  import React from "react";
     2  import ErrorBoundary from "../../ErrorBoundary";
     3  import each from 'lodash/each';
     4  import isEmpty from 'lodash/isEmpty';
     5  import map from 'lodash/map';
     6  import partialRight from 'lodash/partialRight';
     7  import omit from 'lodash/omit';
     8  
     9  import { ConfigService } from "../../services/ConfigService";
    10  
    11  import Layout from "../../Layout";
    12  import ConfigRender from "../config_render/ConfigRender";
    13  import Toast from "../shared/Toast";
    14  import Loader from "../shared/Loader";
    15  
    16  const EDITABLE_ITEM_TYPES = [
    17    "select", "text", "textarea", "password", "file", "bool", "select_many", "select_one",
    18  ];
    19  
    20  export default class ConfigOnly extends React.Component {
    21  
    22    constructor(props) {
    23      super(props);
    24      this.state = {
    25        toastDetails: {
    26          opts: {}
    27        },
    28      };
    29    }
    30  
    31    componentDidMount() {
    32      if (!this.props.settingsFieldsList.length) {
    33        this.props.getApplicationSettings({item_values: null});
    34      }
    35    }
    36  
    37    componentDidUpdate(lastProps) {
    38      if (this.props.phase !== lastProps.phase) {
    39        if (this.props.phase !== "render.config") {
    40          this.props.history.push("/");
    41        }
    42      }
    43      if (this.props.settingsFields !== lastProps.settingsFields) {
    44        const data = this.getData(this.props.settingsFields);
    45        this.setState({ itemData: data });
    46      }
    47    }
    48  
    49    getData = (groups) => {
    50      const getItemData = (item) => {
    51        let data = {
    52          name: item.name,
    53          value: item.value,
    54          multi_value: item.multi_value,
    55        };
    56        if (item.multiple) {
    57          if (item.multi_value && item.multi_value.length) {
    58            data.multi_value = item.multi_value;
    59          } else if (item.default) {
    60            data.multi_value = [item.default];
    61          }
    62        } else {
    63          if (item.value && item.value.length) {
    64            data.value = item.value;
    65          } else {
    66            data.value = item.default;
    67          }
    68        }
    69        if (item.type === "file") {
    70          data.data = "";
    71          if (item.multiple) {
    72            if (item.multi_data && item.multi_data.length) {
    73              data.multi_data = item.multi_data;
    74            } else {
    75              data.multi_data = [];
    76            }
    77          } else {
    78            data.data = item.data;
    79          }
    80        }
    81        return data;
    82      };
    83  
    84      let data = [];
    85      each(groups, (group) => {
    86        if (ConfigService.isEnabled(groups, group)) {
    87          each(group.items, (item) => {
    88            if (ConfigService.isEnabled(groups, item)) {
    89              if (item.type !== "select_many") {
    90                data.push(getItemData(item));
    91              }
    92              if (item.type !== "select_one") {
    93                each(item.items, (childItem) => {
    94                  data.push(getItemData(childItem));
    95                });
    96              }
    97            }
    98          });
    99        }
   100      });
   101      return data;
   102    }
   103  
   104    cancelToast = () => {
   105      let nextState = {};
   106      nextState.toastDetails = {
   107        showToast: false,
   108        title: "",
   109        subText: "",
   110        type: "default",
   111        opts: {}
   112      };
   113      this.setState(nextState)
   114    }
   115  
   116    onConfigSaved = () => {
   117      const {
   118        actions,
   119        handleAction,
   120        finalizeApplicationSettings,
   121      } = this.props;
   122      const configAction = actions[0];
   123      const { text } = configAction;
   124  
   125      const nextState = {
   126        toastDetails: {
   127          showToast: true,
   128          title: "All changes have been saved.",
   129          type: "default",
   130          opts: {
   131            showCancelButton: true,
   132            confirmButtonText: text,
   133            confirmAction: async () => {
   134              await finalizeApplicationSettings(this.state.itemData, false).catch();
   135              await handleAction(configAction, true);
   136            },
   137          },
   138        },
   139      };
   140  
   141      this.setState(nextState);
   142    }
   143  
   144    onConfigError = () => {
   145      const { configErrors } = this.props;
   146      if (!configErrors.length) return;
   147      configErrors.map((err) => {
   148        // TODO: Refactor to pass errors down instead of manipulating
   149        // the DOM
   150        const el = document.getElementById(`${err.fieldName}-errblock`);
   151        if (el) {
   152          el.innerHTML = err.message;
   153          el.classList.add("visible");
   154        }
   155      })
   156    }
   157  
   158    handleConfigSave = async (e, nextStep = false) => {
   159      let errs = document.getElementsByClassName("config-errblock");
   160      for (let i = 0; i < errs.length; i++) {
   161        errs[i].classList.remove("visible");
   162      }
   163      await this.props.saveApplicationSettings(this.state.itemData, false)
   164        .then((response) => {
   165          if (!response) {
   166            this.onConfigError();
   167            return;
   168          }
   169          this.onConfigSaved();
   170          if(nextStep) this.state.toastDetails.opts.confirmAction();
   171        })
   172        .catch()
   173    }
   174  
   175    pingServer = async (data) => {
   176      const itemValues = map(data, partialRight(omit, "data", "multi_data"));
   177      await this.props.getApplicationSettings({item_values: itemValues}, false)
   178        .then()
   179        .catch();
   180    }
   181  
   182    handleConfigChange = (data) => {
   183      this.setState({ itemData: data });
   184      this.pingServer(data);
   185    }
   186  
   187    render() {
   188      const {
   189        dataLoading,
   190        settingsFields,
   191        settingsFieldsList,
   192        routeId,
   193        goBack,
   194        firstRoute
   195      } = this.props;
   196      const { toastDetails } = this.state;
   197  
   198      return (
   199        <Layout configOnly={true} configRouteId={routeId}>
   200          <ErrorBoundary className="flex-column">
   201            <div className="flex-column flex1">
   202              <div className="flex-column flex1 u-overflow--hidden u-position--relative">
   203                <Toast toast={toastDetails} onCancel={this.cancelToast} />
   204                <div className="flex-1-auto flex-column u-overflow--auto container u-paddingTop--30">
   205                  {dataLoading.appSettingsFieldsLoading || isEmpty(settingsFields) ?
   206                    <div className="flex1 flex-column justifyContent--center alignItems--center">
   207                      <Loader size="60" />
   208                    </div>
   209                    :
   210                    <ConfigRender
   211                      fieldsList={settingsFieldsList}
   212                      fields={settingsFields}
   213                      handleChange={this.handleConfigChange}
   214                      getData={this.getData}
   215                    />
   216                  }
   217                </div>
   218                <div className="flex-auto flex layout-footer-actions">
   219                  <div className="flex flex1">
   220                    {firstRoute ? null :
   221                      <div className="flex-auto u-marginRight--normal">
   222                        <button className="btn secondary" onClick={() => goBack()}>Back</button>
   223                      </div>
   224                    }
   225                    <div className="flex-column flex-verticalCenter">
   226                      <p className="u-margin--none u-marginRight--30 u-fontSize--small u-color--dustyGray u-fontWeight--normal">Contributed by <a target="_blank" rel="noopener noreferrer" href="https://replicated.com" className="u-fontWeight--medium u-color--astral u-textDecoration--underlineOnHover">Replicated</a></p>
   227                    </div>
   228                  </div>
   229                  <div className="flex flex1 justifyContent--flexEnd">
   230                    <button type="button" disabled={dataLoading.saveAppSettingsLoading} onClick={this.handleConfigSave} className="btn secondary u-marginRight--10">{dataLoading.saveAppSettingsLoading ? "Saving" : "Save changes"}</button>
   231                    <button type="button" disabled={dataLoading.saveAppSettingsLoading} onClick={(e) => this.handleConfigSave(e, true)} className="btn primary">Save and continue to next step</button>
   232                  </div>
   233                </div>
   234              </div>
   235            </div>
   236          </ErrorBoundary>
   237        </Layout>
   238      );
   239    }
   240  }