in client/web/src/settings/ApplicationComponent.js [33:281]
export function ApplicationComponent(props) {
const dispatch = useDispatch();
const {
appConfig,
dbOptions,
hasTenants,
loading,
error,
message,
osOptions,
updateConfiguration,
} = props;
console.log('App Config:', appConfig);
const LINUX = 'LINUX';
const WINDOWS = 'WINDOWS';
const FSX = 'FSX';
const EFS = 'EFS';
const os = !!appConfig.operatingSystem
? appConfig.operatingSystem === LINUX
? LINUX
: WINDOWS
: '';
const db = !!appConfig.database
? {
...appConfig.database,
//This is frail, but try to see if the incoming password is base64d
//If so, assume it's encrypted
//Also store a copy in the encryptedPassword field
hasEncryptedPassword: !!appConfig.database.password.match(/^[A-Za-z0-9=+/\s ]+$/),
encryptedPassword: appConfig.database.password,
}
: {
engine: '',
family: '',
version: '',
instance: '',
username: '',
password: '',
hasEncryptedPassword: false,
encryptedPassword: '',
database: '',
bootstrapFilename: '',
};
const getParts = (dateTime) => {
const parts = dateTime.split(':');
const day = parts[0];
const times = parts.slice(1);
const timeStr = times.join(':');
return [day, timeStr];
};
const updateConfig = (values) => {
updateConfiguration(values);
window.scrollTo(0, 0);
};
const getFsx = (fsx) => {
if (!fsx) {
return {
storageGb: 32,
throughputMbs: 8,
backupRetentionDays: 7,
dailyBackupTime: '01:00',
weeklyMaintenanceTime: '07:01:00',
weeklyMaintenanceDay: '1',
windowsMountDrive: 'G:',
};
}
const [day, time] = getParts(fsx.weeklyMaintenanceTime);
return {
...fsx,
weeklyMaintenanceTime: time,
weeklyMaintenanceDay: day,
};
};
const filesystem = {
...appConfig.filesystem,
mountPoint: appConfig.filesystem?.mountPoint || '',
// Start off with FSX if Windows and EFS if Linux
fileSystemType: appConfig.filesystem?.fileSystemType || (os !== LINUX ? FSX : EFS),
efs: appConfig.filesystem?.efs || {
lifecycle: '0',
encryptAtRest: '',
},
fsx: getFsx(appConfig.filesystem?.fsx),
};
const initialValues = {
operatingSystem: os,
windowsVersion: os !== LINUX ? appConfig.operatingSystem : '',
name: appConfig.name || '',
domainName: appConfig.domainName || '',
sslCertArn: appConfig.sslCertArn || '',
computeSize: appConfig.computeSize || '',
containerPort: appConfig.containerPort || 80,
minCount: appConfig.minCount || 1,
maxCount: appConfig.maxCount || 1,
healthCheckURL: appConfig.healthCheckURL || '/index.html',
database: {
...db,
password: db.hasEncryptedPassword ? db.password.substring(0, 8) : db.password,
},
filesystem: filesystem,
billing: appConfig.billing || {
apiKey: '',
},
provisionDb: !!appConfig.database,
provisionFS: !!appConfig.filesystem,
provisionBilling: !!appConfig.billing,
};
const validationSpecs = Yup.object({
operatingSystem: Yup.string().required('Container OS is a required field'),
name: Yup.string().required('Name is a required field.'),
computeSize: Yup.string().required('Compute size is a required field.'),
database: Yup.object().when('provisionDb', {
is: true,
then: Yup.object({
engine: Yup.string().required('Engine is required'),
version: Yup.string().required('Version is required'),
instance: Yup.string().required('Instance is required'),
username: Yup.string()
.matches('^[a-zA-Z]+[a-zA-Z0-9_$]*$', 'Username is not valid')
.required('Username is required'),
password: Yup.string()
.matches('^[a-zA-Z0-9/@"\' ]{8,}$', 'Password is not valid')
.required('Password is required'),
database: Yup.string(),
}),
otherwise: Yup.object(),
}),
filesystem: Yup.object().when('provisionFS', {
is: true,
then: Yup.object({
mountPoint: Yup.string().when('fileSystemType', {
is: EFS,
then: Yup.string()
.matches(/^(\/[a-zA-Z._-]+)*$/, 'Invalid path. Ex: /mnt')
.max(100, "The full path can't exceed 100 characters in length")
.test(
'subdirectories',
'The path can only include up to four subdirectories',
(val) => (val?.match(/\//g) || []).length <= 4
)
.required(),
otherwise: Yup.string()
.matches(
/^[a-zA-Z]:\\(((?![<>:"/\\|?*]).)+((?<![ .])\\)?)*$/,
'Invalid path. Ex: C:\\data'
)
.required(),
}),
fsx: Yup.object().when('fileSystemType', {
is: FSX,
then: Yup.object({
storageGb: Yup.number()
.required()
.min(32, 'Storage minimum is 32 GB')
.max(1048, 'Storage maximum is 1048 GB'),
throughputMbs: Yup.number()
.required()
.min(8, 'Throughput minimum is 8 MB/s')
.max(2048, 'Throughput maximum is 2048 MB/s'),
backupRetentionDays: Yup.number()
.required()
.min(7, 'Minimum retention time is 7 days')
.max(35, 'Maximum retention time is 35 days'),
dailyBackupTime: Yup.string().required('Daily backup time is required'),
weeklyMaintenanceTime: Yup.string().required('Weekly maintenance time is required'),
windowsMountDrive: Yup.string().required('Windows mount drive is required'),
}),
otherwise: Yup.object().nullable(),
}),
efs: Yup.object().when('fileSystemType', {
is: EFS,
then: Yup.object({
encryptAtRest: Yup.bool(),
lifecycle: Yup.number().required('Lifecycle is required'),
filesystemLifecycle: Yup.string(),
}),
otherwise: Yup.object().nullable(),
}),
}),
otherwise: Yup.object(),
}),
containerPort: Yup.number()
.integer('Container port must be an integer value.')
.required('Container port is a required field.'),
minCount: Yup.number()
.required('Minimum count is a required field.')
.integer('Minimum count must be an integer value')
.min(1, 'Minimum count must be at least ${min}'),
maxCount: Yup.number()
.required('Maximum count is a required field.')
.integer('Maximum count must be an integer value')
.max(10, 'Maximum count can be no larger than ${max}')
.test('match', 'Maximum count cannot be smaller than minimum count', function (maxCount) {
return maxCount >= this.parent.minCount;
}),
windowsVersion: Yup.string().when('operatingSystem', {
is: (containerOs) => containerOs && containerOs === WINDOWS,
then: Yup.string().required('Windows version is a required field'),
otherwise: Yup.string().nullable(),
}),
healthCheckURL: Yup.string()
.required('Health Check URL is a required field')
.matches(/^\//, 'Health Check must start with forward slash (/)'),
provisionDb: Yup.boolean(),
provisionFS: Yup.boolean(),
provisionBilling: Yup.boolean(),
});
const onFileSelected = (formik, file) => {
formik.setFieldValue('database.bootstrapFilename', file.name);
props.onFileSelected(file);
};
const dismissError = () => {
dispatch(dismissConfigError());
};
const dismissMessage = () => {
dispatch(dismissConfigMessage());
};
const isSubmitting = () => {
return loading !== 'idle';
};
return (
<LoadingOverlay active={isSubmitting()} spinner text="Loading configuration...">
<div className="animated fadeIn">
{hasTenants && (
<Alert color="primary">
<span>
<i className="fa fa-info-circle" /> Note: some settings cannot be modified once you
have deployed tenants.
</span>
</Alert>
)}
{/* {loading !== "idle" && <div>Loading...</div>} */}