@mui/material#SelectChangeEvent TypeScript Examples

The following examples show how to use @mui/material#SelectChangeEvent. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: customFields.tsx    From Cromwell with MIT License 6 votes vote down vote up
registerSelectCustomField = (settings: {
    entityType: EDBEntity | string;
    key: string;
    label?: string;
    options?: string[];
    props?: SelectProps<string>;
}) => {
    let customFieldValue;

    registerCustomField({
        id: getRandStr(10),
        fieldType: 'Select',
        ...settings,
        component: (props) => {
            const [value, setValue] = useInitialValue(props.initialValue);
            customFieldValue = value;

            return (
                <Select
                    style={{ margin: '15px 0' }}
                    label={settings.label}
                    value={value}
                    onChange={(event: SelectChangeEvent<string>) => {
                        setValue(event.target.value);
                    }}
                    size="small"
                    variant="standard"
                    fullWidth
                    options={settings.options?.map(opt => ({ label: opt, value: opt }))}
                    {...(settings.props ?? {})}
                />
            )
        },
        saveData: () => (!customFieldValue) ? null : customFieldValue,
    });
}
Example #2
Source File: sort-select.tsx    From tams-club-cal with MIT License 6 votes vote down vote up
SortSelect = (props: SortSelectProps) => {
    // Set state value when user changes selection
    const handleChange = (event: SelectChangeEvent<string>) => {
        props.setValue(event.target.value);
    };

    return (
        <React.Fragment>
            <FormControl>
                <Select value={props.value} onChange={handleChange} variant="standard">
                    {props.options
                        ? props.options.map((o) => (
                              <MenuItem value={o} key={o}>
                                  {capitalize(o)}
                              </MenuItem>
                          ))
                        : null}
                </Select>
            </FormControl>
            <Tooltip
                title={props.reverse ? 'Sorted descending' : 'Sorted ascending'}
                onClick={props.setReverse.bind(this, !props.reverse)}
                sx={{
                    marginLeft: 3,
                    marginRight: 2,
                }}
            >
                <IconButton size="large">
                    {props.reverse ? <ArrowUpwardRoundedIcon /> : <ArrowDownwardRoundedIcon />}
                </IconButton>
            </Tooltip>
        </React.Fragment>
    );
}
Example #3
Source File: General.tsx    From Cromwell with MIT License 5 votes vote down vote up
export default function General(props: TTabProps) {
    const { settings, handleTextFieldChange, changeSettings } = props;
    return (
        <Grid container spacing={3}>
            <Grid item xs={12} sm={6}>
                <FormControl className={styles.field} fullWidth>
                    <TextField label="Website URL"
                        value={settings?.url ?? ''}
                        className={styles.textField}
                        fullWidth
                        variant="standard"
                        onChange={handleTextFieldChange('url')}
                    />
                </FormControl>
            </Grid>
            <Grid item xs={12} sm={6}>
                <Select
                    fullWidth
                    className={styles.field}
                    label="Timezone"
                    variant="standard"
                    value={settings?.timezone ?? 0}
                    onChange={(event: SelectChangeEvent<unknown>) => {
                        changeSettings('timezone', parseInt(event.target.value as string));
                    }}
                    options={timezones.map(timezone => ({ value: timezone.value, label: timezone.text }))}
                />
            </Grid>
            <Grid item xs={12} sm={6}>
                <Select
                    disabled
                    fullWidth
                    className={styles.field}
                    variant="standard"
                    label="Language"
                    value={settings?.language ?? 'en'}
                    onChange={(event: SelectChangeEvent<unknown>) => {
                        changeSettings('language', event.target.value);
                    }}
                    options={languages.map(lang => ({ value: lang.code, label: `${lang.name} (${lang.nativeName})` }))}
                />
            </Grid>
            <Grid item xs={12} sm={6}></Grid>
            <Grid item xs={12} sm={6}>
                <ImagePicker
                    label="Logo"
                    onChange={(val) => changeSettings('logo', val)}
                    value={settings?.logo}
                    className={styles.imageField}
                    backgroundSize='80%'
                    showRemove
                />
            </Grid>
            <Grid item xs={12} sm={6}
                style={{ display: 'flex', alignItems: 'flex-end' }}
            >
                <ImagePicker
                    label="Favicon"
                    onChange={(val) => changeSettings('favicon', val)}
                    value={settings?.favicon}
                    className={styles.imageField}
                    showRemove
                />
            </Grid>
            <Grid item xs={12} sm={12}>
                {settings && (
                    <RenderCustomFields
                        entityType={EDBEntity.CMS}
                        entityData={{ ...settings } as any}
                        refetchMeta={async () => settings?.customMeta}
                    />
                )}
            </Grid>
        </Grid>
    )
}
Example #4
Source File: index.tsx    From Search-Next with GNU General Public License v3.0 5 votes vote down vote up
Navigation: React.FC<PageProps> = (props) => {
  const { route, children } = props;

  const [value, setValue] = React.useState<NavigationType>('page');
  const [navigationData, setNavigationData] =
    React.useState<NavigationInterface>({} as NavigationInterface);

  const options = [
    {
      label: '抽屉',
      value: 'drawer',
    },
    {
      label: '页面',
      value: 'page',
    },
  ];

  const init = () => {
    const account = localStorage.getItem('account');
    const result = getAuthDataByKey(account ?? '', 'navigation');
    setValue(result.type ?? 'page');
    setNavigationData(result);
  };

  const onChange = (event: SelectChangeEvent<any>) => {
    const select = event.target.value;
    setValue(select);
    const account = localStorage.getItem('account');
    updateNavigationSetting(account ?? '', { ...navigationData, type: select });
  };

  React.useEffect(() => {
    init();
  }, []);

  return (
    <div {...props}>
      <ContentList>
        <ItemCard
          title="默认效果"
          desc="设置首页点击导航按钮后导航的显示方式"
          action={
            <Select
              label="效果"
              value={value}
              size="small"
              onChange={onChange}
              options={options}
            />
          }
        ></ItemCard>
      </ContentList>
    </div>
  );
}
Example #5
Source File: CategorySelect.tsx    From mojito_pdm with Creative Commons Attribution Share Alike 4.0 International 5 votes vote down vote up
CategorySelect: React.FC = () => {
    const theme = useTheme()
    const [category, setCategory] = useRecoilState(CarState.categorySearch)

    const handleChange = (event: SelectChangeEvent) => {
        setCategory(event.target.value)
    };

    return (
        <>
            <div>
                <FormControl variant="outlined" sx={{margin: theme.spacing(1), minWidth: 240}} color="error">
                    <InputLabel sx={{color: "white"}}>Category</InputLabel>
                    <Select sx={{color: "white"}}
                            labelId="demo-simple-select-outlined-label"
                            id="demo-simple-select-outlined"
                            value={category}
                            onChange={handleChange}
                            label="Category"
                    >
                        <MenuItem value="">
                            <em>All</em>
                        </MenuItem>
                        <MenuItem value={"Sports"}>Sports</MenuItem>
                        <MenuItem value={"Compacts"}>Compacts</MenuItem>
                        <MenuItem value={"Muscle"}>Muscle</MenuItem>
                        <MenuItem value={"Sedan"}>Sedan</MenuItem>
                        <MenuItem value={"Coupe"}>Coupé</MenuItem>
                        <MenuItem value={"Super"}>Super</MenuItem>
                        <MenuItem value={"SUV"}>SUV</MenuItem>
                        <MenuItem value={"Vans"}>Vans</MenuItem>
                        <MenuItem value={"Offroad"}>Offroad</MenuItem>
                        <MenuItem value={"Sports Classics"}>Sports Classics</MenuItem>
                        <MenuItem value={"Motorcycles"}>Motorcycles</MenuItem>
                    </Select>
                </FormControl>
            </div>
        </>
    )
}
Example #6
Source File: ConfigLogSearch.tsx    From console with GNU Affero General Public License v3.0 4 votes vote down vote up
ConfigLogSearch = ({ classes }: IConfigureProps) => {
  const dispatch = useDispatch();

  const storageClasses = useSelector(
    (state: AppState) => state.createTenant.storageClasses
  );
  const logSearchEnabled = useSelector(
    (state: AppState) => state.createTenant.fields.configure.logSearchEnabled
  );
  const logSearchVolumeSize = useSelector(
    (state: AppState) => state.createTenant.fields.configure.logSearchVolumeSize
  );
  const logSearchSelectedStorageClass = useSelector(
    (state: AppState) =>
      state.createTenant.fields.configure.logSearchSelectedStorageClass
  );
  const logSearchImage = useSelector(
    (state: AppState) => state.createTenant.fields.configure.logSearchImage
  );
  const logSearchPostgresImage = useSelector(
    (state: AppState) =>
      state.createTenant.fields.configure.logSearchPostgresImage
  );
  const logSearchPostgresInitImage = useSelector(
    (state: AppState) =>
      state.createTenant.fields.configure.logSearchPostgresInitImage
  );
  const selectedStorageClass = useSelector(
    (state: AppState) =>
      state.createTenant.fields.nameTenant.selectedStorageClass
  );
  const tenantSecurityContext = useSelector(
    (state: AppState) =>
      state.createTenant.fields.configure.tenantSecurityContext
  );
  const logSearchSecurityContext = useSelector(
    (state: AppState) =>
      state.createTenant.fields.configure.logSearchSecurityContext
  );
  const logSearchPostgresSecurityContext = useSelector(
    (state: AppState) =>
      state.createTenant.fields.configure.logSearchPostgresSecurityContext
  );

  const [validationErrors, setValidationErrors] = useState<any>({});

  const configureSTClasses = [
    { label: "Default", value: "default" },
    ...storageClasses,
  ];

  // Common
  const updateField = useCallback(
    (field: string, value: any) => {
      dispatch(
        updateAddField({ pageName: "configure", field: field, value: value })
      );
    },
    [dispatch]
  );

  // Validation
  useEffect(() => {
    let customAccountValidation: IValidation[] = [];

    if (logSearchEnabled) {
      customAccountValidation = [
        ...customAccountValidation,
        {
          fieldKey: "log_search_storage_class",
          required: true,
          value: logSearchSelectedStorageClass,
          customValidation: logSearchSelectedStorageClass === "",
          customValidationMessage: "Field cannot be empty",
        },
        {
          fieldKey: "log_search_volume_size",
          required: true,
          value: logSearchVolumeSize,
          customValidation:
            logSearchVolumeSize === "" || parseInt(logSearchVolumeSize) <= 0,
          customValidationMessage: `Volume size must be present and be greatter than 0`,
        },
        {
          fieldKey: "logSearch_securityContext_runAsUser",
          required: true,
          value: logSearchSecurityContext.runAsUser,
          customValidation:
            logSearchSecurityContext.runAsUser === "" ||
            parseInt(logSearchSecurityContext.runAsUser) < 0,
          customValidationMessage: `runAsUser must be present and be 0 or more`,
        },
        {
          fieldKey: "logSearch_securityContext_runAsGroup",
          required: true,
          value: logSearchSecurityContext.runAsGroup,
          customValidation:
            logSearchSecurityContext.runAsGroup === "" ||
            parseInt(logSearchSecurityContext.runAsGroup) < 0,
          customValidationMessage: `runAsGroup must be present and be 0 or more`,
        },
        {
          fieldKey: "logSearch_securityContext_fsGroup",
          required: true,
          value: logSearchSecurityContext.fsGroup,
          customValidation:
            logSearchSecurityContext.fsGroup === "" ||
            parseInt(logSearchSecurityContext.fsGroup) < 0,
          customValidationMessage: `fsGroup must be present and be 0 or more`,
        },
        {
          fieldKey: "postgres_securityContext_runAsUser",
          required: true,
          value: logSearchPostgresSecurityContext.runAsUser,
          customValidation:
            logSearchPostgresSecurityContext.runAsUser === "" ||
            parseInt(logSearchPostgresSecurityContext.runAsUser) < 0,
          customValidationMessage: `runAsUser must be present and be 0 or more`,
        },
        {
          fieldKey: "postgres_securityContext_runAsGroup",
          required: true,
          value: logSearchSecurityContext.runAsGroup,
          customValidation:
            logSearchPostgresSecurityContext.runAsGroup === "" ||
            parseInt(logSearchPostgresSecurityContext.runAsGroup) < 0,
          customValidationMessage: `runAsGroup must be present and be 0 or more`,
        },
        {
          fieldKey: "postgres_securityContext_fsGroup",
          required: true,
          value: logSearchPostgresSecurityContext.fsGroup,
          customValidation:
            logSearchPostgresSecurityContext.fsGroup === "" ||
            parseInt(logSearchPostgresSecurityContext.fsGroup) < 0,
          customValidationMessage: `fsGroup must be present and be 0 or more`,
        },
      ];
    }

    const commonVal = commonFormValidation(customAccountValidation);

    dispatch(
      isPageValid({
        pageName: "configure",
        valid: Object.keys(commonVal).length === 0,
      })
    );

    setValidationErrors(commonVal);
  }, [
    logSearchImage,
    logSearchPostgresImage,
    logSearchPostgresInitImage,
    dispatch,
    logSearchEnabled,
    logSearchSelectedStorageClass,
    logSearchVolumeSize,
    tenantSecurityContext,
    logSearchSecurityContext,
    logSearchPostgresSecurityContext,
  ]);

  useEffect(() => {
    // New default values in current selection is invalid
    if (storageClasses.length > 0) {
      const filterLogSearch = storageClasses.filter(
        (item: any) => item.value === logSearchSelectedStorageClass
      );
      if (filterLogSearch.length === 0) {
        updateField("logSearchSelectedStorageClass", "default");
      }
    }
  }, [
    logSearchSelectedStorageClass,
    selectedStorageClass,
    storageClasses,
    updateField,
  ]);

  const cleanValidation = (fieldName: string) => {
    setValidationErrors(clearValidationError(validationErrors, fieldName));
  };

  return (
    <Paper className={classes.paperWrapper}>
      <Grid container alignItems={"center"}>
        <Grid item xs>
          <SectionH1>Audit Log</SectionH1>
        </Grid>
        <Grid item xs={4}>
          <FormSwitchWrapper
            value="enableLogging"
            id="enableLogging"
            name="enableLogging"
            checked={logSearchEnabled}
            onChange={(e) => {
              const targetD = e.target;
              const checked = targetD.checked;

              updateField("logSearchEnabled", checked);
            }}
            indicatorLabels={["Enabled", "Disabled"]}
          />
        </Grid>
      </Grid>
      <Grid container spacing={1}>
        <Grid item xs={12}>
          <span className={classes.descriptionText}>
            Deploys a small PostgreSQL database and stores access logs of all
            calls into the tenant.
          </span>
        </Grid>
        <Grid xs={12}>
          <hr className={classes.hrClass} />
        </Grid>
        {logSearchEnabled && (
          <Fragment>
            <Grid item xs={12}>
              <SelectWrapper
                id="log_search_storage_class"
                name="log_search_storage_class"
                onChange={(e: SelectChangeEvent<string>) => {
                  updateField(
                    "logSearchSelectedStorageClass",
                    e.target.value as string
                  );
                }}
                label="Log Search Storage Class"
                value={logSearchSelectedStorageClass}
                options={configureSTClasses}
                disabled={configureSTClasses.length < 1}
              />
            </Grid>
            <Grid item xs={12}>
              <div className={classes.multiContainer}>
                <InputBoxWrapper
                  type="number"
                  id="log_search_volume_size"
                  name="log_search_volume_size"
                  onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
                    updateField("logSearchVolumeSize", e.target.value);
                    cleanValidation("log_search_volume_size");
                  }}
                  label="Storage Size"
                  overlayObject={
                    <InputUnitMenu
                      id={"size-unit"}
                      onUnitChange={() => {}}
                      unitSelected={"Gi"}
                      unitsList={[{ label: "Gi", value: "Gi" }]}
                      disabled={true}
                    />
                  }
                  value={logSearchVolumeSize}
                  required
                  error={validationErrors["log_search_volume_size"] || ""}
                  min="0"
                />
              </div>
            </Grid>

            <fieldset
              className={`${classes.fieldGroup} ${classes.fieldSpaceTop}`}
            >
              <legend className={classes.descriptionText}>
                SecurityContext for LogSearch
              </legend>

              <Grid item xs={12}>
                <div
                  className={`${classes.multiContainer} ${classes.responsiveSectionItem}`}
                >
                  <div className={classes.configSectionItem}>
                    <InputBoxWrapper
                      type="number"
                      id="logSearch_securityContext_runAsUser"
                      name="logSearch_securityContext_runAsUser"
                      onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
                        updateField("logSearchSecurityContext", {
                          ...logSearchSecurityContext,
                          runAsUser: e.target.value,
                        });
                        cleanValidation("logSearch_securityContext_runAsUser");
                      }}
                      label="Run As User"
                      value={logSearchSecurityContext.runAsUser}
                      required
                      error={
                        validationErrors[
                          "logSearch_securityContext_runAsUser"
                        ] || ""
                      }
                      min="0"
                    />
                  </div>
                  <div className={classes.configSectionItem}>
                    <InputBoxWrapper
                      type="number"
                      id="logSearch_securityContext_runAsGroup"
                      name="logSearch_securityContext_runAsGroup"
                      onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
                        updateField("logSearchSecurityContext", {
                          ...logSearchSecurityContext,
                          runAsGroup: e.target.value,
                        });
                        cleanValidation("logSearch_securityContext_runAsGroup");
                      }}
                      label="Run As Group"
                      value={logSearchSecurityContext.runAsGroup}
                      required
                      error={
                        validationErrors[
                          "logSearch_securityContext_runAsGroup"
                        ] || ""
                      }
                      min="0"
                    />
                  </div>
                  <div className={classes.configSectionItem}>
                    <InputBoxWrapper
                      type="number"
                      id="logSearch_securityContext_fsGroup"
                      name="logSearch_securityContext_fsGroup"
                      onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
                        updateField("logSearchSecurityContext", {
                          ...logSearchSecurityContext,
                          fsGroup: e.target.value,
                        });
                        cleanValidation("logSearch_securityContext_fsGroup");
                      }}
                      label="FsGroup"
                      value={logSearchSecurityContext.fsGroup}
                      required
                      error={
                        validationErrors["logSearch_securityContext_fsGroup"] ||
                        ""
                      }
                      min="0"
                    />
                  </div>
                </div>
              </Grid>
              <br />
              <Grid item xs={12}>
                <div className={classes.multiContainer}>
                  <FormSwitchWrapper
                    value="logSearchSecurityContextRunAsNonRoot"
                    id="logSearch_securityContext_runAsNonRoot"
                    name="logSearch_securityContext_runAsNonRoot"
                    checked={logSearchSecurityContext.runAsNonRoot}
                    onChange={(e) => {
                      const targetD = e.target;
                      const checked = targetD.checked;
                      updateField("logSearchSecurityContext", {
                        ...logSearchSecurityContext,
                        runAsNonRoot: checked,
                      });
                    }}
                    label={"Do not run as Root"}
                  />
                </div>
              </Grid>
            </fieldset>
            <fieldset className={classes.fieldGroup}>
              <legend className={classes.descriptionText}>
                SecurityContext for PostgreSQL
              </legend>

              <Grid item xs={12}>
                <div
                  className={`${classes.multiContainer} ${classes.responsiveSectionItem}`}
                >
                  <div className={classes.configSectionItem}>
                    <InputBoxWrapper
                      type="number"
                      id="postgres_securityContext_runAsUser"
                      name="postgres_securityContext_runAsUser"
                      onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
                        updateField("logSearchPostgresSecurityContext", {
                          ...logSearchPostgresSecurityContext,
                          runAsUser: e.target.value,
                        });
                        cleanValidation("postgres_securityContext_runAsUser");
                      }}
                      label="Run As User"
                      value={logSearchPostgresSecurityContext.runAsUser}
                      required
                      error={
                        validationErrors[
                          "postgres_securityContext_runAsUser"
                        ] || ""
                      }
                      min="0"
                    />
                  </div>
                  <div className={classes.configSectionItem}>
                    <InputBoxWrapper
                      type="number"
                      id="postgres_securityContext_runAsGroup"
                      name="postgres_securityContext_runAsGroup"
                      onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
                        updateField("logSearchPostgresSecurityContext", {
                          ...logSearchPostgresSecurityContext,
                          runAsGroup: e.target.value,
                        });
                        cleanValidation("postgres_securityContext_runAsGroup");
                      }}
                      label="Run As Group"
                      value={logSearchPostgresSecurityContext.runAsGroup}
                      required
                      error={
                        validationErrors[
                          "postgres_securityContext_runAsGroup"
                        ] || ""
                      }
                      min="0"
                    />
                  </div>
                  <div className={classes.configSectionItem}>
                    <InputBoxWrapper
                      type="number"
                      id="postgres_securityContext_fsGroup"
                      name="postgres_securityContext_fsGroup"
                      onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
                        updateField("logSearchPostgresSecurityContext", {
                          ...logSearchPostgresSecurityContext,
                          fsGroup: e.target.value,
                        });
                        cleanValidation("postgres_securityContext_fsGroup");
                      }}
                      label="FsGroup"
                      value={logSearchPostgresSecurityContext.fsGroup}
                      required
                      error={
                        validationErrors["postgres_securityContext_fsGroup"] ||
                        ""
                      }
                      min="0"
                    />
                  </div>
                </div>
              </Grid>
              <br />
              <Grid item xs={12}>
                <div className={classes.multiContainer}>
                  <FormSwitchWrapper
                    value="postgresSecurityContextRunAsNonRoot"
                    id="postgres_securityContext_runAsNonRoot"
                    name="postgres_securityContext_runAsNonRoot"
                    checked={logSearchPostgresSecurityContext.runAsNonRoot}
                    onChange={(e) => {
                      const targetD = e.target;
                      const checked = targetD.checked;
                      updateField("logSearchPostgresSecurityContext", {
                        ...logSearchPostgresSecurityContext,
                        runAsNonRoot: checked,
                      });
                    }}
                    label={"Do not run as Root"}
                  />
                </div>
              </Grid>
            </fieldset>
          </Fragment>
        )}
      </Grid>
    </Paper>
  );
}
Example #7
Source File: DateSelector.tsx    From console with GNU Affero General Public License v3.0 4 votes vote down vote up
DateSelector = forwardRef(
  (
    {
      classes,
      id,
      label,
      disableOptions = false,
      addSwitch = false,
      tooltip = "",
      borderBottom = false,
      onDateChange,
      value = "",
    }: IDateSelectorProps,
    ref: any
  ) => {
    useImperativeHandle(ref, () => ({ resetDate }));

    const [dateEnabled, setDateEnabled] = useState<boolean>(false);
    const [month, setMonth] = useState<string>("");
    const [day, setDay] = useState<string>("");
    const [year, setYear] = useState<string>("");

    useEffect(() => {
      // verify if there is a current value
      // assume is in the format "2021-12-30"
      if (value !== "") {
        const valueSplit = value.split("-");
        setYear(valueSplit[0]);
        setMonth(valueSplit[1]);
        // Turn to single digit to be displayed on dropdown buttons
        setDay(`${parseInt(valueSplit[2])}`);
      }
    }, [value]);

    useEffect(() => {
      const [isValid, dateString] = validDate(year, month, day);
      onDateChange(dateString, isValid);
    }, [month, day, year, onDateChange]);

    const resetDate = () => {
      setMonth("");
      setDay("");
      setYear("");
    };

    const isDateDisabled = () => {
      if (disableOptions) {
        return disableOptions;
      } else if (addSwitch) {
        return !dateEnabled;
      } else {
        return false;
      }
    };

    const onMonthChange = (e: SelectChangeEvent<string>) => {
      setMonth(e.target.value as string);
    };

    const onDayChange = (e: SelectChangeEvent<string>) => {
      setDay(e.target.value as string);
    };

    const onYearChange = (e: SelectChangeEvent<string>) => {
      setYear(e.target.value as string);
    };

    return (
      <Grid
        item
        xs={12}
        className={clsx(classes.fieldContainer, {
          [classes.fieldContainerBorder]: borderBottom,
        })}
      >
        <div className={classes.labelContainer}>
          <Grid container>
            <InputLabel htmlFor={id} className={classes.inputLabel}>
              <span>{label}</span>
              {tooltip !== "" && (
                <div className={classes.tooltipContainer}>
                  <Tooltip title={tooltip} placement="top-start">
                    <div className={classes.tooltip}>
                      <HelpIcon />
                    </div>
                  </Tooltip>
                </div>
              )}
            </InputLabel>
            {addSwitch && (
              <FormSwitchWrapper
                indicatorLabels={["Specific Date", "Default (7 Days)"]}
                checked={dateEnabled}
                value={"date_enabled"}
                id="date-status"
                name="date-status"
                onChange={(e) => {
                  setDateEnabled(e.target.checked);
                  if (!e.target.checked) {
                    onDateChange("", true);
                  }
                }}
                switchOnly
              />
            )}
          </Grid>
        </div>
        <div>
          <FormControl
            disabled={isDateDisabled()}
            className={classes.dateInput}
          >
            <Select
              id={`${id}-month`}
              name={`${id}-month`}
              value={month}
              displayEmpty
              onChange={onMonthChange}
              input={<SelectStyled />}
            >
              <MenuItem value="" disabled>
                {"<Month>"}
              </MenuItem>
              {months.map((option) => (
                <MenuItem
                  value={option.value}
                  key={`select-${id}-monthOP-${option.label}`}
                >
                  {option.label}
                </MenuItem>
              ))}
            </Select>
          </FormControl>
          <FormControl
            disabled={isDateDisabled()}
            className={classes.dateInput}
          >
            <Select
              id={`${id}-day`}
              name={`${id}-day`}
              value={day}
              displayEmpty
              onChange={onDayChange}
              input={<SelectStyled />}
            >
              <MenuItem value="" disabled>
                {"<Day>"}
              </MenuItem>
              {days.map((dayNumber) => (
                <MenuItem
                  value={dayNumber}
                  key={`select-${id}-dayOP-${dayNumber}`}
                >
                  {dayNumber}
                </MenuItem>
              ))}
            </Select>
          </FormControl>
          <FormControl
            disabled={isDateDisabled()}
            className={classes.dateInput}
          >
            <Select
              id={`${id}-year`}
              name={`${id}-year`}
              value={year}
              displayEmpty
              onChange={onYearChange}
              input={<SelectStyled />}
            >
              <MenuItem value="" disabled>
                {"<Year>"}
              </MenuItem>
              {years.map((year) => (
                <MenuItem value={year} key={`select-${id}-yearOP-${year}`}>
                  {year}
                </MenuItem>
              ))}
            </Select>
          </FormControl>
        </div>
      </Grid>
    );
  }
)
Example #8
Source File: ConfigPrometheus.tsx    From console with GNU Affero General Public License v3.0 4 votes vote down vote up
ConfigPrometheus = ({ classes }: IConfigureProps) => {
  const dispatch = useDispatch();

  const storageClasses = useSelector(
    (state: AppState) => state.createTenant.storageClasses
  );
  const prometheusEnabled = useSelector(
    (state: AppState) => state.createTenant.fields.configure.prometheusEnabled
  );
  const prometheusVolumeSize = useSelector(
    (state: AppState) =>
      state.createTenant.fields.configure.prometheusVolumeSize
  );
  const prometheusSelectedStorageClass = useSelector(
    (state: AppState) =>
      state.createTenant.fields.configure.prometheusSelectedStorageClass
  );
  const prometheusImage = useSelector(
    (state: AppState) => state.createTenant.fields.configure.prometheusImage
  );
  const prometheusSidecarImage = useSelector(
    (state: AppState) =>
      state.createTenant.fields.configure.prometheusSidecarImage
  );
  const prometheusInitImage = useSelector(
    (state: AppState) => state.createTenant.fields.configure.prometheusInitImage
  );
  const selectedStorageClass = useSelector(
    (state: AppState) =>
      state.createTenant.fields.nameTenant.selectedStorageClass
  );
  const tenantSecurityContext = useSelector(
    (state: AppState) =>
      state.createTenant.fields.configure.tenantSecurityContext
  );
  const prometheusSecurityContext = useSelector(
    (state: AppState) =>
      state.createTenant.fields.configure.prometheusSecurityContext
  );

  const [validationErrors, setValidationErrors] = useState<any>({});

  const configureSTClasses = [
    { label: "Default", value: "default" },
    ...storageClasses,
  ];

  // Common
  const updateField = useCallback(
    (field: string, value: any) => {
      dispatch(
        updateAddField({ pageName: "configure", field: field, value: value })
      );
    },
    [dispatch]
  );

  // Validation
  useEffect(() => {
    let customAccountValidation: IValidation[] = [];

    if (prometheusEnabled) {
      customAccountValidation = [
        ...customAccountValidation,
        {
          fieldKey: "prometheus_storage_class",
          required: true,
          value: prometheusSelectedStorageClass,
          customValidation: prometheusSelectedStorageClass === "",
          customValidationMessage: "Field cannot be empty",
        },
        {
          fieldKey: "prometheus_volume_size",
          required: true,
          value: prometheusVolumeSize,
          customValidation:
            prometheusVolumeSize === "" || parseInt(prometheusVolumeSize) <= 0,
          customValidationMessage: `Volume size must be present and be greater than 0`,
        },
        {
          fieldKey: "prometheus_securityContext_runAsUser",
          required: true,
          value: prometheusSecurityContext.runAsUser,
          customValidation:
            prometheusSecurityContext.runAsUser === "" ||
            parseInt(prometheusSecurityContext.runAsUser) < 0,
          customValidationMessage: `runAsUser must be present and be 0 or more`,
        },
        {
          fieldKey: "prometheus_securityContext_runAsGroup",
          required: true,
          value: prometheusSecurityContext.runAsGroup,
          customValidation:
            prometheusSecurityContext.runAsGroup === "" ||
            parseInt(prometheusSecurityContext.runAsGroup) < 0,
          customValidationMessage: `runAsGroup must be present and be 0 or more`,
        },
        {
          fieldKey: "prometheus_securityContext_fsGroup",
          required: true,
          value: prometheusSecurityContext.fsGroup,
          customValidation:
            prometheusSecurityContext.fsGroup === "" ||
            parseInt(prometheusSecurityContext.fsGroup) < 0,
          customValidationMessage: `fsGroup must be present and be 0 or more`,
        },
      ];
    }

    const commonVal = commonFormValidation(customAccountValidation);

    dispatch(
      isPageValid({
        pageName: "configure",
        valid: Object.keys(commonVal).length === 0,
      })
    );

    setValidationErrors(commonVal);
  }, [
    prometheusImage,
    prometheusSidecarImage,
    prometheusInitImage,
    dispatch,
    prometheusEnabled,
    prometheusSelectedStorageClass,
    prometheusVolumeSize,
    tenantSecurityContext,
    prometheusSecurityContext,
  ]);

  useEffect(() => {
    // New default values in current selection is invalid
    if (storageClasses.length > 0) {
      const filterPrometheus = storageClasses.filter(
        (item: any) => item.value === prometheusSelectedStorageClass
      );
      if (filterPrometheus.length === 0) {
        updateField("prometheusSelectedStorageClass", "default");
      }
    }
  }, [
    prometheusSelectedStorageClass,
    selectedStorageClass,
    storageClasses,
    updateField,
  ]);

  const cleanValidation = (fieldName: string) => {
    setValidationErrors(clearValidationError(validationErrors, fieldName));
  };

  return (
    <Paper className={classes.paperWrapper}>
      <Grid container alignItems={"center"}>
        <Grid item xs>
          <SectionH1>Monitoring</SectionH1>
        </Grid>
        <Grid item xs={4}>
          <FormSwitchWrapper
            indicatorLabels={["Enabled", "Disabled"]}
            checked={prometheusEnabled}
            value={"monitoring_status"}
            id="monitoring-status"
            name="monitoring-status"
            onChange={(e) => {
              const targetD = e.target;
              const checked = targetD.checked;

              updateField("prometheusEnabled", checked);
            }}
            description=""
          />
        </Grid>
      </Grid>
      <Grid item xs={12}>
        <span className={classes.descriptionText}>
          A small Prometheus will be deployed to keep metrics about the tenant.
        </span>
      </Grid>
      <Grid xs={12}>
        <hr className={classes.hrClass} />
      </Grid>
      <Grid container spacing={1}>
        {prometheusEnabled && (
          <Fragment>
            <Grid item xs={12}>
              <SelectWrapper
                id="prometheus_storage_class"
                name="prometheus_storage_class"
                onChange={(e: SelectChangeEvent<string>) => {
                  updateField(
                    "prometheusSelectedStorageClass",
                    e.target.value as string
                  );
                }}
                label="Storage Class"
                value={prometheusSelectedStorageClass}
                options={configureSTClasses}
                disabled={configureSTClasses.length < 1}
              />
            </Grid>
            <Grid item xs={12}>
              <div className={classes.multiContainer}>
                <InputBoxWrapper
                  type="number"
                  id="prometheus_volume_size"
                  name="prometheus_volume_size"
                  onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
                    updateField("prometheusVolumeSize", e.target.value);
                    cleanValidation("prometheus_volume_size");
                  }}
                  label="Storage Size"
                  overlayObject={
                    <InputUnitMenu
                      id={"size-unit"}
                      onUnitChange={() => {}}
                      unitSelected={"Gi"}
                      unitsList={[{ label: "Gi", value: "Gi" }]}
                      disabled={true}
                    />
                  }
                  value={prometheusVolumeSize}
                  required
                  error={validationErrors["prometheus_volume_size"] || ""}
                  min="0"
                />
              </div>
            </Grid>
            <fieldset
              className={`${classes.fieldGroup} ${classes.fieldSpaceTop}`}
            >
              <legend className={classes.descriptionText}>
                SecurityContext
              </legend>
              <Grid item xs={12} className={classes.configSectionItem}>
                <div
                  className={`${classes.multiContainer} ${classes.responsiveSectionItem}`}
                >
                  <div className={classes.configSectionItem}>
                    <InputBoxWrapper
                      type="number"
                      id="prometheus_securityContext_runAsUser"
                      name="prometheus_securityContext_runAsUser"
                      onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
                        updateField("prometheusSecurityContext", {
                          ...prometheusSecurityContext,
                          runAsUser: e.target.value,
                        });
                        cleanValidation("prometheus_securityContext_runAsUser");
                      }}
                      label="Run As User"
                      value={prometheusSecurityContext.runAsUser}
                      required
                      error={
                        validationErrors[
                          "prometheus_securityContext_runAsUser"
                        ] || ""
                      }
                      min="0"
                    />
                  </div>
                  <div className={classes.configSectionItem}>
                    <InputBoxWrapper
                      type="number"
                      id="prometheus_securityContext_runAsGroup"
                      name="prometheus_securityContext_runAsGroup"
                      onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
                        updateField("prometheusSecurityContext", {
                          ...prometheusSecurityContext,
                          runAsGroup: e.target.value,
                        });
                        cleanValidation(
                          "prometheus_securityContext_runAsGroup"
                        );
                      }}
                      label="Run As Group"
                      value={prometheusSecurityContext.runAsGroup}
                      required
                      error={
                        validationErrors[
                          "prometheus_securityContext_runAsGroup"
                        ] || ""
                      }
                      min="0"
                    />
                  </div>
                  <div className={classes.configSectionItem}>
                    <InputBoxWrapper
                      type="number"
                      id="prometheus_securityContext_fsGroup"
                      name="prometheus_securityContext_fsGroup"
                      onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
                        updateField("prometheusSecurityContext", {
                          ...prometheusSecurityContext,
                          fsGroup: e.target.value,
                        });
                        cleanValidation("prometheus_securityContext_fsGroup");
                      }}
                      label="FsGroup"
                      value={prometheusSecurityContext.fsGroup}
                      required
                      error={
                        validationErrors[
                          "prometheus_securityContext_fsGroup"
                        ] || ""
                      }
                      min="0"
                    />
                  </div>
                </div>
              </Grid>
              <Grid item xs={12} className={classes.configSectionItem}>
                <div
                  className={`${classes.multiContainer} ${classes.fieldSpaceTop}`}
                >
                  <FormSwitchWrapper
                    value="prometheusSecurityContextRunAsNonRoot"
                    id="prometheus_securityContext_runAsNonRoot"
                    name="prometheus_securityContext_runAsNonRoot"
                    checked={prometheusSecurityContext.runAsNonRoot}
                    onChange={(e) => {
                      const targetD = e.target;
                      const checked = targetD.checked;
                      updateField("prometheusSecurityContext", {
                        ...prometheusSecurityContext,
                        runAsNonRoot: checked,
                      });
                    }}
                    label={"Do not run as Root"}
                  />
                </div>
              </Grid>
            </fieldset>
          </Fragment>
        )}
      </Grid>
    </Paper>
  );
}
Example #9
Source File: NameTenantMain.tsx    From console with GNU Affero General Public License v3.0 4 votes vote down vote up
NameTenantMain = ({ classes, formToRender }: INameTenantMainScreen) => {
  const dispatch = useDispatch();

  const selectedStorageClass = useSelector(
    (state: AppState) =>
      state.createTenant.fields.nameTenant.selectedStorageClass
  );
  const selectedStorageType = useSelector(
    (state: AppState) =>
      state.createTenant.fields.nameTenant.selectedStorageType
  );
  const storageClasses = useSelector(
    (state: AppState) => state.createTenant.storageClasses
  );
  const features = useSelector(selFeatures);

  // Common
  const updateField = useCallback(
    (field: string, value: string) => {
      dispatch(
        updateAddField({ pageName: "nameTenant", field: field, value: value })
      );
    },
    [dispatch]
  );

  // Validation
  useEffect(() => {
    const isValid =
      (formToRender === IMkEnvs.default && storageClasses.length > 0) ||
      (formToRender !== IMkEnvs.default && selectedStorageType !== "");

    dispatch(isPageValid({ pageName: "nameTenant", valid: isValid }));
  }, [storageClasses, dispatch, selectedStorageType, formToRender]);

  return (
    <Fragment>
      <Grid container>
        <Grid item xs={8} md={9}>
          <Paper className={classes.paperWrapper} sx={{ minHeight: 550 }}>
            <Grid container>
              <Grid item xs={12}>
                <div className={classes.headerElement}>
                  <h3 className={classes.h3Section}>Name</h3>
                  <span className={classes.descriptionText}>
                    How would you like to name this new tenant?
                  </span>
                </div>
                <div className={classes.formFieldRow}>
                  <NameTenantField />
                </div>
              </Grid>
              <Grid item xs={12} className={classes.formFieldRow}>
                <NamespaceSelector formToRender={formToRender} />
              </Grid>
              {formToRender === IMkEnvs.default ? (
                <Grid item xs={12} className={classes.formFieldRow}>
                  <SelectWrapper
                    id="storage_class"
                    name="storage_class"
                    onChange={(e: SelectChangeEvent<string>) => {
                      updateField(
                        "selectedStorageClass",
                        e.target.value as string
                      );
                    }}
                    label="Storage Class"
                    value={selectedStorageClass}
                    options={storageClasses}
                    disabled={storageClasses.length < 1}
                  />
                </Grid>
              ) : (
                <Grid item xs={12} className={classes.formFieldRow}>
                  <SelectWrapper
                    id="storage_type"
                    name="storage_type"
                    onChange={(e: SelectChangeEvent<string>) => {
                      setStorageType({
                        storageType: e.target.value as string,
                        features: features,
                      });
                    }}
                    label={get(
                      mkPanelConfigurations,
                      `${formToRender}.variantSelectorLabel`,
                      "Storage Type"
                    )}
                    value={selectedStorageType}
                    options={get(
                      mkPanelConfigurations,
                      `${formToRender}.variantSelectorValues`,
                      []
                    )}
                  />
                </Grid>
              )}
              {formToRender === IMkEnvs.default ? (
                <TenantSize />
              ) : (
                get(
                  mkPanelConfigurations,
                  `${formToRender}.sizingComponent`,
                  null
                )
              )}
            </Grid>
          </Paper>
        </Grid>
        <Grid item xs={4} md={3}>
          <div className={classes.sizePreview}>
            <SizePreview />
          </div>
        </Grid>
      </Grid>
    </Fragment>
  );
}
Example #10
Source File: TenantSize.tsx    From console with GNU Affero General Public License v3.0 4 votes vote down vote up
TenantSize = ({ classes, formToRender }: ITenantSizeProps) => {
  const dispatch = useDispatch();

  const volumeSize = useSelector(
    (state: AppState) => state.createTenant.fields.tenantSize.volumeSize
  );
  const sizeFactor = useSelector(
    (state: AppState) => state.createTenant.fields.tenantSize.sizeFactor
  );
  const drivesPerServer = useSelector(
    (state: AppState) => state.createTenant.fields.tenantSize.drivesPerServer
  );
  const nodes = useSelector(
    (state: AppState) => state.createTenant.fields.tenantSize.nodes
  );
  const memoryNode = useSelector(
    (state: AppState) => state.createTenant.fields.tenantSize.memoryNode
  );
  const ecParity = useSelector(
    (state: AppState) => state.createTenant.fields.tenantSize.ecParity
  );
  const ecParityChoices = useSelector(
    (state: AppState) => state.createTenant.fields.tenantSize.ecParityChoices
  );
  const cleanECChoices = useSelector(
    (state: AppState) => state.createTenant.fields.tenantSize.cleanECChoices
  );
  const resourcesSize = useSelector(
    (state: AppState) => state.createTenant.fields.tenantSize.resourcesSize
  );
  const distribution = useSelector(
    (state: AppState) => state.createTenant.fields.tenantSize.distribution
  );
  const ecParityCalc = useSelector(
    (state: AppState) => state.createTenant.fields.tenantSize.ecParityCalc
  );
  const untouchedECField = useSelector(
    (state: AppState) => state.createTenant.fields.tenantSize.untouchedECField
  );
  const limitSize = useSelector(
    (state: AppState) => state.createTenant.limitSize
  );
  const selectedStorageClass = useSelector(
    (state: AppState) =>
      state.createTenant.fields.nameTenant.selectedStorageClass
  );
  const selectedStorageType = useSelector(
    (state: AppState) =>
      state.createTenant.fields.nameTenant.selectedStorageType
  );

  const [validationErrors, setValidationErrors] = useState<any>({});
  const [errorFlag, setErrorFlag] = useState<boolean>(false);
  const [nodeError, setNodeError] = useState<string>("");

  // Common
  const updateField = useCallback(
    (field: string, value: any) => {
      dispatch(
        updateAddField({
          pageName: "tenantSize",
          field: field,
          value: value,
        })
      );
    },
    [dispatch]
  );

  const cleanValidation = (fieldName: string) => {
    setValidationErrors(clearValidationError(validationErrors, fieldName));
  };

  /*Debounce functions*/

  // Storage Quotas
  useEffect(() => {
    if (cleanECChoices.length > 0 && ecParityCalc.defaultEC !== "") {
      updateField(
        "ecParityChoices",
        ecListTransform(cleanECChoices, ecParityCalc.defaultEC)
      );
    }
  }, [ecParityCalc, cleanECChoices, updateField]);

  useEffect(() => {
    if (ecParity !== "" && ecParityCalc.defaultEC !== ecParity) {
      updateField("untouchedECField", false);
      return;
    }

    updateField("untouchedECField", true);
  }, [ecParity, ecParityCalc, updateField]);

  useEffect(() => {
    if (ecParityChoices.length > 0 && distribution.error === "") {
      const ecCodeValidated = erasureCodeCalc(
        cleanECChoices,
        distribution.persistentVolumes,
        distribution.pvSize,
        distribution.nodes
      );

      updateField("ecParityCalc", ecCodeValidated);

      if (!cleanECChoices.includes(ecParity) || ecParity === "") {
        updateField("ecParity", ecCodeValidated.defaultEC);
      }
    }
  }, [
    ecParity,
    ecParityChoices.length,
    distribution,
    cleanECChoices,
    updateField,
    untouchedECField,
  ]);
  /*End debounce functions*/

  /*Calculate Allocation*/
  useEffect(() => {
    //Validate Cluster Size
    const size = volumeSize;
    const factor = sizeFactor;
    const limitSize = getBytes("16", "Ti", true);

    const clusterCapacity: ICapacity = {
      unit: factor,
      value: size.toString(),
    };

    const distrCalculate = calculateDistribution(
      clusterCapacity,
      parseInt(nodes),
      parseInt(limitSize),
      parseInt(drivesPerServer),
      formToRender,
      selectedStorageType
    );

    updateField("distribution", distrCalculate);
    setErrorFlag(false);
    setNodeError("");
  }, [
    nodes,
    volumeSize,
    sizeFactor,
    updateField,
    drivesPerServer,
    selectedStorageType,
    formToRender,
  ]);

  /*Calculate Allocation End*/

  /* Validations of pages */

  useEffect(() => {
    const parsedSize = getBytes(volumeSize, sizeFactor, true);

    const commonValidation = commonFormValidation([
      {
        fieldKey: "nodes",
        required: true,
        value: nodes,
        customValidation: errorFlag,
        customValidationMessage: nodeError,
      },
      {
        fieldKey: "volume_size",
        required: true,
        value: volumeSize,
        customValidation:
          parseInt(parsedSize) < 1073741824 ||
          parseInt(parsedSize) > limitSize[selectedStorageClass],
        customValidationMessage: `Volume size must be greater than 1Gi and less than ${niceBytes(
          limitSize[selectedStorageClass],
          true
        )}`,
      },
      {
        fieldKey: "drivesps",
        required: true,
        value: drivesPerServer,
        customValidation: parseInt(drivesPerServer) < 1,
        customValidationMessage: "There must be at least one drive",
      },
    ]);

    dispatch(
      isPageValid({
        pageName: "tenantSize",
        valid:
          !("nodes" in commonValidation) &&
          !("volume_size" in commonValidation) &&
          !("drivesps" in commonValidation) &&
          distribution.error === "" &&
          ecParityCalc.error === 0 &&
          ecParity !== "",
      })
    );

    setValidationErrors(commonValidation);
  }, [
    nodes,
    volumeSize,
    sizeFactor,
    memoryNode,
    distribution,
    ecParityCalc,
    resourcesSize,
    limitSize,
    selectedStorageClass,
    dispatch,
    errorFlag,
    nodeError,
    drivesPerServer,
    ecParity,
  ]);

  useEffect(() => {
    if (distribution.error === "") {
      // Get EC Value
      if (nodes.trim() !== "" && distribution.disks !== 0) {
        api
          .invoke("GET", `api/v1/get-parity/${nodes}/${distribution.disks}`)
          .then((ecList: string[]) => {
            updateField("ecParityChoices", ecListTransform(ecList));
            updateField("cleanECChoices", ecList);
            if (untouchedECField) {
              updateField("ecParity", "");
            }
          })
          .catch((err: any) => {
            updateField("ecparityChoices", []);
            dispatch(
              isPageValid({
                pageName: "tenantSize",
                valid: false,
              })
            );
            updateField("ecParity", "");
          });
      }
    }
  }, [distribution, dispatch, updateField, nodes, untouchedECField]);

  /* End Validation of pages */

  return (
    <Fragment>
      <Grid item xs={12}>
        <div className={classes.headerElement}>
          <h3 className={classes.h3Section}>Capacity</h3>
          <span className={classes.descriptionText}>
            Please select the desired capacity
          </span>
        </div>
      </Grid>
      {distribution.error !== "" && (
        <Grid item xs={12}>
          <div className={classes.error}>{distribution.error}</div>
        </Grid>
      )}
      <Grid item xs={12} className={classes.formFieldRow}>
        <InputBoxWrapper
          id="nodes"
          name="nodes"
          onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
            if (e.target.validity.valid) {
              updateField("nodes", e.target.value);
              cleanValidation("nodes");
            }
          }}
          label="Number of Servers"
          disabled={selectedStorageClass === ""}
          value={nodes}
          min="4"
          required
          error={validationErrors["nodes"] || ""}
          pattern={"[0-9]*"}
        />
      </Grid>
      <Grid item xs={12} className={classes.formFieldRow}>
        <InputBoxWrapper
          id="drivesps"
          name="drivesps"
          onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
            if (e.target.validity.valid) {
              updateField("drivesPerServer", e.target.value);
              cleanValidation("drivesps");
            }
          }}
          label="Drives per Server"
          value={drivesPerServer}
          disabled={selectedStorageClass === ""}
          min="1"
          required
          error={validationErrors["drivesps"] || ""}
          pattern={"[0-9]*"}
        />
      </Grid>
      <Grid item xs={12}>
        <div className={classes.formFieldRow}>
          <InputBoxWrapper
            type="number"
            id="volume_size"
            name="volume_size"
            onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
              updateField("volumeSize", e.target.value);
              cleanValidation("volume_size");
            }}
            label="Total Size"
            value={volumeSize}
            disabled={selectedStorageClass === ""}
            required
            error={validationErrors["volume_size"] || ""}
            min="0"
            overlayObject={
              <InputUnitMenu
                id={"size-unit"}
                onUnitChange={(newValue) => {
                  updateField("sizeFactor", newValue);
                }}
                unitSelected={sizeFactor}
                unitsList={k8sScalarUnitsExcluding(["Ki", "Mi"])}
                disabled={selectedStorageClass === ""}
              />
            }
          />
        </div>
      </Grid>

      <Grid item xs={12} className={classes.formFieldRow}>
        <SelectWrapper
          id="ec_parity"
          name="ec_parity"
          onChange={(e: SelectChangeEvent<string>) => {
            updateField("ecParity", e.target.value as string);
          }}
          label="Erasure Code Parity"
          disabled={selectedStorageClass === ""}
          value={ecParity}
          options={ecParityChoices}
        />
        <span className={classes.descriptionText}>
          Please select the desired parity. This setting will change the max
          usable capacity in the cluster
        </span>
      </Grid>

      <TenantSizeResources />
    </Fragment>
  );
}
Example #11
Source File: TenantSizeMK.tsx    From console with GNU Affero General Public License v3.0 4 votes vote down vote up
TenantSizeMK = ({
  classes,

  formToRender,
}: ITenantSizeAWSProps) => {
  const dispatch = useDispatch();

  const volumeSize = useSelector(
    (state: AppState) => state.createTenant.fields.tenantSize.volumeSize
  );
  const sizeFactor = useSelector(
    (state: AppState) => state.createTenant.fields.tenantSize.sizeFactor
  );
  const drivesPerServer = useSelector(
    (state: AppState) => state.createTenant.fields.tenantSize.drivesPerServer
  );
  const nodes = useSelector(
    (state: AppState) => state.createTenant.fields.tenantSize.nodes
  );
  const memoryNode = useSelector(
    (state: AppState) => state.createTenant.fields.tenantSize.memoryNode
  );
  const ecParity = useSelector(
    (state: AppState) => state.createTenant.fields.tenantSize.ecParity
  );
  const ecParityChoices = useSelector(
    (state: AppState) => state.createTenant.fields.tenantSize.ecParityChoices
  );
  const cleanECChoices = useSelector(
    (state: AppState) => state.createTenant.fields.tenantSize.cleanECChoices
  );
  const resourcesSize = useSelector(
    (state: AppState) => state.createTenant.fields.tenantSize.resourcesSize
  );
  const distribution = useSelector(
    (state: AppState) => state.createTenant.fields.tenantSize.distribution
  );
  const ecParityCalc = useSelector(
    (state: AppState) => state.createTenant.fields.tenantSize.ecParityCalc
  );
  const cpuToUse = useSelector(
    (state: AppState) => state.createTenant.fields.tenantSize.cpuToUse
  );
  const maxCPUsUse = useSelector(
    (state: AppState) => state.createTenant.fields.tenantSize.maxCPUsUse
  );
  const integrationSelection = useSelector(
    (state: AppState) =>
      state.createTenant.fields.tenantSize.integrationSelection
  );
  const limitSize = useSelector(
    (state: AppState) => state.createTenant.limitSize
  );
  const selectedStorageType = useSelector(
    (state: AppState) =>
      state.createTenant.fields.nameTenant.selectedStorageType
  );

  const [validationErrors, setValidationErrors] = useState<any>({});

  // Common
  const updateField = useCallback(
    (field: string, value: any) => {
      dispatch(
        updateAddField({
          pageName: "tenantSize",
          field: field,
          value: value,
        })
      );
    },
    [dispatch]
  );

  const updateMainField = useCallback(
    (field: string, value: string) => {
      dispatch(
        updateAddField({
          pageName: "nameTenant",
          field: field,
          value: value,
        })
      );
    },
    [dispatch]
  );

  const cleanValidation = (fieldName: string) => {
    setValidationErrors(clearValidationError(validationErrors, fieldName));
  };

  /*Debounce functions*/

  // Storage Quotas
  useEffect(() => {
    if (ecParityChoices.length > 0 && distribution.error === "") {
      const ecCodeValidated = erasureCodeCalc(
        cleanECChoices,
        distribution.persistentVolumes,
        distribution.pvSize,
        distribution.nodes
      );

      updateField("ecParityCalc", ecCodeValidated);

      if (!cleanECChoices.includes(ecParity) || ecParity === "") {
        updateField("ecParity", ecCodeValidated.defaultEC);
      }
    }
  }, [ecParity, ecParityChoices, distribution, cleanECChoices, updateField]);
  /*End debounce functions*/

  /*Set location Storage Types*/
  useEffect(() => {
    if (formToRender !== undefined && parseInt(nodes) >= 4) {
      const setConfigs = mkPanelConfigurations[formToRender];
      const keyCount = Object.keys(setConfigs).length;

      //Configuration is filled
      if (keyCount > 0) {
        const configs: IntegrationConfiguration[] = get(
          setConfigs,
          "configurations",
          []
        );

        const mainSelection = configs.find(
          (item) => item.typeSelection === selectedStorageType
        );

        if (mainSelection) {
          updateField("integrationSelection", mainSelection);
          updateMainField("selectedStorageClass", mainSelection.storageClass);

          let pvSize = parseInt(
            getBytes(
              mainSelection.driveSize.driveSize,
              mainSelection.driveSize.sizeUnit,
              true
            ),
            10
          );

          const distrCalculate: IStorageDistribution = {
            pvSize,
            nodes: parseInt(nodes),
            disks: mainSelection.drivesPerServer,
            persistentVolumes: mainSelection.drivesPerServer * parseInt(nodes),
            error: "",
          };

          updateField("distribution", distrCalculate);
          // apply requests, half of the available resources
          updateField(
            "resourcesCPURequest",
            Math.max(1, mainSelection.CPU / 2)
          );
          updateField(
            "resourcesMemoryRequest",
            Math.max(2, mainSelection.memory / 2)
          );
        }
      }
    }
  }, [nodes, selectedStorageType, formToRender, updateField, updateMainField]);

  /*Calculate Allocation End*/

  /* Validations of pages */

  useEffect(() => {
    const commonValidation = commonFormValidation([
      {
        fieldKey: "nodes",
        required: true,
        value: nodes,
        customValidation: parseInt(nodes) < 4,
        customValidationMessage: "Al least 4 servers must be selected",
      },
    ]);

    dispatch(
      isPageValid({
        pageName: "tenantSize",
        valid:
          !("nodes" in commonValidation) &&
          distribution.error === "" &&
          ecParityCalc.error === 0 &&
          resourcesSize.error === "" &&
          ecParity !== "" &&
          parseInt(nodes) >= 4,
      })
    );

    setValidationErrors(commonValidation);
  }, [
    nodes,
    volumeSize,
    sizeFactor,
    memoryNode,
    distribution,
    ecParityCalc,
    resourcesSize,
    limitSize,
    selectedStorageType,
    cpuToUse,
    maxCPUsUse,
    dispatch,
    drivesPerServer,
    ecParity,
  ]);

  useEffect(() => {
    if (integrationSelection.drivesPerServer !== 0) {
      // Get EC Value
      if (nodes.trim() !== "") {
        api
          .invoke(
            "GET",
            `api/v1/get-parity/${nodes}/${integrationSelection.drivesPerServer}`
          )
          .then((ecList: string[]) => {
            updateField("ecParityChoices", ecListTransform(ecList));
            updateField("cleanECChoices", ecList);
          })
          .catch((err: any) => {
            updateField("ecparityChoices", []);
            dispatch(
              isPageValid({
                pageName: "tenantSize",
                valid: false,
              })
            );
            updateField("ecParity", "");
          });
      }
    }
  }, [integrationSelection, nodes, dispatch, updateField]);

  /* End Validation of pages */

  return (
    <Fragment>
      <Grid item xs={12}>
        <div className={classes.headerElement}>
          <h3 className={classes.h3Section}>Tenant Size</h3>
          <span className={classes.descriptionText}>
            Please select the desired capacity
          </span>
        </div>
      </Grid>
      {distribution.error !== "" && (
        <Grid item xs={12}>
          <div className={classes.error}>{distribution.error}</div>
        </Grid>
      )}
      {resourcesSize.error !== "" && (
        <Grid item xs={12}>
          <div className={classes.error}>{resourcesSize.error}</div>
        </Grid>
      )}
      <Grid item xs={12} className={classes.formFieldRow}>
        <InputBoxWrapper
          id="nodes"
          name="nodes"
          onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
            if (e.target.validity.valid) {
              updateField("nodes", e.target.value);
              cleanValidation("nodes");
            }
          }}
          label="Number of Servers"
          disabled={selectedStorageType === ""}
          value={nodes}
          min="4"
          required
          error={validationErrors["nodes"] || ""}
          pattern={"[0-9]*"}
        />
      </Grid>
      <Grid item xs={12} className={classes.formFieldRow}>
        <SelectWrapper
          id="ec_parity"
          name="ec_parity"
          onChange={(e: SelectChangeEvent<string>) => {
            updateField("ecParity", e.target.value as string);
          }}
          label="Erasure Code Parity"
          disabled={selectedStorageType === ""}
          value={ecParity}
          options={ecParityChoices}
        />
        <span className={classes.descriptionText}>
          Please select the desired parity. This setting will change the max
          usable capacity in the cluster
        </span>
      </Grid>
    </Fragment>
  );
}
Example #12
Source File: ListTenants.tsx    From console with GNU Affero General Public License v3.0 4 votes vote down vote up
ListTenants = ({ classes }: ITenantsList) => {
  const dispatch = useDispatch();
  const [isLoading, setIsLoading] = useState<boolean>(false);
  const [filterTenants, setFilterTenants] = useState<string>("");
  const [records, setRecords] = useState<ITenant[]>([]);
  const [showNewCredentials, setShowNewCredentials] = useState<boolean>(false);
  const [createdAccount, setCreatedAccount] =
    useState<NewServiceAccount | null>(null);
  const [sortValue, setSortValue] = useState<string>("name");

  const closeCredentialsModal = () => {
    setShowNewCredentials(false);
    setCreatedAccount(null);
  };

  const filteredRecords = records.filter((b: any) => {
    if (filterTenants === "") {
      return true;
    } else {
      if (b.name.indexOf(filterTenants) >= 0) {
        return true;
      } else {
        return false;
      }
    }
  });

  filteredRecords.sort((a, b) => {
    switch (sortValue) {
      case "capacity":
        if (!a.capacity || !b.capacity) {
          return 0;
        }

        if (a.capacity > b.capacity) {
          return 1;
        }

        if (a.capacity < b.capacity) {
          return -1;
        }

        return 0;
      case "usage":
        if (!a.capacity_usage || !b.capacity_usage) {
          return 0;
        }

        if (a.capacity_usage > b.capacity_usage) {
          return 1;
        }

        if (a.capacity_usage < b.capacity_usage) {
          return -1;
        }

        return 0;
      case "active_status":
        if (a.health_status === "red" && b.health_status !== "red") {
          return 1;
        }

        if (a.health_status !== "red" && b.health_status === "red") {
          return -1;
        }

        return 0;
      case "failing_status":
        if (a.health_status === "green" && b.health_status !== "green") {
          return 1;
        }

        if (a.health_status !== "green" && b.health_status === "green") {
          return -1;
        }

        return 0;
      default:
        if (a.name > b.name) {
          return 1;
        }
        if (a.name < b.name) {
          return -1;
        }
        return 0;
    }
  });

  useEffect(() => {
    if (isLoading) {
      const fetchRecords = () => {
        api
          .invoke("GET", `/api/v1/tenants`)
          .then((res: ITenantsResponse) => {
            if (res === null) {
              setIsLoading(false);
              return;
            }
            let resTenants: ITenant[] = [];
            if (res.tenants !== null) {
              resTenants = res.tenants;
            }

            for (let i = 0; i < resTenants.length; i++) {
              resTenants[i].total_capacity = niceBytes(
                resTenants[i].total_size + ""
              );
            }

            setRecords(resTenants);
            setIsLoading(false);
          })
          .catch((err: ErrorResponseHandler) => {
            dispatch(setErrorSnackMessage(err));
            setIsLoading(false);
          });
      };
      fetchRecords();
    }
  }, [isLoading, dispatch]);

  useEffect(() => {
    setIsLoading(true);
  }, []);

  const renderItemLine = (index: number) => {
    const tenant = filteredRecords[index] || null;

    if (tenant) {
      return <TenantListItem tenant={tenant} />;
    }

    return null;
  };

  return (
    <Fragment>
      {showNewCredentials && (
        <CredentialsPrompt
          newServiceAccount={createdAccount}
          open={showNewCredentials}
          closeModal={() => {
            closeCredentialsModal();
          }}
          entity="Tenant"
        />
      )}
      <PageHeader
        label="Tenants"
        middleComponent={
          <SearchBox
            placeholder={"Filter Tenants"}
            onChange={(val) => {
              setFilterTenants(val);
            }}
            value={filterTenants}
          />
        }
        actions={
          <Grid item xs={12} marginRight={"30px"}>
            <RBIconButton
              id={"refresh-tenant-list"}
              tooltip={"Refresh Tenant List"}
              text={""}
              onClick={() => {
                setIsLoading(true);
              }}
              icon={<RefreshIcon />}
              color="primary"
              variant={"outlined"}
            />
            <RBIconButton
              id={"create-tenant"}
              tooltip={"Create Tenant"}
              text={"Create Tenant"}
              onClick={() => {
                history.push("/tenants/add");
              }}
              icon={<AddIcon />}
              color="primary"
              variant={"contained"}
            />
          </Grid>
        }
      />
      <PageLayout>
        <Grid item xs={12} className={classes.tenantsList}>
          {isLoading && <LinearProgress />}
          {!isLoading && (
            <Fragment>
              {filteredRecords.length !== 0 && (
                <Fragment>
                  <Grid item xs={12} className={classes.sortByContainer}>
                    <div className={classes.innerSort}>
                      <span className={classes.sortByLabel}>Sort by</span>
                      <SelectWrapper
                        id={"sort-by"}
                        label={""}
                        value={sortValue}
                        onChange={(e: SelectChangeEvent<string>) => {
                          setSortValue(e.target.value as string);
                        }}
                        name={"sort-by"}
                        options={[
                          { label: "Name", value: "name" },
                          {
                            label: "Capacity",
                            value: "capacity",
                          },
                          {
                            label: "Usage",
                            value: "usage",
                          },
                          {
                            label: "Active Status",
                            value: "active_status",
                          },
                          {
                            label: "Failing Status",
                            value: "failing_status",
                          },
                        ]}
                      />
                    </div>
                  </Grid>
                  <VirtualizedList
                    rowRenderFunction={renderItemLine}
                    totalItems={filteredRecords.length}
                  />
                </Fragment>
              )}
              {filteredRecords.length === 0 && (
                <Grid
                  container
                  justifyContent={"center"}
                  alignContent={"center"}
                  alignItems={"center"}
                >
                  <Grid item xs={8}>
                    <HelpBox
                      iconComponent={<TenantsIcon />}
                      title={"Tenants"}
                      help={
                        <Fragment>
                          Tenant is the logical structure to represent a MinIO
                          deployment. A tenant can have different size and
                          configurations from other tenants, even a different
                          storage class.
                          <br />
                          <br />
                          To get started,&nbsp;
                          <AButton
                            onClick={() => {
                              history.push("/tenants/add");
                            }}
                          >
                            Create a Tenant.
                          </AButton>
                        </Fragment>
                      }
                    />
                  </Grid>
                </Grid>
              )}
            </Fragment>
          )}
        </Grid>
      </PageLayout>
    </Fragment>
  );
}
Example #13
Source File: PoolPodPlacement.tsx    From console with GNU Affero General Public License v3.0 4 votes vote down vote up
Affinity = ({ classes }: IAffinityProps) => {
  const dispatch = useDispatch();

  const podAffinity = useSelector(
    (state: AppState) => state.addPool.affinity.podAffinity
  );
  const nodeSelectorLabels = useSelector(
    (state: AppState) => state.addPool.affinity.nodeSelectorLabels
  );
  const withPodAntiAffinity = useSelector(
    (state: AppState) => state.addPool.affinity.withPodAntiAffinity
  );
  const keyValuePairs = useSelector(
    (state: AppState) => state.addPool.nodeSelectorPairs
  );
  const tolerations = useSelector(
    (state: AppState) => state.addPool.tolerations
  );

  const [validationErrors, setValidationErrors] = useState<any>({});
  const [loading, setLoading] = useState<boolean>(true);
  const [keyValueMap, setKeyValueMap] = useState<{ [key: string]: string[] }>(
    {}
  );
  const [keyOptions, setKeyOptions] = useState<OptionPair[]>([]);

  // Common
  const updateField = useCallback(
    (field: string, value: any) => {
      dispatch(
        setPoolField({
          page: "affinity",
          field: field,
          value: value,
        })
      );
    },
    [dispatch]
  );

  useEffect(() => {
    if (loading) {
      api
        .invoke("GET", `/api/v1/nodes/labels`)
        .then((res: { [key: string]: string[] }) => {
          setLoading(false);
          setKeyValueMap(res);
          let keys: OptionPair[] = [];
          for (let k in res) {
            keys.push({
              label: k,
              value: k,
            });
          }
          setKeyOptions(keys);
        })
        .catch((err: ErrorResponseHandler) => {
          setLoading(false);
          dispatch(setModalErrorSnackMessage(err));
          setKeyValueMap({});
        });
    }
  }, [dispatch, loading]);

  useEffect(() => {
    if (keyValuePairs) {
      const vlr = keyValuePairs
        .filter((kvp) => kvp.key !== "")
        .map((kvp) => `${kvp.key}=${kvp.value}`)
        .filter((kvs, i, a) => a.indexOf(kvs) === i);
      const vl = vlr.join("&");
      updateField("nodeSelectorLabels", vl);
    }
  }, [keyValuePairs, updateField]);

  // Validation
  useEffect(() => {
    let customAccountValidation: IValidation[] = [];

    if (podAffinity === "nodeSelector") {
      let valid = true;

      const splittedLabels = nodeSelectorLabels.split("&");

      if (splittedLabels.length === 1 && splittedLabels[0] === "") {
        valid = false;
      }

      splittedLabels.forEach((item: string, index: number) => {
        const splitItem = item.split("=");

        if (splitItem.length !== 2) {
          valid = false;
        }

        if (index + 1 !== splittedLabels.length) {
          if (splitItem[0] === "" || splitItem[1] === "") {
            valid = false;
          }
        }
      });

      customAccountValidation = [
        ...customAccountValidation,
        {
          fieldKey: "labels",
          required: true,
          value: nodeSelectorLabels,
          customValidation: !valid,
          customValidationMessage:
            "You need to add at least one label key-pair",
        },
      ];
    }

    const commonVal = commonFormValidation(customAccountValidation);

    dispatch(
      isPoolPageValid({
        page: "affinity",
        status: Object.keys(commonVal).length === 0,
      })
    );

    setValidationErrors(commonVal);
  }, [dispatch, podAffinity, nodeSelectorLabels]);

  const updateToleration = (index: number, field: string, value: any) => {
    const alterToleration = { ...tolerations[index], [field]: value };

    dispatch(
      setPoolTolerationInfo({
        index: index,
        tolerationValue: alterToleration,
      })
    );
  };

  return (
    <Paper className={classes.paperWrapper}>
      <div className={classes.headerElement}>
        <h3 className={classes.h3Section}>Pod Placement</h3>
        <span className={classes.descriptionText}>
          Configure how pods will be assigned to nodes
        </span>
      </div>
      <Grid item xs={12} className={classes.affinityConfigField}>
        <Grid item className={classes.affinityFieldLabel}>
          <div className={classes.label}>Type</div>
          <div
            className={`${classes.descriptionText} ${classes.affinityHelpText}`}
          >
            MinIO supports multiple configurations for Pod Affinity
          </div>
          <Grid item className={classes.radioField}>
            <RadioGroupSelector
              currentSelection={podAffinity}
              id="affinity-options"
              name="affinity-options"
              label={" "}
              onChange={(e) => {
                updateField("podAffinity", e.target.value);
              }}
              selectorOptions={[
                { label: "None", value: "none" },
                { label: "Default (Pod Anti-Affinity)", value: "default" },
                { label: "Node Selector", value: "nodeSelector" },
              ]}
            />
          </Grid>
        </Grid>
      </Grid>
      {podAffinity === "nodeSelector" && (
        <Fragment>
          <br />
          <Grid item xs={12}>
            <FormSwitchWrapper
              value="with_pod_anti_affinity"
              id="with_pod_anti_affinity"
              name="with_pod_anti_affinity"
              checked={withPodAntiAffinity}
              onChange={(e) => {
                const targetD = e.target;
                const checked = targetD.checked;

                updateField("withPodAntiAffinity", checked);
              }}
              label={"With Pod Anti-Affinity"}
            />
          </Grid>
          <Grid item xs={12}>
            <h3>Labels</h3>
            <span className={classes.error}>{validationErrors["labels"]}</span>
            <Grid container>
              {keyValuePairs &&
                keyValuePairs.map((kvp, i) => {
                  return (
                    <Grid
                      item
                      xs={12}
                      className={classes.affinityRow}
                      key={`affinity-keyVal-${i.toString()}`}
                    >
                      <Grid item xs={5} className={classes.affinityLabelKey}>
                        {keyOptions.length > 0 && (
                          <SelectWrapper
                            onChange={(e: SelectChangeEvent<string>) => {
                              const newKey = e.target.value as string;
                              const newLKP: LabelKeyPair = {
                                key: newKey,
                                value: keyValueMap[newKey][0],
                              };
                              const arrCp: LabelKeyPair[] = [...keyValuePairs];
                              arrCp[i] = newLKP;
                              dispatch(setPoolKeyValuePairs(arrCp));
                            }}
                            id="select-access-policy"
                            name="select-access-policy"
                            label={""}
                            value={kvp.key}
                            options={keyOptions}
                          />
                        )}
                        {keyOptions.length === 0 && (
                          <InputBoxWrapper
                            id={`nodeselector-key-${i.toString()}`}
                            label={""}
                            name={`nodeselector-${i.toString()}`}
                            value={kvp.key}
                            onChange={(e) => {
                              const arrCp: LabelKeyPair[] = [...keyValuePairs];
                              arrCp[i] = {
                                key: arrCp[i].key,
                                value: e.target.value as string,
                              };
                              dispatch(setPoolKeyValuePairs(arrCp));
                            }}
                            index={i}
                            placeholder={"Key"}
                          />
                        )}
                      </Grid>
                      <Grid item xs={5} className={classes.affinityLabelValue}>
                        {keyOptions.length > 0 && (
                          <SelectWrapper
                            onChange={(e: SelectChangeEvent<string>) => {
                              const arrCp: LabelKeyPair[] = [...keyValuePairs];
                              arrCp[i] = {
                                key: arrCp[i].key,
                                value: e.target.value as string,
                              };
                              dispatch(setPoolKeyValuePairs(arrCp));
                            }}
                            id="select-access-policy"
                            name="select-access-policy"
                            label={""}
                            value={kvp.value}
                            options={
                              keyValueMap[kvp.key]
                                ? keyValueMap[kvp.key].map((v) => {
                                    return { label: v, value: v };
                                  })
                                : []
                            }
                          />
                        )}
                        {keyOptions.length === 0 && (
                          <InputBoxWrapper
                            id={`nodeselector-value-${i.toString()}`}
                            label={""}
                            name={`nodeselector-${i.toString()}`}
                            value={kvp.value}
                            onChange={(e) => {
                              const arrCp: LabelKeyPair[] = [...keyValuePairs];
                              arrCp[i] = {
                                key: arrCp[i].key,
                                value: e.target.value as string,
                              };
                              dispatch(setPoolKeyValuePairs(arrCp));
                            }}
                            index={i}
                            placeholder={"value"}
                          />
                        )}
                      </Grid>
                      <Grid item xs={2} className={classes.rowActions}>
                        <div className={classes.overlayAction}>
                          <IconButton
                            size={"small"}
                            onClick={() => {
                              const arrCp = [...keyValuePairs];
                              if (keyOptions.length > 0) {
                                arrCp.push({
                                  key: keyOptions[0].value,
                                  value: keyValueMap[keyOptions[0].value][0],
                                });
                              } else {
                                arrCp.push({ key: "", value: "" });
                              }

                              dispatch(setPoolKeyValuePairs(arrCp));
                            }}
                          >
                            <AddIcon />
                          </IconButton>
                        </div>
                        {keyValuePairs.length > 1 && (
                          <div className={classes.overlayAction}>
                            <IconButton
                              size={"small"}
                              onClick={() => {
                                const arrCp = keyValuePairs.filter(
                                  (item, index) => index !== i
                                );
                                dispatch(setPoolKeyValuePairs(arrCp));
                              }}
                            >
                              <RemoveIcon />
                            </IconButton>
                          </div>
                        )}
                      </Grid>
                    </Grid>
                  );
                })}
            </Grid>
          </Grid>
        </Fragment>
      )}
      <Grid item xs={12} className={classes.affinityConfigField}>
        <Grid item className={classes.affinityFieldLabel}>
          <h3>Tolerations</h3>
          <span className={classes.error}>
            {validationErrors["tolerations"]}
          </span>
          <Grid container>
            {tolerations &&
              tolerations.map((tol, i) => {
                return (
                  <Grid
                    item
                    xs={12}
                    className={classes.affinityRow}
                    key={`affinity-keyVal-${i.toString()}`}
                  >
                    <TolerationSelector
                      effect={tol.effect}
                      onEffectChange={(value) => {
                        updateToleration(i, "effect", value);
                      }}
                      tolerationKey={tol.key}
                      onTolerationKeyChange={(value) => {
                        updateToleration(i, "key", value);
                      }}
                      operator={tol.operator}
                      onOperatorChange={(value) => {
                        updateToleration(i, "operator", value);
                      }}
                      value={tol.value}
                      onValueChange={(value) => {
                        updateToleration(i, "value", value);
                      }}
                      tolerationSeconds={tol.tolerationSeconds?.seconds || 0}
                      onSecondsChange={(value) => {
                        updateToleration(i, "tolerationSeconds", {
                          seconds: value,
                        });
                      }}
                      index={i}
                    />
                    <div className={classes.overlayAction}>
                      <IconButton
                        size={"small"}
                        onClick={() => {
                          dispatch(addNewPoolToleration());
                        }}
                        disabled={i !== tolerations.length - 1}
                      >
                        <AddIcon />
                      </IconButton>
                    </div>

                    <div className={classes.overlayAction}>
                      <IconButton
                        size={"small"}
                        onClick={() => dispatch(removePoolToleration(i))}
                        disabled={tolerations.length <= 1}
                      >
                        <RemoveIcon />
                      </IconButton>
                    </div>
                  </Grid>
                );
              })}
          </Grid>
        </Grid>
      </Grid>
    </Paper>
  );
}
Example #14
Source File: PoolResources.tsx    From console with GNU Affero General Public License v3.0 4 votes vote down vote up
PoolResources = ({ classes }: IPoolResourcesProps) => {
  const dispatch = useDispatch();

  const tenant = useSelector((state: AppState) => state.tenants.tenantInfo);
  const storageClasses = useSelector(
    (state: AppState) => state.addPool.storageClasses
  );
  const numberOfNodes = useSelector((state: AppState) =>
    state.addPool.setup.numberOfNodes.toString()
  );
  const storageClass = useSelector(
    (state: AppState) => state.addPool.setup.storageClass
  );
  const volumeSize = useSelector((state: AppState) =>
    state.addPool.setup.volumeSize.toString()
  );
  const volumesPerServer = useSelector((state: AppState) =>
    state.addPool.setup.volumesPerServer.toString()
  );

  const [validationErrors, setValidationErrors] = useState<any>({});

  const instanceCapacity: number =
    parseInt(volumeSize) * 1073741824 * parseInt(volumesPerServer);
  const totalCapacity: number = instanceCapacity * parseInt(numberOfNodes);

  // Validation
  useEffect(() => {
    let customAccountValidation: IValidation[] = [
      {
        fieldKey: "number_of_nodes",
        required: true,
        value: numberOfNodes.toString(),
        customValidation:
          parseInt(numberOfNodes) < 1 || isNaN(parseInt(numberOfNodes)),
        customValidationMessage: "Number of servers must be at least 1",
      },
      {
        fieldKey: "pool_size",
        required: true,
        value: volumeSize.toString(),
        customValidation:
          parseInt(volumeSize) < 1 || isNaN(parseInt(volumeSize)),
        customValidationMessage: "Pool Size cannot be 0",
      },
      {
        fieldKey: "volumes_per_server",
        required: true,
        value: volumesPerServer.toString(),
        customValidation:
          parseInt(volumesPerServer) < 1 || isNaN(parseInt(volumesPerServer)),
        customValidationMessage: "1 volume or more are required",
      },
    ];

    const commonVal = commonFormValidation(customAccountValidation);

    dispatch(
      isPoolPageValid({
        page: "setup",
        status: Object.keys(commonVal).length === 0,
      })
    );

    setValidationErrors(commonVal);
  }, [dispatch, numberOfNodes, volumeSize, volumesPerServer, storageClass]);

  useEffect(() => {
    if (storageClasses.length === 0 && tenant) {
      api
        .invoke(
          "GET",
          `/api/v1/namespaces/${tenant.namespace}/resourcequotas/${tenant.namespace}-storagequota`
        )
        .then((res: IQuotas) => {
          const elements: IQuotaElement[] = get(res, "elements", []);

          const newStorage = elements.map((storageClass: any) => {
            const name = get(storageClass, "name", "").split(
              ".storageclass.storage.k8s.io/requests.storage"
            )[0];

            return { label: name, value: name };
          });

          dispatch(
            setPoolField({
              page: "setup",
              field: "storageClass",
              value: newStorage[0].value,
            })
          );

          dispatch(setPoolStorageClasses(newStorage));
        })
        .catch((err: ErrorResponseHandler) => {
          console.error(err);
        });
    }
  }, [tenant, storageClasses, dispatch]);

  const setFieldInfo = (fieldName: string, value: any) => {
    dispatch(
      setPoolField({
        page: "setup",
        field: fieldName,
        value: value,
      })
    );
  };

  return (
    <Paper className={classes.paperWrapper}>
      <div className={classes.headerElement}>
        <h3 className={classes.h3Section}>New Pool Configuration</h3>
        <span className={classes.descriptionText}>
          Configure a new Pool to expand MinIO storage
        </span>
      </div>

      <Grid item xs={12} className={classes.formFieldRow}>
        <InputBoxWrapper
          id="number_of_nodes"
          name="number_of_nodes"
          onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
            const intValue = parseInt(e.target.value);

            if (e.target.validity.valid && !isNaN(intValue)) {
              setFieldInfo("numberOfNodes", intValue);
            } else if (isNaN(intValue)) {
              setFieldInfo("numberOfNodes", 0);
            }
          }}
          label="Number of Servers"
          value={numberOfNodes}
          error={validationErrors["number_of_nodes"] || ""}
          pattern={"[0-9]*"}
        />
      </Grid>
      <Grid item xs={12} className={classes.formFieldRow}>
        <InputBoxWrapper
          id="pool_size"
          name="pool_size"
          onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
            const intValue = parseInt(e.target.value);

            if (e.target.validity.valid && !isNaN(intValue)) {
              setFieldInfo("volumeSize", intValue);
            } else if (isNaN(intValue)) {
              setFieldInfo("volumeSize", 0);
            }
          }}
          label="Volume Size"
          value={volumeSize}
          error={validationErrors["pool_size"] || ""}
          pattern={"[0-9]*"}
          overlayObject={
            <InputUnitMenu
              id={"quota_unit"}
              onUnitChange={() => {}}
              unitSelected={"Gi"}
              unitsList={[{ label: "Gi", value: "Gi" }]}
              disabled={true}
            />
          }
        />
      </Grid>
      <Grid item xs={12} className={classes.formFieldRow}>
        <InputBoxWrapper
          id="volumes_per_sever"
          name="volumes_per_sever"
          onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
            const intValue = parseInt(e.target.value);

            if (e.target.validity.valid && !isNaN(intValue)) {
              setFieldInfo("volumesPerServer", intValue);
            } else if (isNaN(intValue)) {
              setFieldInfo("volumesPerServer", 0);
            }
          }}
          label="Volumes per Server"
          value={volumesPerServer}
          error={validationErrors["volumes_per_server"] || ""}
          pattern={"[0-9]*"}
        />
      </Grid>
      <Grid item xs={12} className={classes.formFieldRow}>
        <SelectWrapper
          id="storage_class"
          name="storage_class"
          onChange={(e: SelectChangeEvent<string>) => {
            setFieldInfo("storageClasses", e.target.value as string);
          }}
          label="Storage Class"
          value={storageClass}
          options={storageClasses}
          disabled={storageClasses.length < 1}
        />
      </Grid>
      <Grid item xs={12} className={classes.bottomContainer}>
        <div className={classes.factorElements}>
          <div>
            <div className={classes.sizeNumber}>
              {niceBytes(instanceCapacity.toString(10))}
            </div>
            <div className={classes.sizeDescription}>Instance Capacity</div>
          </div>
          <div>
            <div className={classes.sizeNumber}>
              {niceBytes(totalCapacity.toString(10))}
            </div>
            <div className={classes.sizeDescription}>Total Capacity</div>
          </div>
        </div>
      </Grid>
    </Paper>
  );
}
Example #15
Source File: EditPoolPlacement.tsx    From console with GNU Affero General Public License v3.0 4 votes vote down vote up
Affinity = ({ classes }: IAffinityProps) => {
  const dispatch = useDispatch();

  const podAffinity = useSelector(
    (state: AppState) => state.editPool.fields.affinity.podAffinity
  );
  const nodeSelectorLabels = useSelector(
    (state: AppState) => state.editPool.fields.affinity.nodeSelectorLabels
  );
  const withPodAntiAffinity = useSelector(
    (state: AppState) => state.editPool.fields.affinity.withPodAntiAffinity
  );
  const keyValuePairs = useSelector(
    (state: AppState) => state.editPool.fields.nodeSelectorPairs
  );
  const tolerations = useSelector(
    (state: AppState) => state.editPool.fields.tolerations
  );

  const [validationErrors, setValidationErrors] = useState<any>({});
  const [loading, setLoading] = useState<boolean>(true);
  const [keyValueMap, setKeyValueMap] = useState<{ [key: string]: string[] }>(
    {}
  );
  const [keyOptions, setKeyOptions] = useState<OptionPair[]>([]);

  // Common
  const updateField = useCallback(
    (field: string, value: any) => {
      dispatch(
        setEditPoolField({
          page: "affinity",
          field: field,
          value: value,
        })
      );
    },
    [dispatch]
  );

  useEffect(() => {
    if (loading) {
      api
        .invoke("GET", `/api/v1/nodes/labels`)
        .then((res: { [key: string]: string[] }) => {
          setLoading(false);
          setKeyValueMap(res);
          let keys: OptionPair[] = [];
          for (let k in res) {
            keys.push({
              label: k,
              value: k,
            });
          }
          setKeyOptions(keys);
        })
        .catch((err: ErrorResponseHandler) => {
          setLoading(false);
          dispatch(setModalErrorSnackMessage(err));
          setKeyValueMap({});
        });
    }
  }, [dispatch, loading]);

  useEffect(() => {
    if (keyValuePairs) {
      const vlr = keyValuePairs
        .filter((kvp) => kvp.key !== "")
        .map((kvp) => `${kvp.key}=${kvp.value}`)
        .filter((kvs, i, a) => a.indexOf(kvs) === i);
      const vl = vlr.join("&");
      updateField("nodeSelectorLabels", vl);
    }
  }, [keyValuePairs, updateField]);

  // Validation
  useEffect(() => {
    let customAccountValidation: IValidation[] = [];

    if (podAffinity === "nodeSelector") {
      let valid = true;

      const splittedLabels = nodeSelectorLabels.split("&");

      if (splittedLabels.length === 1 && splittedLabels[0] === "") {
        valid = false;
      }

      splittedLabels.forEach((item: string, index: number) => {
        const splitItem = item.split("=");

        if (splitItem.length !== 2) {
          valid = false;
        }

        if (index + 1 !== splittedLabels.length) {
          if (splitItem[0] === "" || splitItem[1] === "") {
            valid = false;
          }
        }
      });

      customAccountValidation = [
        ...customAccountValidation,
        {
          fieldKey: "labels",
          required: true,
          value: nodeSelectorLabels,
          customValidation: !valid,
          customValidationMessage:
            "You need to add at least one label key-pair",
        },
      ];
    }

    const commonVal = commonFormValidation(customAccountValidation);

    dispatch(
      isEditPoolPageValid({
        page: "affinity",
        status: Object.keys(commonVal).length === 0,
      })
    );

    setValidationErrors(commonVal);
  }, [dispatch, podAffinity, nodeSelectorLabels]);

  const updateToleration = (index: number, field: string, value: any) => {
    const alterToleration = { ...tolerations[index], [field]: value };

    dispatch(
      setEditPoolTolerationInfo({
        index: index,
        tolerationValue: alterToleration,
      })
    );
  };

  return (
    <Paper className={classes.paperWrapper}>
      <div className={classes.headerElement}>
        <h3 className={classes.h3Section}>Pod Placement</h3>
      </div>
      <Grid item xs={12} className={classes.affinityConfigField}>
        <Grid item className={classes.affinityFieldLabel}>
          <div className={classes.label}>Type</div>
          <div
            className={`${classes.descriptionText} ${classes.affinityHelpText}`}
          >
            MinIO supports multiple configurations for Pod Affinity
          </div>
          <Grid item className={classes.radioField}>
            <RadioGroupSelector
              currentSelection={podAffinity}
              id="affinity-options"
              name="affinity-options"
              label={" "}
              onChange={(e) => {
                updateField("podAffinity", e.target.value);
              }}
              selectorOptions={[
                { label: "None", value: "none" },
                { label: "Default (Pod Anti-Affinity)", value: "default" },
                { label: "Node Selector", value: "nodeSelector" },
              ]}
            />
          </Grid>
        </Grid>
      </Grid>
      {podAffinity === "nodeSelector" && (
        <Fragment>
          <br />
          <Grid item xs={12}>
            <FormSwitchWrapper
              value="with_pod_anti_affinity"
              id="with_pod_anti_affinity"
              name="with_pod_anti_affinity"
              checked={withPodAntiAffinity}
              onChange={(e) => {
                const targetD = e.target;
                const checked = targetD.checked;

                updateField("withPodAntiAffinity", checked);
              }}
              label={"With Pod Anti-Affinity"}
            />
          </Grid>
          <Grid item xs={12}>
            <h3>Labels</h3>
            <span className={classes.error}>{validationErrors["labels"]}</span>
            <Grid container>
              {keyValuePairs &&
                keyValuePairs.map((kvp, i) => {
                  return (
                    <Grid
                      item
                      xs={12}
                      className={classes.affinityRow}
                      key={`affinity-keyVal-${i.toString()}`}
                    >
                      <Grid item xs={5} className={classes.affinityLabelKey}>
                        {keyOptions.length > 0 && (
                          <SelectWrapper
                            onChange={(e: SelectChangeEvent<string>) => {
                              const newKey = e.target.value as string;
                              const newLKP: LabelKeyPair = {
                                key: newKey,
                                value: keyValueMap[newKey][0],
                              };
                              const arrCp: LabelKeyPair[] = [...keyValuePairs];
                              arrCp[i] = newLKP;
                              dispatch(setEditPoolKeyValuePairs(arrCp));
                            }}
                            id="select-access-policy"
                            name="select-access-policy"
                            label={""}
                            value={kvp.key}
                            options={keyOptions}
                          />
                        )}
                        {keyOptions.length === 0 && (
                          <InputBoxWrapper
                            id={`nodeselector-key-${i.toString()}`}
                            label={""}
                            name={`nodeselector-${i.toString()}`}
                            value={kvp.key}
                            onChange={(e) => {
                              const arrCp: LabelKeyPair[] = [...keyValuePairs];
                              arrCp[i] = {
                                key: arrCp[i].key,
                                value: e.target.value as string,
                              };
                              dispatch(setEditPoolKeyValuePairs(arrCp));
                            }}
                            index={i}
                            placeholder={"Key"}
                          />
                        )}
                      </Grid>
                      <Grid item xs={5} className={classes.affinityLabelValue}>
                        {keyOptions.length > 0 && (
                          <SelectWrapper
                            onChange={(e: SelectChangeEvent<string>) => {
                              const arrCp: LabelKeyPair[] = [...keyValuePairs];
                              arrCp[i] = {
                                key: arrCp[i].key,
                                value: e.target.value as string,
                              };
                              dispatch(setEditPoolKeyValuePairs(arrCp));
                            }}
                            id="select-access-policy"
                            name="select-access-policy"
                            label={""}
                            value={kvp.value}
                            options={
                              keyValueMap[kvp.key]
                                ? keyValueMap[kvp.key].map((v) => {
                                    return { label: v, value: v };
                                  })
                                : []
                            }
                          />
                        )}
                        {keyOptions.length === 0 && (
                          <InputBoxWrapper
                            id={`nodeselector-value-${i.toString()}`}
                            label={""}
                            name={`nodeselector-${i.toString()}`}
                            value={kvp.value}
                            onChange={(e) => {
                              const arrCp: LabelKeyPair[] = [...keyValuePairs];
                              arrCp[i] = {
                                key: arrCp[i].key,
                                value: e.target.value as string,
                              };
                              dispatch(setEditPoolKeyValuePairs(arrCp));
                            }}
                            index={i}
                            placeholder={"value"}
                          />
                        )}
                      </Grid>
                      <Grid item xs={2} className={classes.rowActions}>
                        <div className={classes.overlayAction}>
                          <IconButton
                            size={"small"}
                            onClick={() => {
                              const arrCp = [...keyValuePairs];
                              if (keyOptions.length > 0) {
                                arrCp.push({
                                  key: keyOptions[0].value,
                                  value: keyValueMap[keyOptions[0].value][0],
                                });
                              } else {
                                arrCp.push({ key: "", value: "" });
                              }

                              dispatch(setEditPoolKeyValuePairs(arrCp));
                            }}
                          >
                            <AddIcon />
                          </IconButton>
                        </div>
                        {keyValuePairs.length > 1 && (
                          <div className={classes.overlayAction}>
                            <IconButton
                              size={"small"}
                              onClick={() => {
                                const arrCp = keyValuePairs.filter(
                                  (item, index) => index !== i
                                );
                                dispatch(setEditPoolKeyValuePairs(arrCp));
                              }}
                            >
                              <RemoveIcon />
                            </IconButton>
                          </div>
                        )}
                      </Grid>
                    </Grid>
                  );
                })}
            </Grid>
          </Grid>
        </Fragment>
      )}
      <Grid item xs={12} className={classes.affinityConfigField}>
        <Grid item className={classes.affinityFieldLabel}>
          <h3>Tolerations</h3>
          <span className={classes.error}>
            {validationErrors["tolerations"]}
          </span>
          <Grid container>
            {tolerations &&
              tolerations.map((tol, i) => {
                return (
                  <Grid
                    item
                    xs={12}
                    className={classes.affinityRow}
                    key={`affinity-keyVal-${i.toString()}`}
                  >
                    <TolerationSelector
                      effect={tol.effect}
                      onEffectChange={(value) => {
                        updateToleration(i, "effect", value);
                      }}
                      tolerationKey={tol.key}
                      onTolerationKeyChange={(value) => {
                        updateToleration(i, "key", value);
                      }}
                      operator={tol.operator}
                      onOperatorChange={(value) => {
                        updateToleration(i, "operator", value);
                      }}
                      value={tol.value}
                      onValueChange={(value) => {
                        updateToleration(i, "value", value);
                      }}
                      tolerationSeconds={tol.tolerationSeconds?.seconds || 0}
                      onSecondsChange={(value) => {
                        updateToleration(i, "tolerationSeconds", {
                          seconds: value,
                        });
                      }}
                      index={i}
                    />
                    <div className={classes.overlayAction}>
                      <IconButton
                        size={"small"}
                        onClick={() => {
                          dispatch(addNewEditPoolToleration());
                        }}
                        disabled={i !== tolerations.length - 1}
                      >
                        <AddIcon />
                      </IconButton>
                    </div>

                    <div className={classes.overlayAction}>
                      <IconButton
                        size={"small"}
                        onClick={() => {
                          dispatch(removeEditPoolToleration(i));
                        }}
                        disabled={tolerations.length <= 1}
                      >
                        <RemoveIcon />
                      </IconButton>
                    </div>
                  </Grid>
                );
              })}
          </Grid>
        </Grid>
      </Grid>
    </Paper>
  );
}
Example #16
Source File: EditPoolResources.tsx    From console with GNU Affero General Public License v3.0 4 votes vote down vote up
PoolResources = ({ classes }: IPoolResourcesProps) => {
  const dispatch = useDispatch();

  const tenant = useSelector((state: AppState) => state.tenants.tenantInfo);
  const storageClasses = useSelector(
    (state: AppState) => state.editPool.storageClasses
  );
  const numberOfNodes = useSelector((state: AppState) =>
    state.editPool.fields.setup.numberOfNodes.toString()
  );
  const storageClass = useSelector(
    (state: AppState) => state.editPool.fields.setup.storageClass
  );
  const volumeSize = useSelector((state: AppState) =>
    state.editPool.fields.setup.volumeSize.toString()
  );
  const volumesPerServer = useSelector((state: AppState) =>
    state.editPool.fields.setup.volumesPerServer.toString()
  );

  const [validationErrors, setValidationErrors] = useState<any>({});

  const instanceCapacity: number =
    parseInt(volumeSize) * 1073741824 * parseInt(volumesPerServer);
  const totalCapacity: number = instanceCapacity * parseInt(numberOfNodes);

  // Validation
  useEffect(() => {
    let customAccountValidation: IValidation[] = [
      {
        fieldKey: "number_of_nodes",
        required: true,
        value: numberOfNodes.toString(),
        customValidation:
          parseInt(numberOfNodes) < 1 || isNaN(parseInt(numberOfNodes)),
        customValidationMessage: "Number of servers must be at least 1",
      },
      {
        fieldKey: "pool_size",
        required: true,
        value: volumeSize.toString(),
        customValidation:
          parseInt(volumeSize) < 1 || isNaN(parseInt(volumeSize)),
        customValidationMessage: "Pool Size cannot be 0",
      },
      {
        fieldKey: "volumes_per_server",
        required: true,
        value: volumesPerServer.toString(),
        customValidation:
          parseInt(volumesPerServer) < 1 || isNaN(parseInt(volumesPerServer)),
        customValidationMessage: "1 volume or more are required",
      },
    ];

    const commonVal = commonFormValidation(customAccountValidation);

    dispatch(
      isEditPoolPageValid({
        page: "setup",
        status: Object.keys(commonVal).length === 0,
      })
    );

    setValidationErrors(commonVal);
  }, [dispatch, numberOfNodes, volumeSize, volumesPerServer, storageClass]);

  useEffect(() => {
    if (storageClasses.length === 0 && tenant) {
      api
        .invoke(
          "GET",
          `/api/v1/namespaces/${tenant.namespace}/resourcequotas/${tenant.namespace}-storagequota`
        )
        .then((res: IQuotas) => {
          const elements: IQuotaElement[] = get(res, "elements", []);

          const newStorage = elements.map((storageClass: any) => {
            const name = get(storageClass, "name", "").split(
              ".storageclass.storage.k8s.io/requests.storage"
            )[0];

            return { label: name, value: name };
          });

          dispatch(
            setEditPoolField({
              page: "setup",
              field: "storageClass",
              value: newStorage[0].value,
            })
          );

          dispatch(setEditPoolStorageClasses(newStorage));
        })
        .catch((err: ErrorResponseHandler) => {
          console.error(err);
        });
    }
  }, [tenant, storageClasses, dispatch]);

  const setFieldInfo = (fieldName: string, value: any) => {
    dispatch(
      setEditPoolField({
        page: "setup",
        field: fieldName,
        value: value,
      })
    );
  };

  return (
    <Paper className={classes.paperWrapper}>
      <div className={classes.headerElement}>
        <h3 className={classes.h3Section}>Pool Resources</h3>
      </div>

      <Grid item xs={12} className={classes.formFieldRow}>
        <InputBoxWrapper
          id="number_of_nodes"
          name="number_of_nodes"
          onChange={() => {}}
          label="Number of Servers"
          value={numberOfNodes}
          error={validationErrors["number_of_nodes"] || ""}
          disabled
        />
      </Grid>
      <Grid item xs={12} className={classes.formFieldRow}>
        <InputBoxWrapper
          id="volumes_per_sever"
          name="volumes_per_sever"
          onChange={() => {}}
          label="Volumes per Server"
          value={volumesPerServer}
          error={validationErrors["volumes_per_server"] || ""}
          disabled
        />
      </Grid>
      <Grid item xs={12} className={classes.formFieldRow}>
        <InputBoxWrapper
          id="pool_size"
          name="pool_size"
          onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
            const intValue = parseInt(e.target.value);

            if (e.target.validity.valid && !isNaN(intValue)) {
              setFieldInfo("volumeSize", intValue);
            } else if (isNaN(intValue)) {
              setFieldInfo("volumeSize", 0);
            }
          }}
          label="Volume Size"
          value={volumeSize}
          error={validationErrors["pool_size"] || ""}
          pattern={"[0-9]*"}
          overlayObject={
            <InputUnitMenu
              id={"quota_unit"}
              onUnitChange={() => {}}
              unitSelected={"Gi"}
              unitsList={[{ label: "Gi", value: "Gi" }]}
              disabled={true}
            />
          }
        />
      </Grid>

      <Grid item xs={12} className={classes.formFieldRow}>
        <SelectWrapper
          id="storage_class"
          name="storage_class"
          onChange={(e: SelectChangeEvent<string>) => {
            setFieldInfo("storageClasses", e.target.value as string);
          }}
          label="Storage Class"
          value={storageClass}
          options={storageClasses}
          disabled={storageClasses.length < 1}
        />
      </Grid>
      <Grid item xs={12} className={classes.bottomContainer}>
        <div className={classes.factorElements}>
          <div>
            <div className={classes.sizeNumber}>
              {niceBytes(instanceCapacity.toString(10))}
            </div>
            <div className={classes.sizeDescription}>Instance Capacity</div>
          </div>
          <div>
            <div className={classes.sizeNumber}>
              {niceBytes(totalCapacity.toString(10))}
            </div>
            <div className={classes.sizeDescription}>Total Capacity</div>
          </div>
        </div>
      </Grid>
    </Paper>
  );
}
Example #17
Source File: Form.tsx    From frontend with MIT License 4 votes vote down vote up
export default function Form() {
  const router = useRouter()
  const { t } = useTranslation()
  let id = router.query.id
  const [beneficiaryType, setBeneficiaryType] = useState<string>('')
  const [personId, setPersonId] = useState<string>('')
  const [companyId, setCompanyId] = useState<string>('')
  const [coordinatorId, setCoordinatorId] = useState<string>('')
  const [cityId, setCityId] = useState<string>('')
  const [countryCode, setCountryCode] = useState<string>('')
  const people = usePeopleList().data
  const companies = useCompaniesList().data
  const coordinators = useCoordinatorsList().data
  const coordinatorRelations = Object.values(PersonRelation)
  const cities = useCitiesList().data
  const countries = useCountriesList().data

  let initialValues: BeneficiaryFormData = {
    type: beneficiaryType,
    personId: personId,
    companyId: companyId,
    coordinatorId: coordinatorId,
    countryCode: countryCode,
    cityId: cityId,
    description: '',
    publicData: '',
    privateData: '',
    coordinatorRelation: 'none',
    campaigns: [],
  }

  if (id) {
    id = String(id)
    const { data }: UseQueryResult<BeneficiaryListResponse> = useViewBeneficiary(id)
    initialValues = {
      type: data?.type,
      cityId: data?.cityId || '',
      companyId: data?.companyId || '',
      coordinatorId: data?.coordinatorId || '',
      countryCode: data?.countryCode || '',
      description: data?.description || '',
      personId: data?.personId || '',
      privateData: data?.privateData || '',
      publicData: data?.publicData || '',
      coordinatorRelation: data?.coordinatorRelation || '',
      campaigns: data?.campaigns || [],
    }
  }
  const mutationFn = id ? useEditBeneficiary(id) : useCreateBeneficiary()

  const mutation = useMutation<
    AxiosResponse<BeneficiaryListResponse>,
    AxiosError<ApiErrors>,
    BeneficiaryFormData
  >({
    mutationFn,
    onError: () => AlertStore.show(t('documents:alerts:error'), 'error'),
    onSuccess: () => {
      AlertStore.show(id ? t('documents:alerts:edit') : t('documents:alerts:create'), 'success')
      router.push(routes.admin.beneficiary.index)
    },
  })

  async function onSubmit(data: BeneficiaryFormData) {
    mutation.mutateAsync(data)
  }

  return (
    <GenericForm
      onSubmit={onSubmit}
      initialValues={initialValues}
      validationSchema={validationSchema}>
      <Box sx={{ height: '62.6vh', marginBottom: '9%' }}>
        <Typography variant="h5" component="h2" sx={{ textAlign: 'center' }}>
          {id ? t('beneficiary:forms:edit-heading') : t('beneficiary:forms:add-heading')}
        </Typography>
        <Grid container spacing={2}>
          <Grid item xs={12}>
            <InputLabel>{t('beneficiary:grid:type')}</InputLabel>
            <Select
              fullWidth
              sx={{ height: '55%' }}
              name="type"
              defaultValue={initialValues.type}
              onChange={(e: SelectChangeEvent) => {
                setBeneficiaryType(e.target.value)
              }}>
              <MenuItem value="">
                <em>None</em>
              </MenuItem>
              {Object.values(LegalEntityType)?.map((type) => {
                return (
                  <MenuItem key={type} value={type}>
                    {t('beneficiary:grid:' + type)}
                  </MenuItem>
                )
              })}
            </Select>
          </Grid>
          <Grid item xs={6}>
            <InputLabel>{t('beneficiary:grid:individual')}</InputLabel>
            <Select
              fullWidth
              sx={{ height: '55%' }}
              name="personId"
              defaultValue={initialValues.personId}
              onChange={(e: SelectChangeEvent) => {
                setPersonId(e.target.value)
              }}>
              <MenuItem value="">
                <em>None</em>
              </MenuItem>
              {people?.map((person) => {
                return (
                  <MenuItem key={person.id} value={person.id}>
                    {person.firstName + ' ' + person.lastName}
                  </MenuItem>
                )
              })}
            </Select>
          </Grid>
          <Grid item xs={6}>
            <InputLabel>{t('beneficiary:grid:company')}</InputLabel>
            <Select
              fullWidth
              sx={{ height: '55%' }}
              name="companyId"
              defaultValue={initialValues.personId}
              onChange={(e: SelectChangeEvent) => {
                setCompanyId(e.target.value)
              }}>
              <MenuItem value="">
                <em>None</em>
              </MenuItem>
              {companies?.map((company) => {
                return (
                  <MenuItem key={company.id} value={company.id}>
                    {company.companyName}
                  </MenuItem>
                )
              })}
            </Select>
          </Grid>
          <Grid item xs={6}>
            <InputLabel>{t('beneficiary:grid:coordinatorRelation')}</InputLabel>
            <Select
              fullWidth
              sx={{ height: '55%' }}
              name="coordinatorRelation"
              defaultValue={initialValues.coordinatorRelation}
              onChange={(e: SelectChangeEvent) => {
                setPersonId(e.target.value)
              }}>
              <MenuItem value="">
                <em>None</em>
              </MenuItem>
              {coordinatorRelations?.map((relation) => {
                return (
                  <MenuItem key={relation} value={relation}>
                    {relation}
                  </MenuItem>
                )
              })}
            </Select>
          </Grid>
          <Grid item xs={6}>
            <InputLabel>{t('beneficiary:grid:coordinator')}</InputLabel>
            <Select
              fullWidth
              sx={{ height: '55%' }}
              name="coordinatorId"
              defaultValue={initialValues.coordinatorId}
              onChange={(e: SelectChangeEvent) => {
                setCoordinatorId(e.target.value)
              }}>
              <MenuItem value="">
                <em>None</em>
              </MenuItem>
              {coordinators?.map((coordinator) => {
                return (
                  <MenuItem key={coordinator.id} value={coordinator.id}>
                    {coordinator.person.firstName} {coordinator.person.lastName}
                  </MenuItem>
                )
              })}
            </Select>
          </Grid>
          <Grid item xs={6}>
            <InputLabel>{t('beneficiary:grid:countryCode')}</InputLabel>
            <Select
              fullWidth
              sx={{ height: '55%' }}
              name="countryCode"
              defaultValue={initialValues.countryCode}
              onChange={(e: SelectChangeEvent) => {
                setCountryCode(e.target.value)
              }}>
              <MenuItem value="">
                <em>None</em>
              </MenuItem>
              {countries?.map((country) => {
                return (
                  <MenuItem key={country.id} value={country.countryCode}>
                    {country.countryCode} - {country.name}
                  </MenuItem>
                )
              })}
            </Select>
          </Grid>
          <Grid item xs={6}>
            <InputLabel>{t('beneficiary:grid:city')}</InputLabel>
            <Select
              fullWidth
              sx={{ height: '55%' }}
              name="cityId"
              defaultValue={initialValues.cityId}
              onChange={(e: SelectChangeEvent) => {
                setCityId(e.target.value)
              }}>
              <MenuItem value="">
                <em>None</em>
              </MenuItem>
              {cities?.map((city) => {
                return (
                  <MenuItem key={city.id} value={city.id}>
                    {city.name}
                  </MenuItem>
                )
              })}
            </Select>
          </Grid>
          <Grid item xs={6}>
            <FormTextField
              type="text"
              name="description"
              autoComplete="target-amount"
              label={t('beneficiary:grid:description')}
              multiline
              rows={1.5}
              defaultValue={initialValues.description}
            />
          </Grid>
          <Grid item xs={6}>
            <SubmitButton fullWidth label={t('documents:cta:submit')} />
          </Grid>
          <Grid item xs={6}>
            <Link href={routes.admin.beneficiary.index} passHref>
              <Button>{t('documents:cta:cancel')}</Button>
            </Link>
          </Grid>
        </Grid>
      </Box>
    </GenericForm>
  )
}
Example #18
Source File: Form.tsx    From frontend with MIT License 4 votes vote down vote up
export default function Form() {
  const router = useRouter()
  const { t } = useTranslation()
  let id = router.query.id
  const [parentId, setParentId] = useState<string>('')
  const campaignTypes = useCampaignTypesList().data

  let initialValues: CampaignTypeFormData = {
    name: '',
    category: CampaignTypeCategory.others,
    description: '',
    parentId,
  }

  if (id) {
    id = String(id)
    const { data }: UseQueryResult<CampaignTypesResponse> = useCampaignType(id)

    initialValues = {
      name: data?.name || '',
      category: data?.category || '',
      description: data?.description || '',
      parentId: data?.parentId || '',
    }
  }
  const mutationFn = id ? useEditCampaignType(id) : useCreateCampaignType()

  const mutation = useMutation<
    AxiosResponse<CampaignTypesResponse>,
    AxiosError<ApiErrors>,
    CampaignTypeFormData
  >({
    mutationFn,
    onError: () => AlertStore.show(t('documents:alerts:error'), 'error'),
    onSuccess: () => {
      AlertStore.show(id ? t('documents:alerts:edit') : t('documents:alerts:create'), 'success')
      router.push(routes.admin.campaignTypes.index)
    },
  })

  async function onSubmit(data: CampaignTypeFormData) {
    data.parentId = parentId
    if (data.parentId === '') {
      delete data['parentId']
    }
    data.slug = createSlug(data.name)
    mutation.mutateAsync(data)
  }

  return (
    <GenericForm
      onSubmit={onSubmit}
      initialValues={initialValues}
      validationSchema={validationSchema}>
      <Box sx={{ height: '62.6vh', marginBottom: '9%' }}>
        <Typography variant="h5" component="h2" sx={{ textAlign: 'center' }}>
          {id ? t('campaign-types:forms:edit-heading') : t('campaign-types:forms:add-heading')}
        </Typography>
        <Grid sx={{ display: 'flex', marginTop: '1%' }}>
          <Grid item xs={6} sx={{ marginRight: '10%' }}>
            <FormTextField
              type="text"
              name="name"
              autoComplete="target-amount"
              label={t('campaign-types:grid:name')}
              multiline
              rows={1.5}
            />
          </Grid>
          <Grid item xs={6}>
            <FormTextField
              type="text"
              name="description"
              autoComplete="target-amount"
              label={t('campaign-types:grid:description')}
              multiline
              rows={3}
            />
          </Grid>
        </Grid>
        <Grid container spacing={2} sx={{ marginTop: '1%' }}>
          <Grid item xs={12}>
            <InputLabel>{t('campaign-types:grid:category')}</InputLabel>
            <Select
              fullWidth
              sx={{
                height: '55%',
              }}
              name="parentId"
              defaultValue={initialValues.parentId || ''}
              onChange={(e: SelectChangeEvent) => {
                setParentId(e.target.value)
              }}>
              <MenuItem value="">
                <em>None</em>
              </MenuItem>
              {Object.values(campaignTypes || [])?.map((type) => {
                return (
                  <MenuItem key={type.id} value={type.id}>
                    {type.name}
                  </MenuItem>
                )
              })}
            </Select>
          </Grid>
          <Grid item xs={6}>
            <SubmitButton fullWidth label={t('documents:cta:submit')} />
          </Grid>
          <Grid item xs={6}>
            <Link href={routes.admin.campaignTypes.index} passHref>
              <Button>{t('documents:cta:cancel')}</Button>
            </Link>
          </Grid>
        </Grid>
      </Box>
    </GenericForm>
  )
}
Example #19
Source File: index.tsx    From Search-Next with GNU General Public License v3.0 4 votes vote down vote up
Release: FC = () => {
  const [update, setUpdate] = React.useState(false);
  const [remind, setRemind] =
    React.useState<AccountUpdateMessageRemind>('popup');
  const [interval, setInterval] = React.useState(0);
  const [messageData, setMessageData] = React.useState({} as AuthMessage);

  const init = () => {
    const account = localStorage.getItem('account');
    const result = getAuthDataByKey(account ?? '', 'message');
    if (isBoolean(result?.update)) {
      setUpdate(result.update);
      setRemind('popup');
      setInterval(0);
    } else {
      const { update = {} } = result || {};
      const {
        update: privUpdate = true,
        remind = 'popup',
        interval = 0,
      } = update;
      setUpdate(privUpdate);
      setRemind(remind);
      setInterval(interval);
    }
    setMessageData(result);
  };

  const handleUpdate = (key: any, val: any) => {
    const account = localStorage.getItem('account');
    const updateData: any = {
      update,
      interval,
      remind,
      lastTime: dayjs(),
    };
    updateData[key] = val;
    const newMessageData = {
      ...messageData,
      update: updateData,
    };
    setMessageData(newMessageData);
    updateAuthDataByKey(account ?? '', 'message', newMessageData);
    init();
  };

  const onUpdateSwichChange = (
    _: React.ChangeEvent<HTMLInputElement>,
    checked: boolean,
  ) => {
    setUpdate(checked);
    handleUpdate('update', checked);
  };

  const onRemindChange = (e: SelectChangeEvent<any>) => {
    const value = e.target.value as AccountUpdateMessageRemind;
    setRemind(value);
    handleUpdate('remind', value);
  };

  const onIntervalChange = (e: SelectChangeEvent<any>) => {
    const value = e.target.value as number;
    setInterval(value);
    handleUpdate('interval', value);
  };

  useEffect(() => {
    init();
  }, []);

  return (
    <div>
      <ContentList>
        <Alert severity="info">
          <AlertTitle>提示</AlertTitle>
          修改任意配置都会重置版本更新时间间隔依赖的时间
        </Alert>
        <ItemCard
          title="版本更新提醒"
          desc="设置版本更新时是否提醒"
          action={<Switch checked={update} onChange={onUpdateSwichChange} />}
        />
        <ItemCard
          title="提醒方式"
          desc="设置版本更新提醒方式"
          action={
            <Select
              size="small"
              label="提醒方式"
              value={remind}
              options={[
                { label: '消息', value: 'message' },
                // { label: '通知', value: 'notification' },
                { label: '弹窗', value: 'popup' },
              ]}
              onChange={onRemindChange}
            />
          }
        />
        <ItemCard
          title="提醒间隔"
          desc="设置版本更新提醒时间间隔"
          action={
            <Select
              size="small"
              label="提醒间隔"
              value={interval}
              options={[
                { label: '随时', value: 0 },
                { label: '7天', value: 7 },
                { label: '30天', value: 30 },
                { label: '60天', value: 60 },
                { label: '90天', value: 90 },
              ]}
              onChange={onIntervalChange}
            />
          }
        />
      </ContentList>
    </div>
  );
}
Example #20
Source File: index.tsx    From Search-Next with GNU General Public License v3.0 4 votes vote down vote up
OtherApis: React.FC<PageProps> = (props) => {
  const { route, children } = props;
  const [iconApi, setIconApi] = React.useState('');
  const [apiStatus, setApiStatus] = React.useState<ApiStatus>({});

  const init = () => {
    const account = localStorage.getItem('account');
    const data = getOtherIconApi({
      userId: account ?? '',
      type: 'icon',
    });
    setIconApi(data.apiId);
    let map = {} as ApiStatus;
    websiteIconApis.forEach((i) => {
      map[i.id] = 'warning';
    });
    setApiStatus(map);
  };

  const onChange = (event: SelectChangeEvent<any>) => {
    const select = event.target.value;
    setIconApi(select);
    const account = localStorage.getItem('account');
    setOtherIconApi({
      userId: account ?? '',
      apiId: select,
      type: 'icon',
    });
  };

  const StatusChip = (status: string) => {
    const statusMap = {
      warning: (
        <>
          <PendingOutlined /> 等待响应
        </>
      ),
      success: (
        <>
          <Done /> 成功
        </>
      ),
      error: (
        <>
          <Close /> 失败
        </>
      ),
    };
    return (
      <Chip
        size="small"
        color={status as any}
        label={
          <div className="text-sm flex items-center gap-1">
            {(statusMap as any)[status as any]}
          </div>
        }
      />
    );
  };

  React.useEffect(() => {
    init();
  }, []);

  return (
    <div>
      <ContentList>
        <Alert severity="info">
          <AlertTitle>提示</AlertTitle>
          不同地区,不同网络下各API的表现可能不同,请选择最适合的API以提高使用体验。
        </Alert>
        <ItemAccordion
          title="Website Icon API"
          desc="设置获取网站图标的api"
          action={
            <Select
              label="API"
              value={iconApi}
              size="small"
              onChange={onChange}
              options={websiteIconApis.map((i) => ({
                label: i.name,
                value: i.id,
              }))}
            />
          }
        >
          <div className="flex items-center text-sm gap-1 pb-2">
            <PendingOutlined /> <span>等待响应</span>
            <Done /> <span>成功</span>
            <Close /> <span>失败</span> 状态仅作参考,具体以实际使用为准
          </div>
          {websiteIconApis.map((i) => {
            return (
              <AccordionDetailItem
                key={i.id}
                disabledRightPadding
                title={i.name}
                action={
                  <>
                    {StatusChip(apiStatus[i.id])}
                    <img
                      className={css`
                        display: none;
                      `}
                      src={`${i.url}google.com`}
                      alt={i.name}
                      onLoad={(v) => {
                        setApiStatus({ ...apiStatus, [i.id]: 'success' });
                      }}
                      onError={(err) => {
                        setApiStatus({ ...apiStatus, [i.id]: 'error' });
                      }}
                    />
                  </>
                }
              />
            );
          })}
        </ItemAccordion>
      </ContentList>
    </div>
  );
}
Example #21
Source File: index.tsx    From Search-Next with GNU General Public License v3.0 4 votes vote down vote up
Background: React.FC = () => {
  const [value, setValue] = React.useState<BgOptions>({} as BgOptions); // 选择背景类型
  const [selected, setSelected] = React.useState<AuthBackgroundType>('color');
  const [account, setAccount] = React.useState<AuthData>({} as AuthData); // 当前账户
  const [userBgSetting, setUserBgSetting] = React.useState<AuthBackground>(
    {} as AuthBackground,
  ); // 当前账户的背景设置数据
  const [expanded, setExpanded] = React.useState(false);

  const bgOptions: BgOptions[] = [
    { label: '纯色', value: 'color', canSelect: true, autoExpaneded: false },
    {
      label: '必应壁纸',
      value: 'random',
      canSelect: true,
      autoExpaneded: true,
    },
    {
      label: '每日一图',
      value: 'everyday',
      canSelect: true,
      autoExpaneded: false,
    },
    { label: '在线图片', value: 'link', canSelect: true, autoExpaneded: true },
  ];

  // 更新设置
  const updateBgSetting = (id: string, setting: AuthBackground) => {
    editAccount(id, {
      background: setting,
    });
  };

  // 选择背景类型
  const handleChange = (event: SelectChangeEvent<any>) => {
    const selected: AuthBackgroundType = event.target.value;
    const data = bgOptions.find((i) => i.value === selected);
    if (!data) return;
    setSelected(selected);
    setValue(data);
    setExpanded(data.autoExpaneded);
    if (data.canSelect === true) {
      const setting = {
        type: selected,
      };
      account._id && updateBgSetting(account._id, setting);
      setUserBgSetting(setting);
    }
  };

  // 初始化背景设置
  const init = () => {
    const data: AuthData = getAccount();
    setAccount(data);
    if (data && data.background) {
      const type = data.background.type;
      const option = bgOptions.find((i) => i.value === type);
      setValue(option || bgOptions[0]);
      setSelected(type || bgOptions[0].value);
      setUserBgSetting(data.background);
    } else {
      data._id &&
        updateBgSetting(data._id, {
          type: bgOptions[0].value,
        });
      setValue(bgOptions[0]);
      setSelected(bgOptions[0].value);
      setUserBgSetting({ type: bgOptions[0].value });
    }
  };

  React.useEffect(() => {
    init();
  }, []);

  return (
    <div>
      <Example data={userBgSetting} />
      <div className="flex gap-2 flex-col">
        <Alert severity="info">
          <AlertTitle>提示</AlertTitle>
          近期必应在国内访问可能受阻,会导致图片无法加载,出现此情况非本网站原因。
        </Alert>
        <ItemAccordion
          expanded={expanded}
          onChange={(_, expanded) => {
            setExpanded(expanded);
          }}
          title="个性化设置背景"
          desc="背景设置主要适用于主页"
          action={
            <Select
              label="背景类型"
              value={selected}
              size="small"
              onChange={handleChange}
              options={bgOptions}
            />
          }
          disableDetailPadding
        >
          {value.value === 'color' && (
            <Alert severity="info">
              设置为纯色背景,在纯色设置时,会自动应用当前主题配色。
            </Alert>
          )}
          {value.value === 'random' && (
            <Random
              data={userBgSetting.data as AuthBackgroundRandomData}
              onChange={(data) => {
                if (userBgSetting.type === 'random') {
                  const setting = { ...userBgSetting, data };
                  setUserBgSetting(setting);
                  account._id && updateBgSetting(account._id, setting);
                }
              }}
            />
          )}
          {value.value === 'everyday' && (
            <EveryDay data={userBgSetting.data as AuthBackgroundRandomData} />
          )}
          {value.value === 'link' && (
            <Link
              data={userBgSetting.data as AuthBackgroundLinkData}
              onChange={(url) => {
                if (userBgSetting.type === 'link') {
                  const data = { url };
                  const setting = { ...userBgSetting, data: data };
                  setUserBgSetting(setting);
                  account._id && updateBgSetting(account._id, setting);
                }
              }}
            />
          )}
        </ItemAccordion>
      </div>
    </div>
  );
}
Example #22
Source File: SettingsView.tsx    From react-flight-tracker with MIT License 4 votes vote down vote up
SettingsView: React.FC<Props> = (props) => {

  // Fields
  const contextName: string = ViewKeys.SettingsView;

  // Contexts
  const systemContext = useContext(SystemContext);
  const appContext = useContext(AppContext);

  const getSetting = (key: string, type: string) => {

    const value = systemContext.getSetting(key)
    if (typeof (value) === type)
      return value;

    return false;
  };

  const handleChange = (e: SelectChangeEvent) => {
    appContext.changeTheme(e.target.value);
  };

  const handleSettingsChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    systemContext.storeSetting(e.target.name, e.target.checked);
  };

  const renderAppSettings = () => {

    return (
      <Card>

        <CardContent>

          <Typography
            variant={'h6'}
            gutterBottom={true}>
            {'App settings'}
          </Typography>

          <FormGroup>
            <FormControl
              color='secondary'
              variant="filled"
              sx={{ m: 1, minWidth: 120 }}>

              <InputLabel
                id="demo-simple-select-filled-label">
                Theme change
              </InputLabel>
              <Select
                labelId="demo-simple-select-filled-label"
                id="demo-simple-select-filled"
                value={appContext.activeThemeName}
                onChange={handleChange}>

                <MenuItem
                  value={ThemeKeys.DarkTheme}>
                  {ThemeKeys.DarkTheme}
                </MenuItem>
                <MenuItem
                  value={ThemeKeys.LightTheme}>
                  {ThemeKeys.LightTheme}
                </MenuItem>
                <MenuItem
                  value={ThemeKeys.PineappleTheme}>
                  {ThemeKeys.PineappleTheme}
                </MenuItem>
              </Select>
            </FormControl>

          </FormGroup>
        </CardContent>
      </Card>
    );
  };

  const renderMapSettings = () => {

    return (
      <Card>

        <CardContent>

          <Typography
            variant={'h6'}
            gutterBottom={true}>
            {'Map settings'}
          </Typography>

          <FormGroup>
            <FormControlLabel
              control={
                <Switch
                  color='secondary'
                  name={SettingKeys.ShowDataOverlayOnMap}
                  checked={getSetting(SettingKeys.ShowDataOverlayOnMap, 'boolean')}
                  onChange={handleSettingsChange} />
              }
              label="Show data overlay on map"
            />
            <FormControlLabel
              control={
                <Switch
                  color='secondary'
                  name={SettingKeys.ShowLogOverlayOnMap}
                  checked={getSetting(SettingKeys.ShowLogOverlayOnMap, 'boolean')}
                  onChange={handleSettingsChange} />
              }
              label="Show log overlay on map"
            />
          </FormGroup>
        </CardContent>
      </Card>
    );
  };

  return (

    <ViewContainer
      isScrollLocked={true}>

      {renderAppSettings()}

      <Box sx={{ height: (theme) => theme.spacing(1) }} />

      {renderMapSettings()}
    </ViewContainer>
  );
}
Example #23
Source File: index.tsx    From yearn-watch-legacy with GNU Affero General Public License v3.0 4 votes vote down vote up
SearchInput = (props: SearchInputProps) => {
    const {
        onFilter,
        debounceWait,
        totalItems,
        foundItems,
        totalSubItems,
        foundSubItems,
    } = props;
    const [searchText, setSearchText] = useState('');
    const [isSearching, setIsSearching] = useState(false);
    const [filterVaultsWithWarnings, setFilterVaultsWithWarnings] =
        useState(false);
    const [healthCheckFilter, setHealthCheckFilter] = useState('');

    const debounceFilter = useCallback(
        debounce((newSearchText, flags) => {
            const newSearchTextLowerCase = newSearchText.toLowerCase();
            onFilter(newSearchTextLowerCase, flags, healthCheckFilter);
            setIsSearching(false);
        }, debounceWait),
        [debounceWait, isSearching]
    );

    // Event listener called on every change
    const onChange = useCallback(
        (event: ChangeEvent) => {
            const value = (event.target as HTMLInputElement).value;
            setIsSearching(true);
            setSearchText(value);
            debounceFilter(value, getCurrentFlags(filterVaultsWithWarnings));
        },
        [filterVaultsWithWarnings, searchText, isSearching, healthCheckFilter]
    );

    const onFilterVaultsWithWarnings = useCallback(
        (e: ChangeEvent<HTMLInputElement>) => {
            setFilterVaultsWithWarnings(e.target.checked);
            setIsSearching(true);
            const newSearchTextLowerCase = searchText.toLowerCase();
            onFilter(
                newSearchTextLowerCase,
                getCurrentFlags(e.target.checked),
                healthCheckFilter
            );
            setIsSearching(false);
        },
        [searchText, isSearching, healthCheckFilter]
    );
    const handleClickClearSearch = useCallback(() => {
        setSearchText('');
        setFilterVaultsWithWarnings(false);
        onFilter('', getCurrentFlags(false), '');
    }, [onFilter]);
    const healthCheckFilterChange = useCallback(
        (e: SelectChangeEvent<unknown>) => {
            setHealthCheckFilter((e.target as HTMLInputElement).value);
            setIsSearching(true);
            const newSearchTextLowerCase = searchText.toLowerCase();
            onFilter(
                newSearchTextLowerCase,
                getCurrentFlags(filterVaultsWithWarnings),
                (e.target as HTMLInputElement).value
            );
            setIsSearching(false);
        },
        [searchText, healthCheckFilter, isSearching]
    );

    const renderSearchingLabel = useCallback(() => {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        let render: any;
        if (isSearching) {
            render = (
                <div>
                    <ProgressSpinnerBar label="results" />
                </div>
            );
        } else {
            render = (
                <>
                    {healthCheckFilter !== '' && (
                        <WarningLabel
                            warningText={'HealthCheck Filter is ON!'}
                        />
                    )}
                    <ResultsLabel
                        title="Vaults"
                        totalItems={totalItems}
                        foundItems={foundItems}
                        displayFound={true}
                        isSearching={isSearching}
                    />
                    <ResultsLabel
                        title="Strategies"
                        totalItems={totalSubItems}
                        foundItems={foundSubItems}
                        displayFound={true}
                        isSearching={isSearching}
                    />
                </>
            );
        }

        return render;
    }, [isSearching, totalItems, foundItems, totalSubItems, foundSubItems]);

    return (
        <div>
            <StyledForm>
                <Grid container direction="row" alignItems="center" spacing={3}>
                    <Grid item xs={12} sm={6}>
                        <StyledContainer maxWidth="lg">
                            <StyledTextField
                                variant="outlined"
                                onChange={onChange}
                                type="search"
                                value={searchText}
                                placeholder="Search by vault/strategy address/name, strategist address, token name/symbol, share token symbol/name or API version."
                                InputProps={
                                    searchText == ''
                                        ? {
                                              startAdornment: (
                                                  <InputAdornment position="end">
                                                      <Search />
                                                  </InputAdornment>
                                              ),
                                          }
                                        : {
                                              endAdornment: (
                                                  <InputAdornment position="end">
                                                      <IconButton
                                                          aria-label="delete"
                                                          onClick={
                                                              handleClickClearSearch
                                                          }
                                                          size="large"
                                                      >
                                                          <Delete />
                                                      </IconButton>
                                                  </InputAdornment>
                                              ),
                                          }
                                }
                            />
                        </StyledContainer>
                    </Grid>
                    <Grid item xs={12} sm={3}>
                        <StyledContainer maxWidth="lg">
                            <StyledFormControlLabel
                                control={
                                    <Switch
                                        checked={filterVaultsWithWarnings}
                                        onChange={onFilterVaultsWithWarnings}
                                        color="primary"
                                    />
                                }
                                labelPlacement="start"
                                label="Vaults with warnings"
                            />
                        </StyledContainer>
                    </Grid>
                    <Grid item xs={12} sm={3}>
                        <StyledContainer maxWidth="lg">
                            <StyledFormControlLabel
                                control={
                                    <StyledSelect
                                        displayEmpty
                                        variant="standard"
                                        defaultValue=""
                                        value={healthCheckFilter}
                                        onChange={healthCheckFilterChange}
                                    >
                                        <MenuItem value="">All</MenuItem>
                                        <MenuItem value="Enabled">
                                            Enabled
                                        </MenuItem>
                                        <MenuItem value="Disabled">
                                            Disabled
                                        </MenuItem>
                                        <MenuItem value="None">
                                            Not Set
                                        </MenuItem>
                                    </StyledSelect>
                                }
                                labelPlacement="start"
                                label="HealthCheck"
                            />
                        </StyledContainer>
                    </Grid>
                </Grid>
            </StyledForm>
            <StyledContainerResult maxWidth="lg">
                {renderSearchingLabel()}
            </StyledContainerResult>
        </div>
    );
}
Example #24
Source File: AddLifecycleModal.tsx    From console with GNU Affero General Public License v3.0 4 votes vote down vote up
AddLifecycleModal = ({
  open,
  closeModalAndRefresh,
  classes,
  bucketName,
}: IReplicationModal) => {
  const dispatch = useDispatch();
  const distributedSetup = useSelector(selDistSet);
  const [loadingTiers, setLoadingTiers] = useState<boolean>(true);
  const [tiersList, setTiersList] = useState<ITiersDropDown[]>([]);
  const [addLoading, setAddLoading] = useState(false);
  const [isVersioned, setIsVersioned] = useState<boolean>(false);
  const [prefix, setPrefix] = useState("");
  const [tags, setTags] = useState<string>("");
  const [storageClass, setStorageClass] = useState("");

  const [ilmType, setIlmType] = useState<string>("expiry");
  const [targetVersion, setTargetVersion] = useState<"current" | "noncurrent">(
    "current"
  );

  const [lifecycleDays, setLifecycleDays] = useState<string>("");
  const [isFormValid, setIsFormValid] = useState<boolean>(false);
  const [expiredObjectDM, setExpiredObjectDM] = useState<boolean>(false);
  const [loadingVersioning, setLoadingVersioning] = useState<boolean>(true);

  useEffect(() => {
    if (loadingTiers) {
      api
        .invoke("GET", `/api/v1/admin/tiers`)
        .then((res: ITierResponse) => {
          const tiersList: ITierElement[] | null = get(res, "items", []);

          if (tiersList !== null && tiersList.length >= 1) {
            const objList = tiersList.map((tier: ITierElement) => {
              const tierType = tier.type;
              const value = get(tier, `${tierType}.name`, "");

              return { label: value, value: value };
            });

            setTiersList(objList);
            if (objList.length > 0) {
              setStorageClass(objList[0].value);
            }
          }
          setLoadingTiers(false);
        })
        .catch((err: ErrorResponseHandler) => {
          setLoadingTiers(false);
        });
    }
  }, [loadingTiers]);

  useEffect(() => {
    let valid = true;

    if (ilmType !== "expiry") {
      if (storageClass === "") {
        valid = false;
      }
    }
    setIsFormValid(valid);
  }, [ilmType, lifecycleDays, storageClass]);

  useEffect(() => {
    if (loadingVersioning && distributedSetup) {
      api
        .invoke("GET", `/api/v1/buckets/${bucketName}/versioning`)
        .then((res: BucketVersioning) => {
          setIsVersioned(res.is_versioned);
          setLoadingVersioning(false);
        })
        .catch((err: ErrorResponseHandler) => {
          dispatch(setModalErrorSnackMessage(err));
          setLoadingVersioning(false);
        });
    }
  }, [loadingVersioning, dispatch, bucketName, distributedSetup]);

  const addRecord = () => {
    let rules = {};

    if (ilmType === "expiry") {
      let expiry: { [key: string]: number } = {};

      if (targetVersion === "current") {
        expiry["expiry_days"] = parseInt(lifecycleDays);
      } else {
        expiry["noncurrentversion_expiration_days"] = parseInt(lifecycleDays);
      }

      rules = {
        ...expiry,
      };
    } else {
      let transition: { [key: string]: number | string } = {};
      if (targetVersion === "current") {
        transition["transition_days"] = parseInt(lifecycleDays);
        transition["storage_class"] = storageClass;
      } else {
        transition["noncurrentversion_transition_days"] =
          parseInt(lifecycleDays);
        transition["noncurrentversion_transition_storage_class"] = storageClass;
      }

      rules = {
        ...transition,
      };
    }

    const lifecycleInsert = {
      type: ilmType,
      prefix,
      tags,
      expired_object_delete_marker: expiredObjectDM,
      ...rules,
    };

    api
      .invoke(
        "POST",
        `/api/v1/buckets/${bucketName}/lifecycle`,
        lifecycleInsert
      )
      .then(() => {
        setAddLoading(false);
        closeModalAndRefresh(true);
      })
      .catch((err: ErrorResponseHandler) => {
        setAddLoading(false);
        dispatch(setModalErrorSnackMessage(err));
      });
  };

  return (
    <ModalWrapper
      modalOpen={open}
      onClose={() => {
        closeModalAndRefresh(false);
      }}
      title="Add Lifecycle Rule"
      titleIcon={<LifecycleConfigIcon />}
    >
      {loadingTiers && (
        <Grid container className={classes.loadingBox}>
          <Grid item xs={12}>
            <LinearProgress />
          </Grid>
        </Grid>
      )}

      {!loadingTiers && (
        <form
          noValidate
          autoComplete="off"
          onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
            e.preventDefault();
            setAddLoading(true);
            addRecord();
          }}
        >
          <Grid container>
            <Grid item xs={12} className={classes.formScrollable}>
              <Grid item xs={12}>
                <Grid container spacing={1}>
                  <Grid item xs={12}>
                    <RadioGroupSelector
                      currentSelection={ilmType}
                      id="ilm_type"
                      name="ilm_type"
                      label="Type of lifecycle"
                      onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
                        setIlmType(e.target.value as string);
                      }}
                      selectorOptions={[
                        { value: "expiry", label: "Expiry" },
                        { value: "transition", label: "Transition" },
                      ]}
                    />
                  </Grid>
                  {isVersioned && (
                    <Grid item xs={12}>
                      <SelectWrapper
                        value={targetVersion}
                        id="object_version"
                        name="object_version"
                        label="Object Version"
                        onChange={(e) => {
                          setTargetVersion(
                            e.target.value as "current" | "noncurrent"
                          );
                        }}
                        options={[
                          { value: "current", label: "Current Version" },
                          { value: "noncurrent", label: "Non-Current Version" },
                        ]}
                      />
                    </Grid>
                  )}

                  <Grid item xs={12}>
                    <InputBoxWrapper
                      id="expiry_days"
                      name="expiry_days"
                      onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
                        if (e.target.validity.valid) {
                          setLifecycleDays(e.target.value);
                        }
                      }}
                      pattern={"[0-9]*"}
                      label="After"
                      value={lifecycleDays}
                      overlayObject={
                        <InputUnitMenu
                          id={"expire-current-unit"}
                          unitSelected={"days"}
                          unitsList={[{ label: "Days", value: "days" }]}
                          disabled={true}
                        />
                      }
                    />
                  </Grid>

                  {ilmType === "expiry" ? (
                    <Fragment></Fragment>
                  ) : (
                    <Fragment>
                      <Grid item xs={12}>
                        <SelectWrapper
                          label="To Tier"
                          id="storage_class"
                          name="storage_class"
                          value={storageClass}
                          onChange={(e: SelectChangeEvent<string>) => {
                            setStorageClass(e.target.value as string);
                          }}
                          options={tiersList}
                        />
                      </Grid>
                    </Fragment>
                  )}
                  <Grid item xs={12} className={classes.formFieldRowFilter}>
                    <Accordion>
                      <AccordionSummary>
                        <Typography>Filters</Typography>
                      </AccordionSummary>
                      <AccordionDetails>
                        <Grid item xs={12}>
                          <InputBoxWrapper
                            id="prefix"
                            name="prefix"
                            onChange={(
                              e: React.ChangeEvent<HTMLInputElement>
                            ) => {
                              setPrefix(e.target.value);
                            }}
                            label="Prefix"
                            value={prefix}
                          />
                        </Grid>
                        <Grid item xs={12}>
                          <QueryMultiSelector
                            name="tags"
                            label="Tags"
                            elements={""}
                            onChange={(vl: string) => {
                              setTags(vl);
                            }}
                            keyPlaceholder="Tag Key"
                            valuePlaceholder="Tag Value"
                            withBorder
                          />
                        </Grid>
                      </AccordionDetails>
                    </Accordion>
                  </Grid>
                  {ilmType === "expiry" && targetVersion === "noncurrent" && (
                    <Grid item xs={12} className={classes.formFieldRowFilter}>
                      <Accordion>
                        <AccordionSummary>
                          <Typography>Advanced</Typography>
                        </AccordionSummary>
                        <AccordionDetails>
                          <Grid item xs={12}>
                            <FormSwitchWrapper
                              value="expired_delete_marker"
                              id="expired_delete_marker"
                              name="expired_delete_marker"
                              checked={expiredObjectDM}
                              onChange={(
                                event: React.ChangeEvent<HTMLInputElement>
                              ) => {
                                setExpiredObjectDM(event.target.checked);
                              }}
                              label={"Expire Delete Marker"}
                              description={
                                "Remove the reference to the object if no versions are left"
                              }
                            />
                          </Grid>
                        </AccordionDetails>
                      </Accordion>
                    </Grid>
                  )}
                </Grid>
              </Grid>
            </Grid>
            <Grid item xs={12} className={classes.modalButtonBar}>
              <Button
                type="button"
                variant="outlined"
                color="primary"
                disabled={addLoading}
                onClick={() => {
                  closeModalAndRefresh(false);
                }}
              >
                Cancel
              </Button>
              <Button
                type="submit"
                variant="contained"
                color="primary"
                disabled={addLoading || !isFormValid}
              >
                Save
              </Button>
            </Grid>
            {addLoading && (
              <Grid item xs={12}>
                <LinearProgress />
              </Grid>
            )}
          </Grid>
        </form>
      )}
    </ModalWrapper>
  );
}
Example #25
Source File: Coupon.tsx    From Cromwell with MIT License 4 votes vote down vote up
CouponPage = () => {
    const { id: couponId } = useParams<{ id: string }>();
    const client = getGraphQLClient();
    const [data, setData] = useState<TCoupon | null>(null);
    const [pickedCategories, setPickedCategories] = useState<TProductCategory[] | null>(null);
    const [pickedProducts, setPickedProducts] = useState<TProduct[] | null>(null);
    const [notFound, setNotFound] = useState(false);
    const [isSaving, setIsSaving] = useState(false);
    const [couponLoading, setCouponLoading] = useState<boolean>(false);
    const history = useHistory();
    const [canValidate, setCanValidate] = useState(false);

    const getCouponData = async (id: number) => {
        let couponData: TCoupon;
        try {
            couponData = await client.getCouponById(id,
                gql`
                    fragment AdminPanelCouponFragment on Coupon {
                        id
                        createDate
                        updateDate
                        pageTitle
                        pageDescription
                        meta {
                            keywords
                        }
                        isEnabled
                        discountType
                        value
                        code
                        description
                        allowFreeShipping
                        minimumSpend
                        maximumSpend
                        categoryIds
                        productIds
                        expiryDate
                        usageLimit
                        customMeta (keys: ${JSON.stringify(getCustomMetaKeysFor(EDBEntity.Coupon))})
                    }`, 'AdminPanelCouponFragment');
            if (couponData) {
                setData(couponData);
            }
        } catch (e) {
            console.error(e)
        }

        if (!couponData) {
            setNotFound(true);
        }
        return couponData;
    }

    const init = async () => {
        setCouponLoading(true);

        let couponData: TCoupon;

        if (couponId && couponId !== 'new') {
            couponData = await getCouponData(parseInt(couponId));
        }

        if (couponId === 'new') {
            setData({} as any);
        }

        setCouponLoading(false);


        if (couponData?.categoryIds?.length) {
            const categories = await Promise.all(couponData.categoryIds.map(async id => {
                try {
                    return await client.getProductCategoryById(Number(id));
                } catch (error) {
                    console.error(error);
                }
            }));
            setPickedCategories(categories ?? []);
        } else setPickedCategories([]);

        if (couponData?.productIds?.length) {
            const products = await Promise.all(couponData.productIds.map(async id => {
                try {
                    return await client.getProductById(Number(id));
                } catch (error) {
                    console.error(error);
                }
            }));
            setPickedProducts(products ?? []);
        } else setPickedProducts([]);
    }

    useEffect(() => {
        init();
    }, []);

    const handleSave = async () => {
        setCanValidate(true);
        if (!data?.code || !data.value || !data.discountType) return;

        setIsSaving(true);

        const inputData: TCouponInput = {
            slug: data.slug,
            pageTitle: data.pageTitle,
            pageDescription: data.pageDescription,
            meta: data.meta && {
                keywords: data.meta.keywords,
            },
            isEnabled: data.isEnabled,
            discountType: data.discountType,
            value: data.value,
            code: data.code,
            description: data.description,
            allowFreeShipping: data.allowFreeShipping,
            minimumSpend: data.minimumSpend ? parseFloat(data.minimumSpend as any) : null,
            maximumSpend: data.maximumSpend ? parseFloat(data.maximumSpend as any) : null,
            categoryIds: data.categoryIds,
            productIds: data.productIds,
            expiryDate: data.expiryDate,
            usageLimit: data.usageLimit,
            customMeta: Object.assign({}, data.customMeta, await getCustomMetaFor(EDBEntity.Coupon)),
        }

        if (couponId === 'new') {
            try {
                const newData = await client?.createCoupon(inputData);
                toast.success('Created coupon!');
                history.replace(`${couponPageInfo.baseRoute}/${newData.id}`)
                await getCouponData(newData.id);
            } catch (e) {
                toast.error('Failed to create coupon');
                console.error(e);
            }
        } else {
            try {
                await client?.updateCoupon(data.id, inputData);
                await getCouponData(data.id);
                toast.success('Saved!');
            } catch (e) {
                toast.error('Failed to save');
                console.error(e)
            }
        }
        setIsSaving(false);
        setCanValidate(false);
    }

    const handleInputChange = (prop: keyof TCoupon, val: any) => {
        if (data) {
            setData((prevData) => {
                const newData = Object.assign({}, prevData);
                (newData[prop] as any) = val;
                return newData;
            });
        }
    }

    const handleGenerateCode = () => {
        handleInputChange('code', getRandStr(8).toUpperCase());
    }

    const handleSearchCategory = async (text: string, params: TPagedParams<TProductCategory>) => {
        return client?.getFilteredProductCategories({
            filterParams: {
                nameSearch: text
            },
            pagedParams: params
        });
    }

    const handleSearchProduct = async (text: string, params: TPagedParams<TProduct>) => {
        return client?.getFilteredProducts({
            filterParams: {
                nameSearch: text
            },
            pagedParams: params
        });
    }

    const refetchMeta = async () => {
        if (!couponId) return;
        const data = await getCouponData(parseInt(couponId));
        return data?.customMeta;
    };

    if (notFound) {
        return (
            <div className={styles.CouponPage}>
                <div className={styles.notFoundPage}>
                    <p className={styles.notFoundText}>Coupon not found</p>
                </div>
            </div>
        )
    }

    let pageFullUrl;
    if (data) {
        pageFullUrl = serviceLocator.getFrontendUrl() + resolvePageRoute('coupon', { slug: data.slug ?? data.id + '' });
    }

    return (
        <div className={styles.CouponPage}>
            <div className={styles.header}>
                <div className={styles.headerLeft}>
                    <IconButton
                        onClick={() => window.history.back()}
                    >
                        <ArrowBackIcon style={{ fontSize: '18px' }} />
                    </IconButton>
                    <p className={commonStyles.pageTitle}>coupon</p>
                </div>
                <div className={styles.headerActions}>
                    {pageFullUrl && (
                        <Tooltip title="Open coupon in the new tab">
                            <IconButton
                                style={{ marginRight: '10px' }}
                                className={styles.openPageBtn}
                                aria-label="open"
                                onClick={() => { window.open(pageFullUrl, '_blank'); }}
                            >
                                <OpenInNewIcon />
                            </IconButton>
                        </Tooltip>
                    )}
                    <Button variant="contained" color="primary"
                        className={styles.saveBtn}
                        size="small"
                        disabled={isSaving}
                        onClick={handleSave}>Save</Button>
                </div>
            </div>
            <div className={styles.fields}>
                {couponLoading && (
                    Array(8).fill(1).map((it, index) => (
                        <Skeleton style={{ marginBottom: '10px' }} key={index} height={"50px"} />
                    ))
                )}
                {!couponLoading && (
                    <Grid container spacing={3}>
                        <Grid item xs={12} sm={6}>
                            <TextField label="Code"
                                value={data?.code || ''}
                                fullWidth
                                variant="standard"
                                className={styles.textField}
                                onChange={(e) => { handleInputChange('code', e.target.value) }}
                                error={canValidate && !data?.code}
                                InputProps={{
                                    endAdornment: (
                                        <InputAdornment position="end">
                                            <Tooltip title="Generate code">
                                                <IconButton
                                                    aria-label="Generate code"
                                                    onClick={handleGenerateCode}
                                                    edge="end"
                                                >
                                                    {<SmartButtonIcon />}
                                                </IconButton>
                                            </Tooltip>
                                        </InputAdornment>
                                    ),
                                }}
                            />
                        </Grid>
                        <Grid item xs={12} sm={6}></Grid>
                        <Grid item xs={12} sm={6}>
                            <Select
                                fullWidth
                                variant="standard"
                                label="Discount type"
                                value={(data?.discountType ?? '')}
                                onChange={(event: SelectChangeEvent<unknown>) => {
                                    handleInputChange('discountType', event.target.value)
                                }}
                                error={canValidate && !data?.discountType}
                                options={[
                                    {
                                        value: 'fixed',
                                        label: 'Fixed'
                                    },
                                    {
                                        value: 'percentage',
                                        label: 'Percentage'
                                    }
                                ]}
                            />
                        </Grid>
                        <Grid item xs={12} sm={6}>
                            <TextField
                                label="Discount value"
                                className={styles.textField}
                                fullWidth
                                variant="standard"
                                value={data?.value || ''}
                                error={canValidate && !data?.value}
                                onChange={(e) => {
                                    const val = Number(e.target.value);
                                    if (!isNaN(val)) handleInputChange('value', val);
                                }}
                            />
                        </Grid>
                        <Grid item xs={12} sm={12}>
                            <TextField
                                label="Description"
                                className={styles.textField}
                                fullWidth
                                multiline
                                variant="standard"
                                value={data?.description || ''}
                                onChange={(e) => { handleInputChange('description', e.target.value) }}
                            />
                        </Grid>
                        <Grid item xs={12} sm={6}>
                            <TextField
                                label="Cart total minimum"
                                className={styles.textField}
                                fullWidth
                                multiline
                                variant="standard"
                                value={data?.minimumSpend || ''}
                                onChange={(e) => { handleInputChange('minimumSpend', e.target.value) }}
                                InputProps={{
                                    inputComponent: NumberFormatCustom as any,
                                }}
                            />
                        </Grid>
                        <Grid item xs={12} sm={6}>
                            <TextField
                                label="Cart total maximum"
                                className={styles.textField}
                                fullWidth
                                multiline
                                variant="standard"
                                value={data?.maximumSpend || ''}
                                onChange={(e) => { handleInputChange('maximumSpend', e.target.value) }}
                                InputProps={{
                                    inputComponent: NumberFormatCustom as any,
                                }}
                            />
                        </Grid>
                        <Grid item xs={12} sm={6}>
                            <LocalizationProvider dateAdapter={AdapterDateFns}>
                                <DatePicker
                                    label="Expiry date"
                                    value={data?.expiryDate}
                                    onChange={(newValue) => {
                                        if (!newValue) {
                                            handleInputChange('expiryDate', null);
                                            return;
                                        }
                                        const date = new Date(newValue);
                                        if (isNaN(date.getTime())) {
                                            handleInputChange('expiryDate', null);
                                            return;
                                        }
                                        handleInputChange('expiryDate', date);
                                    }}
                                    renderInput={(params) => <TextField
                                        variant="standard"
                                        fullWidth
                                        {...params}
                                    />}
                                />
                            </LocalizationProvider>
                        </Grid>
                        <Grid item xs={12} sm={6}>
                            <TextField
                                label="Usage limit"
                                className={styles.textField}
                                fullWidth
                                variant="standard"
                                value={data?.usageLimit || ''}
                                onChange={(e) => {
                                    const val = Number(e.target.value);
                                    if (!isNaN(val)) handleInputChange('usageLimit', val);
                                }}
                            />
                        </Grid>
                        <Grid item xs={12} sm={12}>
                            {pickedCategories ? (
                                <Autocomplete<TProductCategory>
                                    multiple
                                    loader={handleSearchCategory}
                                    onSelect={(data: TProductCategory[]) => {
                                        if (!data?.length) handleInputChange('categoryIds', null);
                                        else handleInputChange('categoryIds', data.map(cat => cat.id));
                                    }}
                                    getOptionLabel={(data) => `${data.name} (id: ${data.id}${data?.parent?.id ? `; parent id: ${data.parent.id}` : ''})`}
                                    getOptionValue={(data) => data.name}
                                    fullWidth
                                    className={styles.textField}
                                    defaultValue={pickedCategories}
                                    label={"Categories"}
                                />
                            ) : <LoadBox size={30} />}
                        </Grid>
                        <Grid item xs={12} sm={12}>
                            {pickedProducts ? (
                                <Autocomplete<TProduct>
                                    multiple
                                    loader={handleSearchProduct}
                                    onSelect={(data: TProduct[]) => {
                                        if (!data?.length) handleInputChange('productIds', null);
                                        else handleInputChange('productIds', data.map(cat => cat.id));
                                    }}
                                    getOptionLabel={(data) => `${data.name} (id: ${data.id})`}
                                    getOptionValue={(data) => data.name}
                                    fullWidth
                                    className={styles.textField}
                                    defaultValue={pickedProducts}
                                    label={"Products"}
                                />
                            ) : <LoadBox size={30} />}
                        </Grid>
                        <Grid item xs={12} >
                            {data && (
                                <RenderCustomFields
                                    entityType={EDBEntity.Coupon}
                                    entityData={data}
                                    refetchMeta={refetchMeta}
                                />
                            )}
                        </Grid>
                    </Grid>
                )}
            </div>
        </div>
    )
}
Example #26
Source File: GalleryBlock.tsx    From Cromwell with MIT License 4 votes vote down vote up
export function GalleryBlockSidebar(props: TBlockMenuProps) {
    const forceUpdate = useForceUpdate();
    const data = props.block?.getData();

    const handleChange = (key: keyof TCromwellBlockData['gallery'], value: any) => {
        const data = props.block?.getData();
        if (!data.gallery) data.gallery = {};
        if (!data.gallery.images) data.gallery.images = [];
        props.modifyData?.(Object.assign({}, data, {
            gallery: { ...data.gallery, [key]: value }
        }));
        forceUpdate();
        props.block?.getContentInstance?.()?.forceUpdate();
    }

    const handleNumberInput = (name: keyof TCromwellBlockData['gallery']) => (e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
        let val = parseFloat(e.target.value);
        if (isNaN(val)) val = undefined;
        handleChange(name, val);
    }

    const handleSelectTextInput = (name: keyof TCromwellBlockData['gallery']) => (e: SelectChangeEvent<unknown>) => {
        let val = e.target.value;
        if (val === '') val = undefined;
        handleChange(name, val);
    }

    const handleBoolInput = (name: keyof TCromwellBlockData['gallery']) => (event: React.ChangeEvent<HTMLInputElement>, checked: boolean) => {
        handleChange(name, checked ?? false);
    }

    return (
        <div>
            <div className={styles.settingsHeader}>
                <PhotoLibraryIcon />
                {props.isGlobalElem(props.getBlockElementById(data?.id)) && (
                    <div className={styles.headerIcon}>
                        <Tooltip title="Global block">
                            <PublicIcon />
                        </Tooltip>
                    </div>
                )}
                <h3 className={styles.settingsTitle}>Gallery settings</h3>
            </div>
            <GalleryPicker
                images={data?.gallery?.images}
                onChange={(val) => handleChange('images', val)}
                className={styles.settingsInput}
                editLink
            />
            <TextField
                fullWidth
                onChange={handleNumberInput('visibleSlides')}
                value={data?.gallery?.visibleSlides ?? 1}
                className={styles.settingsInput}
                type="number"
                variant="standard"
                label="Slides Per View" />
            <TextField
                fullWidth
                onChange={handleNumberInput('width')}
                value={data?.gallery?.width ?? ''}
                className={styles.settingsInput}
                type="number"
                variant="standard"
                label="Width (px)" />
            <TextField
                onChange={handleNumberInput('height')}
                fullWidth
                value={data?.gallery?.height ?? ''}
                className={styles.settingsInput}
                type="number"
                variant="standard"
                label="Height (px)" />
            <TextField
                onChange={handleNumberInput('ratio')}
                fullWidth
                value={data?.gallery?.ratio ?? ''}
                className={styles.settingsInput}
                type="number"
                variant="standard"
                label="Ratio width:height" />
            <TextField
                onChange={(event) => {
                    handleNumberInput('interval')(event);
                    handleBoolInput('autoPlay')(event as any, false);
                    setTimeout(() => handleBoolInput('autoPlay')(event as any, true), 10);
                }}
                fullWidth
                value={data?.gallery?.interval ?? ''}
                className={styles.settingsInput}
                type="number"
                variant="standard"
                label="Interval between slides, ms" />
            {/* <TextField
                onChange={handleNumberInput('speed')}
                fullWidth
                value={data?.gallery?.speed ?? null}
                className={styles.settingsInput}
                type="number"
                    variant="standard"
                label="Transition time between slides, ms" /> */}
            <FormControlLabel
                control={
                    <Checkbox
                        checked={!!data?.gallery?.autoPlay}
                        onChange={handleBoolInput('autoPlay')}
                        color="primary"
                    />
                }
                label="Auto play"
            />
            <FormControlLabel
                control={
                    <Checkbox
                        checked={!!data?.gallery?.navigation}
                        onChange={handleBoolInput('navigation')}
                        color="primary"
                    />
                }
                label="Show navigation"
            />
            <FormControlLabel
                control={
                    <Checkbox
                        checked={!!data?.gallery?.pagination}
                        onChange={handleBoolInput('pagination')}
                        color="primary"
                    />
                }
                label="Show pagination"
            />
            <FormControlLabel
                control={
                    <Checkbox
                        checked={!!data?.gallery?.thumbs}
                        onChange={handleBoolInput('thumbs')}
                        color="primary"
                    />
                }
                label="Show thumbnails"
            />
            <FormControlLabel
                control={
                    <Checkbox
                        checked={!!data?.gallery?.loop}
                        onChange={handleBoolInput('loop')}
                        color="primary"
                    />
                }
                label="Loop slides"
            />
            <FormControlLabel
                control={
                    <Checkbox
                        checked={!!data?.gallery?.fullscreen}
                        onChange={handleBoolInput('fullscreen')}
                        color="primary"
                    />
                }
                label="Enable lightbox pop-up"
            />
            <TextField
                onChange={handleNumberInput('spaceBetween')}
                fullWidth
                value={data?.gallery?.spaceBetween ?? null}
                className={styles.settingsInput}
                type="number"
                variant="standard"
                label="Space between slides, px" />
            <Select
                label="Image fit"
                className={styles.settingsInput}
                fullWidth
                onChange={handleSelectTextInput('backgroundSize')}
                variant="standard"
                value={data?.gallery?.backgroundSize ?? 'cover'}
                options={[{ value: 'contain', label: 'Contain' }, { value: 'cover', label: 'Cover' }]}
            />
            {/* <FormControl
                fullWidth
                className={styles.settingsInput} >
                <InputLabel >Effect</InputLabel>
                <Select
                    fullWidth
                    onChange={handleTextInput('effect')}
                                variant="standard"
                    value={data?.gallery?.effect ?? 'slide'}
                >
                    <MenuItem value={'slide'}>slide</MenuItem>
                    <MenuItem value={'fade'}>fade</MenuItem>
                    <MenuItem value={'cube'}>cube</MenuItem>
                    <MenuItem value={'coverflow'}>coverflow</MenuItem>
                    <MenuItem value={'flip'}>flip</MenuItem>
                </Select>
            </FormControl> */}
            <StylesEditor
                forceUpdate={forceUpdate}
                blockProps={props}
            />
        </div>
    );
}
Example #27
Source File: ImageBlock.tsx    From Cromwell with MIT License 4 votes vote down vote up
export function ImageBlockSidebar(props: TBlockMenuProps) {
    const forceUpdate = useForceUpdate();
    const data = props.block?.getData();
    const imageData = Object.assign({}, props.block?.getContentInstance()?.props, data?.image)

    const handleChange = (key: keyof TCromwellBlockData['image'], value: any) => {
        const data = props.block?.getData();
        if (!data.image) data.image = {};
        (data.image[key] as any) = value;
        props.modifyData?.(data);
        forceUpdate();
    }

    const handleNumberInput = (name: keyof TCromwellBlockData['image']) => (e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
        let val = parseInt(e.target.value);
        if (isNaN(val)) val = undefined;
        handleChange(name, val)
    }

    const handleTextInput = (name: keyof TCromwellBlockData['image']) => (e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement> | SelectChangeEvent<unknown>) => {
        let val = e.target.value;
        if (val === '') val = undefined;
        handleChange(name, val)
    }

    return (
        <div>
            <div className={styles.settingsHeader}>
                <ImageIcon />
                {props.isGlobalElem(props.getBlockElementById(data?.id)) && (
                    <div className={styles.headerIcon}>
                        <Tooltip title="Global block">
                            <PublicIcon />
                        </Tooltip>
                    </div>
                )}
                <h3 className={styles.settingsTitle}>Image settings</h3>
            </div>
            <ImagePicker
                value={imageData?.src}
                style={{ borderBottom: '1px solid #999' }}
                placeholder={"Pick an image"}
                onChange={(val) => handleChange('src', val)}
                className={styles.settingsInput}
            />
            <TextField
                fullWidth
                onChange={handleTextInput('link')}
                value={imageData?.link}
                className={styles.settingsInput}
                variant="standard"
                label="Link to" />
            <TextField
                fullWidth
                onChange={handleNumberInput('width')}
                value={imageData?.width}
                className={styles.settingsInput}
                type="number"
                variant="standard"
                label="Width (px)" />
            <TextField
                onChange={handleNumberInput('height')}
                fullWidth
                value={imageData?.height}
                className={styles.settingsInput}
                type="number"
                variant="standard"
                label="Height (px)" />
            <TextField
                onChange={handleTextInput('alt')}
                fullWidth
                value={imageData?.alt}
                className={styles.settingsInput}
                variant="standard"
                label="Alt" />
            <Select
                label="Image fit"
                className={styles.settingsInput}
                fullWidth
                onChange={handleTextInput('objectFit')}
                variant="standard"
                value={imageData?.objectFit ?? 'contain'}
                options={[{ value: 'contain', label: 'Contain' }, { value: 'cover', label: 'Cover' }]}
            />

            <StylesEditor
                forceUpdate={forceUpdate}
                blockProps={props}
            />
        </div>
    );
}
Example #28
Source File: User.tsx    From Cromwell with MIT License 4 votes vote down vote up
export default function UserPage() {
    const { id: userId } = useParams<{ id: string }>();
    const client = getGraphQLClient();
    const [notFound, setNotFound] = useState(false);
    const [passwordInput, setPasswordInput] = useState('');
    const history = useHistory();
    const [userData, setUserData] = useState<TUser | undefined | null>(null);
    const [showPassword, setShowPassword] = useState(false);
    const isNew = userId === 'new';
    const [canValidate, setCanValidate] = useState(false);

    // Support old and new address format
    const { addressString, addressJson } = parseAddress(userData?.address);

    const handleClickShowPassword = () => {
        setShowPassword(!showPassword);
    }

    const getUser = async (id: number) => {
        let data: TUser | undefined;
        try {
            data = await client?.getUserById(id,
                gql`
                fragment AdminPanelUserFragment on User {
                    id
                    slug
                    createDate
                    updateDate
                    isEnabled
                    pageTitle
                    pageDescription
                    meta {
                        keywords
                    }
                    fullName
                    email
                    avatar
                    bio
                    phone
                    address
                    role
                    customMeta (keys: ${JSON.stringify(getCustomMetaKeysFor(EDBEntity.User))})
                }`, 'AdminPanelUserFragment');
        } catch (e) { console.error(e) }

        return data;
    }


    const init = async () => {
        if (userId && !isNew) {
            const data = await getUser(parseInt(userId));
            if (data?.id) {
                setUserData(data);
            } else setNotFound(true);

        } else if (isNew) {
            setUserData({} as any);
        }
    }

    useEffect(() => {
        init();
    }, []);

    const refetchMeta = async () => {
        if (!userId) return;
        const data = await getUser(parseInt(userId));
        return data?.customMeta;
    };


    const handleInputChange = (prop: keyof TUser, val: any) => {
        if (userData) {
            setUserData((prevData) => {
                const newData = Object.assign({}, prevData);
                (newData[prop] as any) = val;
                return newData;
            });
        }
    }

    const getInput = async (): Promise<TUpdateUser> => ({
        slug: userData.slug,
        pageTitle: userData.pageTitle,
        pageDescription: userData.pageDescription,
        fullName: userData.fullName,
        email: userData.email,
        avatar: userData.avatar,
        bio: userData.bio,
        phone: userData.phone,
        address: userData.address,
        role: userData.role,
        customMeta: Object.assign({}, userData.customMeta, await getCustomMetaFor(EDBEntity.User)),
    });

    const handleSave = async () => {
        setCanValidate(true);
        const inputData = await getInput();

        if (!inputData.email || !inputData.fullName || !inputData.role) return;

        if (isNew) {
            if (!passwordInput) return;
            try {
                const createInput: TCreateUser = {
                    ...inputData,
                    password: passwordInput
                }
                const newData = await client?.createUser(createInput);
                toast.success('Created user');
                history.replace(`${userPageInfo.baseRoute}/${newData.id}`);
                setUserData(newData);
            } catch (e) {
                toast.error('Failed to create user');
                console.error(e);
            }

        } else if (userData?.id) {
            try {
                await client?.updateUser(userData.id, inputData);
                const newData = await getUser(parseInt(userId));
                setUserData(newData);
                toast.success('Saved!');

                const currentUser: TUser | undefined = getStoreItem('userInfo');
                if (currentUser?.id && currentUser.id === newData.id) {
                    setStoreItem('userInfo', userData);
                }

            } catch (e) {
                toast.error('Failed to save');
                console.error(e);
            }
        }
        setCanValidate(false);
    }

    if (notFound) {
        return (
            <div className={styles.UserPage}>
                <div className={styles.notFoundPage}>
                    <p className={styles.notFoundText}>User not found</p>
                </div>
            </div>
        )
    }

    return (
        <div className={styles.UserPage}>
            <div className={styles.header}>
                <div className={styles.headerLeft}>
                    <IconButton
                        onClick={() => window.history.back()}
                    >
                        <ArrowBackIcon style={{ fontSize: '18px' }} />
                    </IconButton>
                    <p className={commonStyles.pageTitle}>account</p>
                </div>
                <div className={styles.headerActions}>
                    <Button variant="contained" color="primary"
                        className={styles.saveBtn}
                        size="small"
                        onClick={handleSave}>
                        Save</Button>
                </div>
            </div>
            <div className={styles.fields}>
                <Grid container spacing={3}>
                    <Grid item xs={12} sm={6} style={{ display: 'flex', alignItems: 'flex-end' }}>
                        <TextField
                            label="Name"
                            value={userData?.fullName || ''}
                            fullWidth
                            variant="standard"
                            className={styles.field}
                            onChange={(e) => { handleInputChange('fullName', e.target.value) }}
                            error={canValidate && !userData?.fullName}
                        />
                    </Grid>
                    <Grid item xs={12} sm={6}>
                        <ImagePicker
                            label="Avatar"
                            onChange={(val) => { handleInputChange('avatar', val) }}
                            value={userData?.avatar ?? null}
                            className={styles.imageField}
                            classes={{ image: styles.image }}
                            backgroundSize='cover'
                            width="50px"
                            height="50px"
                            showRemove
                        />
                    </Grid>
                    <Grid item xs={12} sm={6}>
                        <TextField
                            label="E-mail"
                            value={userData?.email || ''}
                            fullWidth
                            variant="standard"
                            className={styles.field}
                            onChange={(e) => { handleInputChange('email', e.target.value) }}
                            error={canValidate && !userData?.email}
                        />
                    </Grid>
                    {isNew && (
                        <Grid item xs={12} sm={6}>
                            <TextField
                                label="Password"
                                value={passwordInput || ''}
                                type={showPassword ? 'text' : 'password'}
                                fullWidth
                                variant="standard"
                                className={styles.field}
                                onChange={(e) => { setPasswordInput(e.target.value) }}
                                error={canValidate && !passwordInput}
                                InputProps={{
                                    endAdornment: (
                                        <InputAdornment position="end">
                                            <IconButton
                                                aria-label="toggle password visibility"
                                                onClick={handleClickShowPassword}
                                                edge="end"
                                            >
                                                {showPassword ? <VisibilityIcon /> : <VisibilityOffIcon />}
                                            </IconButton>
                                        </InputAdornment>
                                    ),
                                }}
                            />
                        </Grid>
                    )}
                    <Grid item xs={12} sm={6} display="flex" alignItems="flex-end">
                        <Select
                            fullWidth
                            variant="standard"
                            label="Role"
                            value={(userData?.role ?? '') as TUserRole}
                            onChange={(event: SelectChangeEvent<unknown>) => {
                                handleInputChange('role', event.target.value)
                            }}
                            error={canValidate && !userData?.role}
                            options={userRoles.map(role => ({ label: role, value: role }))}
                        />
                    </Grid>
                    <Grid item xs={12} sm={12}>
                        <TextField
                            label="Bio"
                            value={userData?.bio || ''}
                            fullWidth
                            variant="standard"
                            multiline
                            className={styles.field}
                            onChange={(e) => { handleInputChange('bio', e.target.value) }}
                        />
                    </Grid>
                    {!addressJson && (
                        <Grid item xs={12} sm={12}>
                            <TextField label="Address"
                                value={addressString || ''}
                                fullWidth
                                variant="standard"
                                className={styles.field}
                                onChange={(e) => { handleInputChange('address', e.target.value) }}
                            />
                        </Grid>
                    )}
                    {addressJson && (
                        Object.entries<any>(addressJson).map(([fieldKey, value]) => {
                            return (
                                <Grid item xs={12} sm={6} key={fieldKey}>
                                    <TextField label={fieldKey}
                                        value={value || ''}
                                        fullWidth
                                        variant="standard"
                                        className={styles.field}
                                        onChange={(e) => {
                                            const newVal = e.target.value;
                                            handleInputChange('address', JSON.stringify({
                                                ...addressJson,
                                                [fieldKey]: newVal,
                                            }))
                                        }}
                                    />
                                </Grid>
                            )
                        }))}
                    <Grid item xs={12} sm={6}>
                        <TextField
                            label="Phone"
                            value={userData?.phone || ''}
                            fullWidth
                            variant="standard"
                            className={styles.field}
                            onChange={(e) => { handleInputChange('phone', e.target.value) }}
                        />
                    </Grid>
                    <Grid item xs={12} sm={12}>
                        {userData && (
                            <RenderCustomFields
                                entityType={EDBEntity.User}
                                entityData={userData}
                                refetchMeta={refetchMeta}
                            />
                        )}
                    </Grid>
                </Grid>
            </div>
        </div>
    );
}
Example #29
Source File: search.tsx    From Cromwell with MIT License 4 votes vote down vote up
SearchPage: TPageWithLayout<SearchPageProps> = (props) => {
    const filterInput = useRef<TPostFilter>({});
    const listId = 'Blog_list_01';
    const publishSort = useRef<"ASC" | "DESC">('DESC');
    const forceUpdate = useForceUpdate();
    const titleSearchId = "post-filter-search";

    const updateList = () => {
        const list = getBlockInstance<TCList>(listId)?.getContentInstance();
        list?.clearState();
        list?.init();
        list?.updateData();
    }

    const handleChangeTags = (event: any, newValue?: (TTag | undefined | string)[]) => {
        filterInput.current.tagIds = newValue?.map(tag => (tag as TTag)?.id);
        forceUpdate();
        updateList();
    }

    const handleGetPosts = (params: TPagedParams<TPost>) => {
        params.orderBy = 'publishDate';
        params.order = publishSort.current;
        return handleGetFilteredPosts(params, filterInput.current);
    }

    const handleChangeSort = (event: SelectChangeEvent<unknown>) => {
        if (event.target.value === 'Newest') publishSort.current = 'DESC';
        if (event.target.value === 'Oldest') publishSort.current = 'ASC';
        updateList();
    }

    const handleTagClick = (tag?: TTag) => {
        if (!tag) return;
        if (filterInput.current.tagIds?.length === 1 &&
            filterInput.current.tagIds[0] === tag.id) return;
        handleChangeTags(null, [tag]);
        forceUpdate();
    }

    const handleTitleInput = debounce(400, () => {
        filterInput.current.titleSearch = (document.getElementById(titleSearchId) as HTMLInputElement)?.value ?? undefined;
        updateList();
    });

    return (
        <CContainer className={commonStyles.content} id="search_01">
            <CContainer className={styles.filter} id="search_02">
                <div className={styles.filterLeft}>
                    <TextField
                        className={styles.filterItem}
                        placeholder="Search by title"
                        id={titleSearchId}
                        variant="standard"
                        onChange={handleTitleInput}
                    />
                    <Autocomplete
                        multiple
                        freeSolo
                        value={filterInput.current.tagIds?.map(id => props.tags?.find(tag => tag.id === id)) ?? []}
                        className={styles.filterItem}
                        options={props.tags ?? []}
                        getOptionLabel={(option: any) => option?.name ?? ''}
                        style={{ width: 300 }}
                        onChange={handleChangeTags}
                        renderInput={(params) => (
                            <TextField
                                {...params}
                                variant="standard"
                                placeholder="Tags"
                            />
                        )}
                    />
                </div>
                <FormControl className={styles.filterItem}>
                    <InputLabel className={styles.sortLabel}>Sort</InputLabel>
                    <Select
                        style={{ width: '100px' }}
                        onChange={handleChangeSort}
                        variant="standard"
                        defaultValue='Newest'
                    >
                        {['Newest', 'Oldest'].map(sort => (
                            <MenuItem value={sort} key={sort}>{sort}</MenuItem>
                        ))}
                    </Select>
                </FormControl>
            </CContainer>
            <CContainer style={{ marginBottom: '20px' }} id="search_03">
                <CList<TPost>
                    id={listId}
                    ListItem={(props) => (
                        <div className={styles.postWrapper}>
                            <PostCard onTagClick={handleTagClick} data={props.data} key={props.data?.id} />
                        </div>
                    )}
                    usePagination
                    useShowMoreButton
                    useQueryPagination
                    disableCaching
                    pageSize={20}
                    scrollContainerSelector={`.${layoutStyles.Layout}`}
                    firstBatch={props.posts}
                    loader={handleGetPosts}
                    cssClasses={{
                        page: styles.postList
                    }}
                    elements={{
                        pagination: Pagination
                    }}
                />
            </CContainer>
        </CContainer>
    );
}