@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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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,
<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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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>
);
}