Added support of AWS provider for BigAnimal cloud deployment. #5569

This commit is contained in:
Khushboo Vashi
2023-01-09 12:32:26 +05:30
committed by GitHub
parent 3515e74204
commit 9ff015e73c
21 changed files with 1282 additions and 825 deletions

View File

@@ -50,7 +50,8 @@ class BigAnimalModule(PgAdminModule):
'biganimal.db_versions',
'biganimal.instance_types',
'biganimal.volume_types',
'biganimal.volume_properties']
'biganimal.volume_properties',
'biganimal.providers']
blueprint = BigAnimalModule(MODULE_NAME, __name__,
@@ -82,17 +83,27 @@ def verification():
return make_json_response(data=verification_uri)
@blueprint.route('/regions/',
@blueprint.route('/regions/<provider_id>',
methods=['GET'], endpoint='regions')
@login_required
def biganimal_regions():
def biganimal_regions(provider_id):
"""Get Regions."""
biganimal_obj = pickle.loads(session['biganimal']['provider_obj'])
status, regions = biganimal_obj.get_regions()
status, regions = biganimal_obj.get_regions(provider_id)
session['biganimal']['provider_obj'] = pickle.dumps(biganimal_obj, -1)
return make_json_response(data=regions)
@blueprint.route('/providers/',
methods=['GET'], endpoint='providers')
@login_required
def biganimal_providers():
"""Get Providers."""
biganimal_obj = pickle.loads(session['biganimal']['provider_obj'])
status, providers = biganimal_obj.get_providers()
return make_json_response(data=providers)
@blueprint.route('/db_types/',
methods=['GET'], endpoint='db_types')
@login_required
@@ -103,50 +114,52 @@ def biganimal_db_types():
return make_json_response(data=pg_types)
@blueprint.route('/db_versions/<db_type>',
@blueprint.route('/db_versions/<cluster_type>/<pg_type>',
methods=['GET'], endpoint='db_versions')
@login_required
def biganimal_db_versions(db_type):
def biganimal_db_versions(cluster_type, pg_type):
"""Get Database Version."""
biganimal_obj = pickle.loads(session['biganimal']['provider_obj'])
pg_versions = biganimal_obj.get_postgres_versions(db_type)
pg_versions = biganimal_obj.get_postgres_versions(cluster_type, pg_type)
return make_json_response(data=pg_versions)
@blueprint.route('/instance_types/<region_id>',
@blueprint.route('/instance_types/<region_id>/<provider_id>',
methods=['GET'], endpoint='instance_types')
@login_required
def biganimal_instance_types(region_id):
def biganimal_instance_types(region_id, provider_id):
"""Get Instance Types."""
if not region_id:
if not region_id or not provider_id:
return make_json_response(data=[])
biganimal_obj = pickle.loads(session['biganimal']['provider_obj'])
biganimal_instances = biganimal_obj.get_instance_types(region_id)
biganimal_instances = biganimal_obj.get_instance_types(region_id,
provider_id)
return make_json_response(data=biganimal_instances)
@blueprint.route('/volume_types/<region_id>',
@blueprint.route('/volume_types/<region_id>/<provider_id>',
methods=['GET'], endpoint='volume_types')
@login_required
def biganimal_volume_types(region_id):
def biganimal_volume_types(region_id, provider_id):
"""Get Volume Types."""
if not region_id:
if not region_id or not provider_id:
return make_json_response(data=[])
biganimal_obj = pickle.loads(session['biganimal']['provider_obj'])
biganimal_volumes = biganimal_obj.get_volume_types(region_id)
biganimal_volumes = biganimal_obj.get_volume_types(region_id, provider_id)
return make_json_response(data=biganimal_volumes)
@blueprint.route('/volume_properties/<region_id>/<volume_type>',
@blueprint.route('/volume_properties/<region_id>/<provider_id>/<volume_type>',
methods=['GET'], endpoint='volume_properties')
@login_required
def biganimal_volume_properties(region_id, volume_type):
def biganimal_volume_properties(region_id, provider_id, volume_type):
"""Get Volume Properties."""
if not region_id:
if not region_id or not provider_id:
return make_json_response(data=[])
biganimal_obj = pickle.loads(session['biganimal']['provider_obj'])
biganimal_volume_properties = biganimal_obj.get_volume_properties(
region_id,
provider_id,
volume_type)
return make_json_response(data=biganimal_volume_properties)
@@ -261,19 +274,42 @@ class BigAnimalProvider():
# so all the existing clusters moved to the default Project.
# For now, we can get the Proj Id by replacing 'org' to 'prj'
# in organization ID: org_1234 -> prj_1234
proj_Id = content['data']['organizationId'].replace('org',
proj_id = content['data']['organizationId'].replace('org',
'prj')
for permission in content['data']['scopedPermissions']:
if proj_Id == permission['scope'] and\
if proj_id == permission['scope'] and\
'create:clusters' in permission['permissions']:
return True
return False
def get_regions(self):
def get_providers(self):
"""Get cloud providers"""
_url = '{0}/cloud-providers'.format(
self.BASE_URL)
providers = []
resp = requests.get(_url, headers=self._get_headers())
if resp.status_code == 200 and resp.content:
provider_resp = json.loads(resp.content)
for value in provider_resp['data']:
providers.append({
'label': value['cloudProviderName'],
'value': value['cloudProviderId'],
'connected': value['connected']
})
return True, providers
elif resp.content:
provider_resp = json.loads(resp.content)
return False, provider_resp['error']['message']
else:
return False, gettext('Error retrieving providers.')
def get_regions(self, provider_id):
"""Get regions"""
_url = "{0}/{1}".format(
if not provider_id:
return False, gettext('Provider not provided.')
_url = '{0}/cloud-providers/{1}/regions'.format(
self.BASE_URL,
'cloud-providers/azure/regions')
provider_id)
regions = []
resp = requests.get(_url, headers=self._get_headers())
if resp.status_code == 200 and resp.content:
@@ -309,12 +345,15 @@ class BigAnimalProvider():
})
return pg_types
def get_postgres_versions(self, db_type):
def get_postgres_versions(self, cluster_type, pg_type):
"""Get Postgres Versions."""
_url = "{0}/pg-versions?pgTypeIds={1}".format(
self.BASE_URL,
db_type
)
if not cluster_type or not pg_type:
return []
_url = "{0}/pg-versions?clusterArchitectureIds={1}" \
"&pgTypeIds={2}".format(self.BASE_URL,
cluster_type,
pg_type)
pg_versions = []
resp = requests.get(_url, headers=self._get_headers())
if resp.status_code == 200 and resp.content:
@@ -326,48 +365,55 @@ class BigAnimalProvider():
})
return pg_versions
def get_instance_types(self, region_id):
def get_instance_types(self, region_id, provider_id):
"""GEt Instance Types."""
if region_id not in self.regions:
if region_id not in self.regions or not provider_id:
return []
_url = "{0}/{1}".format(
self.BASE_URL,
'cloud-providers/azure/regions/'
'{0}/instance-types'.format(region_id))
_url = '{0}/cloud-providers/{1}/regions/{2}/instance-types?' \
'sort=instanceTypeName'.format(self.BASE_URL,
provider_id,
region_id)
resp = requests.get(_url, headers=self._get_headers())
if resp.status_code == 200 and resp.content:
pg_types = json.loads(resp.content)
return pg_types['data']
_sorted_data = sorted(pg_types['data'], key=lambda x: int(x['cpu'])
)
return _sorted_data
return []
def get_volume_types(self, region_id):
def get_volume_types(self, region_id, provider_id):
"""Get Volume Types."""
if region_id not in self.regions:
return []
_url = "{0}/{1}".format(
_url = '{0}/cloud-providers/{1}/regions/{2}/volume-types'.format(
self.BASE_URL,
'cloud-providers/azure/regions/{0}/volume-types'.format(region_id))
provider_id,
region_id)
volume_types = []
resp = requests.get(_url, headers=self._get_headers())
if resp.status_code == 200 and resp.content:
volume_resp = json.loads(resp.content)
for value in volume_resp['data']:
volume_types.append({
'label': value['volumeTypeName'],
'value': value['volumeTypeId']
})
if value['enabledInRegion']:
volume_types.append({
'label': value['volumeTypeName'],
'value': value['volumeTypeId'],
'supportedInstanceFamilyNames': value[
'supportedInstanceFamilyNames']
})
return volume_types
def get_volume_properties(self, region_id, volume_type):
def get_volume_properties(self, region_id, provider_id, volume_type):
"""Get Volume Properties."""
if region_id not in self.regions:
return []
_url = "{0}/{1}".format(
self.BASE_URL,
'cloud-providers/azure/regions/{0}/volume-types'
'/{1}/volume-properties'.format(region_id, volume_type))
_url = '{0}/cloud-providers/{1}/regions/{2}/volume-types/{3}/' \
'volume-properties'.format(self.BASE_URL,
provider_id,
region_id,
volume_type)
volume_properties = []
resp = requests.get(_url, headers=self._get_headers())
if resp.status_code == 200 and resp.content:
@@ -394,13 +440,12 @@ def deploy_on_biganimal(data):
_private_network = '1' if str(data['instance_details']['cloud_type']
) == 'private' else '0'
_instance_size = data['instance_details']['instance_size'].split('||')[1]
cluster_arch = SINGLE_CLUSTER_ARCH
nodes = 1
if data['db_details']['high_availability']:
cluster_arch = HA_CLUSTER_ARCH
nodes = int(data['db_details']['replicas']) + nodes
if data['cluster_details']['cluster_type'] == HA_CLUSTER_ARCH:
nodes = int(data['cluster_details']['replicas']) + nodes
elif data['cluster_details']['cluster_type'] == EHA_CLUSTER_ARCH:
nodes = 5
args = [_cmd_script,
data['cloud'],
@@ -416,15 +461,25 @@ def deploy_on_biganimal(data):
'--volume-type',
str(data['instance_details']['volume_type']),
'--volume-properties',
str(data['instance_details']['volume_properties']),
str(data['instance_details'].get('volume_properties',
data['instance_details'][
'volume_type'])),
'--volume-size',
str(data['instance_details'].get('volume_size', None)),
'--volume-IOPS',
str(data['instance_details'].get('volume_IOPS', None)),
'--instance-type',
str(_instance_size),
'--private-network',
_private_network,
'--cluster-arch',
cluster_arch,
data['cluster_details']['cluster_type'],
'--nodes',
str(nodes)
str(nodes),
'--replicas',
str(data['cluster_details']['replicas']),
'--cloud-provider',
str(data['cluster_details']['provider']),
]
if 'biganimal_public_ip' in data['instance_details']:

View File

@@ -58,6 +58,7 @@ blueprint = RDSModule(MODULE_NAME, __name__,
@login_required
def verify_credentials():
"""Verify Credentials."""
msg = ''
data = json.loads(request.data, encoding='utf-8')
session_token = data['secret']['session_token'] if\

View File

@@ -22,11 +22,13 @@ import pgAdmin from 'sources/pgadmin';
import {ToggleButtons, FinalSummary} from './cloud_components';
import { PrimaryButton } from '../../../../static/js/components/Buttons';
import {AwsCredentials, AwsInstanceDetails, AwsDatabaseDetails, validateCloudStep1, validateCloudStep2, validateCloudStep3} from './aws';
import {BigAnimalInstance, BigAnimalDatabase, validateBigAnimal,validateBigAnimalStep2, validateBigAnimalStep3} from './biganimal';
import {BigAnimalInstance, BigAnimalDatabase, BigAnimalClusterType, getProviderOptions, validateBigAnimal, validateBigAnimalStep2, validateBigAnimalStep3, validateBigAnimalStep4} from './biganimal';
import { isEmptyString } from 'sources/validators';
import { AWSIcon, BigAnimalIcon, AzureIcon } from '../../../../static/js/components/ExternalIcon';
import {AzureCredentials, AzureInstanceDetails, AzureDatabaseDetails, checkClusternameAvailbility, validateAzureStep2, validateAzureStep3} from './azure';
import EventBus from '../../../../static/js/helpers/EventBus';
import { CLOUD_PROVIDERS } from './cloud_constants';
const useStyles = makeStyles(() =>
({
@@ -67,7 +69,7 @@ export default function CloudWizard({ nodeInfo, nodeData, onClose, cloudPanel})
const eventBus = React.useRef(new EventBus());
let steps = [gettext('Cloud Provider'), gettext('Credentials'),
let steps = [gettext('Cloud Provider'), gettext('Credentials'), gettext('Cluster Type'),
gettext('Instance Specification'), gettext('Database Details'), gettext('Review')];
const [currentStep, setCurrentStep] = React.useState('');
const [selectionVal, setCloudSelection] = React.useState('');
@@ -81,6 +83,8 @@ export default function CloudWizard({ nodeInfo, nodeData, onClose, cloudPanel})
const [verificationIntiated, setVerificationIntiated] = React.useState(false);
const [bigAnimalInstanceData, setBigAnimalInstanceData] = React.useState({});
const [bigAnimalDatabaseData, setBigAnimalDatabaseData] = React.useState({});
const [bigAnimalClusterTypeData, setBigAnimalClusterTypeData] = React.useState({});
const [bigAnimalProviders, setBigAnimalProviders] = React.useState({});
const [azureCredData, setAzureCredData] = React.useState({});
const [azureInstanceData, setAzureInstanceData] = React.useState({});
@@ -124,7 +128,7 @@ export default function CloudWizard({ nodeInfo, nodeData, onClose, cloudPanel})
let _url = url_for('cloud.deploy_on_cloud'),
post_data = {};
if (cloudProvider == 'rds') {
if (cloudProvider == CLOUD_PROVIDERS.RDS) {
post_data = {
gid: nodeInfo.server_group._id,
cloud: cloudProvider,
@@ -132,7 +136,7 @@ export default function CloudWizard({ nodeInfo, nodeData, onClose, cloudPanel})
instance_details:cloudInstanceDetails,
db_details: cloudDBDetails
};
} else if(cloudProvider == 'azure'){
} else if(cloudProvider == CLOUD_PROVIDERS.AZURE){
post_data = {
gid: nodeInfo.server_group._id,
secret: azureCredData,
@@ -145,7 +149,8 @@ export default function CloudWizard({ nodeInfo, nodeData, onClose, cloudPanel})
post_data = {
gid: nodeInfo.server_group._id,
cloud: cloudProvider,
instance_details:bigAnimalInstanceData,
cluster_details: bigAnimalClusterTypeData,
instance_details: bigAnimalInstanceData,
db_details: bigAnimalDatabaseData
};
}
@@ -165,54 +170,61 @@ export default function CloudWizard({ nodeInfo, nodeData, onClose, cloudPanel})
setCallRDSAPI(currentStep);
let isError = (cloudProvider == '');
switch(cloudProvider) {
case 'rds':
case CLOUD_PROVIDERS.RDS:
switch (currentStep) {
case 0:
setCloudSelection('rds');
setCloudSelection(CLOUD_PROVIDERS.RDS);
break;
case 1:
isError = validateCloudStep1(cloudDBCred);
break;
case 2:
isError = validateCloudStep2(cloudInstanceDetails, hostIP);
break;
case 3:
isError = validateCloudStep2(cloudInstanceDetails, hostIP);
break;
case 4:
isError = validateCloudStep3(cloudDBDetails, nodeInfo);
break;
default:
break;
}
break;
case 'biganimal':
case CLOUD_PROVIDERS.BIGANIMAL:
switch (currentStep) {
case 0:
setCloudSelection('biganimal');
setCloudSelection(CLOUD_PROVIDERS.BIGANIMAL);
break;
case 1:
isError = !verificationIntiated;
break;
case 2:
isError = validateBigAnimalStep2(bigAnimalInstanceData);
isError = validateBigAnimalStep2(bigAnimalClusterTypeData);
break;
case 3:
isError = validateBigAnimalStep3(bigAnimalDatabaseData, nodeInfo);
isError = validateBigAnimalStep3(bigAnimalInstanceData);
break;
case 4:
isError = validateBigAnimalStep4(bigAnimalDatabaseData, nodeInfo);
break;
default:
break;
}
break;
case 'azure':
case CLOUD_PROVIDERS.AZURE:
switch (currentStep) {
case 0:
setCloudSelection('azure');
setCloudSelection(CLOUD_PROVIDERS.AZURE);
break;
case 1:
isError = !verificationIntiated;
break;
case 2:
isError = validateAzureStep2(azureInstanceData);
break;
case 3:
isError = validateAzureStep2(azureInstanceData);
break;
case 4:
isError = validateAzureStep3(azureDatabaseData, nodeInfo);
break;
default:
@@ -223,9 +235,18 @@ export default function CloudWizard({ nodeInfo, nodeData, onClose, cloudPanel})
return isError;
};
const onBeforeBack = (activeStep) => {
return new Promise((resolve)=>{
if(activeStep == 3 && (cloudProvider == CLOUD_PROVIDERS.RDS || cloudProvider == CLOUD_PROVIDERS.AZURE)) {
resolve(true);
}
resolve();
});
};
const onBeforeNext = (activeStep) => {
return new Promise((resolve, reject)=>{
if(activeStep == 1 && cloudProvider == 'rds') {
if(activeStep == 1 && cloudProvider == CLOUD_PROVIDERS.RDS) {
setErrMsg([MESSAGE_TYPE.INFO, gettext('Validating credentials...')]);
let _url = url_for('rds.verify_credentials');
const post_data = {
@@ -239,14 +260,18 @@ export default function CloudWizard({ nodeInfo, nodeData, onClose, cloudPanel})
reject();
} else {
setErrMsg(['', '']);
resolve();
if (activeStep == 1) {
resolve(true);
} else {
resolve(false);
}
}
})
.catch(() => {
setErrMsg([MESSAGE_TYPE.ERROR, gettext('Error while checking cloud credentials')]);
reject();
});
} else if(activeStep == 0 && cloudProvider == 'biganimal') {
} else if(activeStep == 0 && cloudProvider == CLOUD_PROVIDERS.BIGANIMAL) {
if (!isEmptyString(verificationURI)) { resolve(); return; }
setErrMsg([MESSAGE_TYPE.INFO, gettext('Getting EDB BigAnimal verification URL...')]);
validateBigAnimal()
@@ -260,20 +285,37 @@ export default function CloudWizard({ nodeInfo, nodeData, onClose, cloudPanel})
setErrMsg([MESSAGE_TYPE.ERROR, gettext(error)]);
reject();
});
} else if(activeStep == 2 && cloudProvider == 'azure'){
setErrMsg([MESSAGE_TYPE.INFO, gettext('Checking cluster name availability...')]);
checkClusternameAvailbility(azureInstanceData.name)
} else if (activeStep == 1 && cloudProvider == CLOUD_PROVIDERS.BIGANIMAL ) {
getProviderOptions()
.then((res)=>{
if (res.data && res.data.success == 0 ) {
setErrMsg([MESSAGE_TYPE.ERROR, gettext('Specified cluster name is already used.')]);
}else{
setErrMsg(['', '']);
}
setBigAnimalProviders(res);
setErrMsg(['', '']);
resolve();
}).catch((error)=>{
setErrMsg([MESSAGE_TYPE.ERROR, gettext(error)]);
reject();
});
} else if (cloudProvider == CLOUD_PROVIDERS.AZURE) {
if (activeStep == 1) {
// Skip the current step
resolve(true);
} else if (activeStep == 2) {
setErrMsg([MESSAGE_TYPE.INFO, gettext('Checking cluster name availability...')]);
checkClusternameAvailbility(azureInstanceData.name)
.then((res)=>{
if (res.data && res.data.success == 0 ) {
setErrMsg([MESSAGE_TYPE.ERROR, gettext('Specified cluster name is already used.')]);
}else{
setErrMsg(['', '']);
}
resolve();
}).catch((error)=>{
setErrMsg([MESSAGE_TYPE.ERROR, gettext(error)]);
reject();
});
} else {
resolve();
}
}
else {
setErrMsg(['', '']);
@@ -333,9 +375,9 @@ export default function CloudWizard({ nodeInfo, nodeData, onClose, cloudPanel})
setErrMsg([]);
});
let cloud_providers = [{label: gettext('Amazon RDS'), value: 'rds', icon: <AWSIcon className={classes.icon} />},
{label: gettext('EDB BigAnimal'), value: 'biganimal', icon: <BigAnimalIcon className={classes.icon} />},
{'label': gettext('Azure PostgreSQL'), value: 'azure', icon: <AzureIcon className={classes.icon} /> }];
let cloud_providers = [{label: gettext('Amazon RDS'), value: CLOUD_PROVIDERS.RDS, icon: <AWSIcon className={classes.icon} />},
{label: gettext('EDB BigAnimal'), value: CLOUD_PROVIDERS.BIGANIMAL, icon: <BigAnimalIcon className={classes.icon} />},
{'label': gettext('Azure PostgreSQL'), value: CLOUD_PROVIDERS.AZURE, icon: <AzureIcon className={classes.icon} /> }];
return (
<CloudWizardEventsContext.Provider value={eventBus.current}>
@@ -347,7 +389,8 @@ export default function CloudWizard({ nodeInfo, nodeData, onClose, cloudPanel})
onStepChange={wizardStepChange}
onSave={onSave}
onHelp={onDialogHelp}
beforeNext={onBeforeNext}>
beforeNext={onBeforeNext}
beforeBack={onBeforeBack}>
<WizardStep stepId={0}>
<Box className={classes.messageBox}>
<Box className={classes.messagePadding}>{gettext('Select a cloud provider.')}</Box>
@@ -361,39 +404,51 @@ export default function CloudWizard({ nodeInfo, nodeData, onClose, cloudPanel})
</WizardStep>
<WizardStep stepId={1} >
<Box className={classes.buttonMarginEDB}>
{cloudProvider == 'biganimal' && <Box className={classes.messageBox}>
{cloudProvider == CLOUD_PROVIDERS.BIGANIMAL && <Box className={classes.messageBox}>
<Box>{gettext('The verification code to authenticate the pgAdmin to EDB BigAnimal is: ')} <strong>{verificationCode}</strong>
<br/>{gettext('By clicking the below button, you will be redirected to the EDB BigAnimal authentication page in a new tab.')}
</Box>
</Box>}
{cloudProvider == 'biganimal' && <PrimaryButton onClick={authenticateBigAnimal} disabled={verificationIntiated ? true: false}>
{cloudProvider == CLOUD_PROVIDERS.BIGANIMAL && <PrimaryButton onClick={authenticateBigAnimal} disabled={verificationIntiated ? true: false}>
{gettext('Click here to authenticate yourself to EDB BigAnimal')}
</PrimaryButton>}
{cloudProvider == 'biganimal' && <Box className={classes.messageBox}>
{cloudProvider == CLOUD_PROVIDERS.BIGANIMAL && <Box className={classes.messageBox}>
<Box ></Box>
</Box>}
</Box>
{cloudProvider == 'rds' && <AwsCredentials cloudProvider={cloudProvider} nodeInfo={nodeInfo} nodeData={nodeData} setCloudDBCred={setCloudDBCred}/>}
{cloudProvider == CLOUD_PROVIDERS.RDS && <AwsCredentials cloudProvider={cloudProvider} nodeInfo={nodeInfo} nodeData={nodeData} setCloudDBCred={setCloudDBCred}/>}
<Box flexGrow={1}>
{cloudProvider == 'azure' && <AzureCredentials cloudProvider={cloudProvider} nodeInfo={nodeInfo} nodeData={nodeData} setAzureCredData={setAzureCredData}/>}
{cloudProvider == CLOUD_PROVIDERS.AZURE && <AzureCredentials cloudProvider={cloudProvider} nodeInfo={nodeInfo} nodeData={nodeData} setAzureCredData={setAzureCredData}/>}
</Box>
<FormFooterMessage type={errMsg[0]} message={errMsg[1]} onClose={onErrClose} />
</WizardStep>
<WizardStep stepId={2} >
{cloudProvider == 'rds' && callRDSAPI == 2 && <AwsInstanceDetails
{cloudProvider == CLOUD_PROVIDERS.BIGANIMAL && callRDSAPI == 2 && <BigAnimalClusterType
cloudProvider={cloudProvider}
nodeInfo={nodeInfo}
nodeData={nodeData}
bigAnimalProviders={bigAnimalProviders}
setBigAnimalClusterTypeData={setBigAnimalClusterTypeData}
hostIP={hostIP}
/> }
<FormFooterMessage type={errMsg[0]} message={errMsg[1]} onClose={onErrClose} />
</WizardStep>
<WizardStep stepId={3} >
{cloudProvider == CLOUD_PROVIDERS.RDS && callRDSAPI == 3 && <AwsInstanceDetails
cloudProvider={cloudProvider}
nodeInfo={nodeInfo}
nodeData={nodeData}
setCloudInstanceDetails={setCloudInstanceDetails}
hostIP={hostIP} /> }
{cloudProvider == 'biganimal' && callRDSAPI == 2 && <BigAnimalInstance
{cloudProvider == CLOUD_PROVIDERS.BIGANIMAL && callRDSAPI == 3 && <BigAnimalInstance
cloudProvider={cloudProvider}
nodeInfo={nodeInfo}
nodeData={nodeData}
setBigAnimalInstanceData={setBigAnimalInstanceData}
hostIP={hostIP}
bigAnimalClusterTypeData={bigAnimalClusterTypeData}
/> }
{cloudProvider == 'azure' && callRDSAPI == 2 && <AzureInstanceDetails
{cloudProvider == CLOUD_PROVIDERS.AZURE && callRDSAPI == 3 && <AzureInstanceDetails
cloudProvider={cloudProvider}
nodeInfo={nodeInfo}
nodeData={nodeData}
@@ -403,22 +458,23 @@ export default function CloudWizard({ nodeInfo, nodeData, onClose, cloudPanel})
/> }
<FormFooterMessage type={errMsg[0]} message={errMsg[1]} onClose={onErrClose} />
</WizardStep>
<WizardStep stepId={3} >
{cloudProvider == 'rds' && <AwsDatabaseDetails
<WizardStep stepId={4} >
{cloudProvider == CLOUD_PROVIDERS.RDS && <AwsDatabaseDetails
cloudProvider={cloudProvider}
nodeInfo={nodeInfo}
nodeData={nodeData}
setCloudDBDetails={setCloudDBDetails}
/>
}
{cloudProvider == 'biganimal' && callRDSAPI == 3 && <BigAnimalDatabase
{cloudProvider == CLOUD_PROVIDERS.BIGANIMAL && callRDSAPI == 4 && <BigAnimalDatabase
cloudProvider={cloudProvider}
nodeInfo={nodeInfo}
nodeData={nodeData}
setBigAnimalDatabaseData={setBigAnimalDatabaseData}
bigAnimalClusterTypeData={bigAnimalClusterTypeData}
/>
}
{cloudProvider == 'azure' && <AzureDatabaseDetails
{cloudProvider == CLOUD_PROVIDERS.AZURE && <AzureDatabaseDetails
cloudProvider={cloudProvider}
nodeInfo={nodeInfo}
nodeData={nodeData}
@@ -426,22 +482,23 @@ export default function CloudWizard({ nodeInfo, nodeData, onClose, cloudPanel})
/>
}
</WizardStep>
<WizardStep stepId={4} >
<WizardStep stepId={5} >
<Box className={classes.boxText}>{gettext('Please review the details before creating the cloud instance.')}</Box>
<Paper variant="outlined" elevation={0} className={classes.summaryContainer}>
{cloudProvider == 'rds' && callRDSAPI == 4 && <FinalSummary
{cloudProvider == CLOUD_PROVIDERS.RDS && callRDSAPI == 5 && <FinalSummary
cloudProvider={cloudProvider}
instanceData={cloudInstanceDetails}
databaseData={cloudDBDetails}
/>
}
{cloudProvider == 'biganimal' && callRDSAPI == 4 && <FinalSummary
{cloudProvider == CLOUD_PROVIDERS.BIGANIMAL && callRDSAPI == 5 && <FinalSummary
cloudProvider={cloudProvider}
instanceData={bigAnimalInstanceData}
databaseData={bigAnimalDatabaseData}
clusterTypeData={bigAnimalClusterTypeData}
/>
}
{cloudProvider == 'azure' && callRDSAPI == 4 && <FinalSummary
{cloudProvider == CLOUD_PROVIDERS.AZURE && callRDSAPI == 5 && <FinalSummary
cloudProvider={cloudProvider}
instanceData={azureInstanceData}
databaseData={azureDatabaseData}

View File

@@ -10,7 +10,7 @@
import React from 'react';
import pgAdmin from 'sources/pgadmin';
import { getNodeAjaxOptions, getNodeListById } from 'pgbrowser/node_ajax';
import {CloudInstanceDetailsSchema, CloudDBCredSchema, DatabaseSchema} from './cloud_db_details_schema.ui';
import {CloudInstanceDetailsSchema, CloudDBCredSchema, DatabaseSchema} from './aws_schema.ui';
import SchemaView from '../../../../static/js/SchemaView';
import url_for from 'sources/url_for';
import getApiInstance from '../../../../static/js/api_instance';

View File

@@ -0,0 +1,334 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import gettext from 'sources/gettext';
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
import { isEmptyString } from 'sources/validators';
class CloudInstanceDetailsSchema extends BaseUISchema {
constructor(fieldOptions = {}, initValues = {}) {
super({
oid: undefined,
name: '',
public_ip: initValues.hostIP,
high_availability: false,
...initValues
});
this.fieldOptions = {
...fieldOptions,
};
this.initValues = initValues;
}
get idAttribute() {
return 'oid';
}
get baseFields() {
return [
{
id: 'name', label: gettext('Instance name'), type: 'text',
mode: ['create'], noEmpty: true,
}, {
id: 'public_ip', label: gettext('Public IP range'), type: 'text',
mode: ['create'],
helpMessage: gettext('IP address range for allowed inbound traffic, for example: 127.0.0.1/32. Add multiple IP addresses/ranges separated with commas.'),
}, {
type: 'nested-fieldset', label: gettext('Version & Instance'),
mode: ['create'],
schema: new InstanceSchema(this.fieldOptions.version,
this.fieldOptions.instance_type,
this.fieldOptions.getInstances),
}, {
type: 'nested-fieldset', label: gettext('Storage'),
mode: ['create'],
schema: new StorageSchema(),
}, {
type: 'nested-fieldset', label: gettext('Availability'),
mode: ['create'],
schema: new HighAvailablity(),
},
];
}
}
class CloudDBCredSchema extends BaseUISchema {
constructor(fieldOptions = {}, initValues = {}) {
super({
oid: null,
region: '',
access_key: '',
secret_access_key: '',
session_token: '',
is_valid_cred: false,
...initValues
});
this.fieldOptions = {
...fieldOptions,
};
}
get idAttribute() {
return 'oid';
}
get baseFields() {
return [
{
id: 'region', label: gettext('Region'),
type: 'select',
options: this.fieldOptions.regions,
controlProps: { allowClear: false },
noEmpty: true,
helpMessage: gettext('The cloud instance will be deployed in the selected region.')
},{
id: 'access_key', label: gettext('AWS access key'), type: 'text',
mode: ['create'], noEmpty: true,
}, {
id: 'secret_access_key', label: gettext('AWS secret access key'), type: 'password',
mode: ['create'], noEmpty: true,
}, {
id: 'session_token', label: gettext('AWS session token'), type: 'multiline',
mode: ['create'], noEmpty: false,
helpMessage: gettext('Temporary AWS session required session token.')
}
];
}
}
class DatabaseSchema extends BaseUISchema {
constructor(fieldOptions = {}, initValues={}) {
super({
oid: undefined,
gid: undefined,
db_name: '',
db_username: '',
db_password: '',
db_confirm_password: '',
db_port: 5432,
...initValues,
});
this.fieldOptions = {
...fieldOptions,
};
}
validate(data, setErrMsg) {
if(!isEmptyString(data.db_password) && !isEmptyString(data.db_confirm_password)
&& data.db_password != data.db_confirm_password) {
setErrMsg('db_confirm_password', gettext('Passwords do not match.'));
return true;
}
if (!isEmptyString(data.db_confirm_password) && data.db_confirm_password.length < 8) {
setErrMsg('db_confirm_password', gettext('Password must be 8 characters or more.'));
return true;
}
if (data.db_confirm_password.includes('\'') || data.db_confirm_password.includes('"') ||
data.db_confirm_password.includes('@') || data.db_confirm_password.includes('/')) {
setErrMsg('db_confirm_password', gettext('Invalid passowrd.'));
return true;
}
return false;
}
get idAttribute() {
return 'oid';
}
get baseFields() {
return [{
id: 'gid', label: gettext('pgAdmin server group'), type: 'select',
options: this.fieldOptions.server_groups,
mode: ['create'],
controlProps: { allowClear: false },
noEmpty: true,
}, {
id: 'db_name', label: gettext('Database name'), type: 'text',
mode: ['create'], noEmpty: true,
}, {
id: 'db_username', label: gettext('Username'), type: 'text',
mode: ['create'], noEmpty: true,
}, {
id: 'db_password', label: gettext('Password'), type: 'password',
mode: ['create'], noEmpty: true,
helpMessage: gettext('At least 8 printable ASCII characters. Cannot contain any of the following: / \(slash\), \'\(single quote\), "\(double quote\) and @ \(at sign\).')
}, {
id: 'db_confirm_password', label: gettext('Confirm password'),
type: 'password',
mode: ['create'], noEmpty: true,
}, {
id: 'db_port', label: gettext('Port'), type: 'text',
mode: ['create'], noEmpty: true,
}];
}
}
export class InstanceSchema extends BaseUISchema {
constructor(versionOpts, instanceOpts, getInstances) {
super({
db_version: '',
db_instance_class: 'm',
instance_type: '',
reload_instances: true,
});
this.versionOpts = versionOpts;
this.instanceOpts = instanceOpts;
this.getInstances = getInstances;
this.instanceData = [];
}
get baseFields() {
return [{
id: 'db_version', label: gettext('Database version'),
type: 'select',
options: this.versionOpts,
controlProps: { allowClear: false },
deps: ['name'],
noEmpty: true,
},{
id: 'db_instance_class', label: gettext('Instance class'),
type: 'select',
options: [
{'label': gettext('Standard classes (includes m classes)'), value: 'm'},
{'label': gettext('Memory optimized classes (includes r & x classes)'), value: 'x'},
{'label': gettext('Burstable classes (includes t classes)'), value: 't'},
], noEmpty: true
},{
id: 'instance_type', label: gettext('Instance type'),
options: this.instanceOpts,
deps: ['db_version', 'db_instance_class'],
depChange: (state, source)=> {
if (source[0] == 'db_instance_class') {
return {reload_instances: false};
} else {
state.instanceData = [];
return {reload_instances: true};
}
},
type: (state) => {
return {
type: 'select',
options: ()=>this.getInstances(state.db_version,
state.reload_instances, state.instanceData),
optionsLoaded: (options) => { state.instanceData = options; },
optionsReloadBasis: state.db_version + (state.db_instance_class || 'm'),
controlProps: {
allowClear: false,
filter: (options) => {
if (options.length == 0) return;
let pattern = 'db.m';
let pattern_1 = 'db.m';
if (state.db_instance_class) {
pattern = 'db.' + state.db_instance_class;
pattern_1 = 'db.' + state.db_instance_class;
}
if (state.db_instance_class == 'x') {
pattern_1 = 'db.' + 'r';
}
return options.filter((option) => {
return (option.value.includes(pattern) || option.value.includes(pattern_1));
});
},
}
};
},
}];
}
}
export class StorageSchema extends BaseUISchema {
constructor() {
super({
storage_type: 'io1',
storage_size: 100,
storage_IOPS: 3000,
storage_msg: 'Minimum: 20 GiB. Maximum: 16,384 GiB.'
});
}
get baseFields() {
return [
{
id: 'storage_type', label: gettext('Storage type'), type: 'select',
mode: ['create'],
options: [
{'label': gettext('General Purpose SSD (gp2)'), 'value': 'gp2'},
{'label': gettext('Provisioned IOPS SSD (io1)'), 'value': 'io1'},
{'label': gettext('Magnetic'), 'value': 'standard'}
], noEmpty: true,
},{
id: 'storage_size', label: gettext('Allocated storage'), type: 'text',
mode: ['create'], noEmpty: true, deps: ['storage_type'],
depChange: (state, source)=> {
if (source[0] !== 'storage_size')
if(state.storage_type === 'io1') {
return {storage_size: 100};
} else if(state.storage_type === 'gp2') {
return {storage_size: 20};
} else {
return {storage_size: 5};
}
},
helpMessage: gettext('Size in GiB.')
}, {
id: 'storage_IOPS', label: gettext('Provisioned IOPS'), type: 'text',
mode: ['create'],
visible: (state) => {
return state.storage_type === 'io1';
} , deps: ['storage_type'],
depChange: (state, source) => {
if (source[0] !== 'storage_IOPS') {
if(state.storage_type === 'io1') {
return {storage_IOPS: 3000};
}
}
},
},
];
}
}
export class HighAvailablity extends BaseUISchema {
constructor() {
super({
high_availability: false
});
}
get baseFields() {
return [
{
id: 'high_availability',
label: gettext('High availability'),
type: 'switch',
mode: ['create'],
helpMessage: gettext(
'Creates a standby in a different Availability Zone (AZ) to provide data redundancy, eliminate I/O freezes, and minimize latency spikes during system backups.'
),
},
];
}
}
export {
CloudInstanceDetailsSchema,
CloudDBCredSchema,
DatabaseSchema
};

View File

@@ -10,59 +10,143 @@
import React from 'react';
import pgAdmin from 'sources/pgadmin';
import { getNodeAjaxOptions, getNodeListById } from 'pgbrowser/node_ajax';
import {BigAnimalClusterSchema, BigAnimalDatabaseSchema} from './cloud_db_details_schema.ui';
import {BigAnimalClusterSchema, BigAnimalDatabaseSchema, BigAnimalClusterTypeSchema} from './biganimal_schema.ui';
import SchemaView from '../../../../static/js/SchemaView';
import url_for from 'sources/url_for';
import getApiInstance from '../../../../static/js/api_instance';
import { isEmptyString } from 'sources/validators';
import PropTypes from 'prop-types';
import gettext from 'sources/gettext';
import { makeStyles } from '@material-ui/core/styles';
import { AWSIcon, MSAzureIcon } from '../../../../static/js/components/ExternalIcon';
const useStyles = makeStyles(() =>
({
providerHeight: {
height: '5em',
},
AwsIcon: {
width: '6rem',
}
}),
);
const axiosApi = getApiInstance();
export function getProviderOptions() {
return new Promise((resolve, reject) => {
axiosApi.get(url_for('biganimal.providers'))
.then((res) => {
if (res.data.data) {
let _options= [],
_options_label = {'azure': <MSAzureIcon key='1' />,
'aws': <AWSIcon style={{width: '6rem'}} key = '2'/>};
_.forEach(res.data.data, (val) => {
_options.push({
'label': _options_label[val['value']],
'value': val['value'],
'disabled': !val['connected']
});
});
resolve(_options);
}
})
.catch((error) => {
reject(gettext(`Error while getting the biganimal providers: ${error.response.data.errormsg}`));
});
});
}
// BigAnimal Cluster Type
export function BigAnimalClusterType(props) {
const [bigAnimalClusterType, setBigAnimalClusterType] = React.useState();
const classes = useStyles();
React.useMemo(() => {
const bigAnimalClusterTypeSchema = new BigAnimalClusterTypeSchema({
providers: ()=>getNodeAjaxOptions('biganimal_providers', pgAdmin.Browser.Nodes['server'], props.nodeInfo, props.nodeData, {
useCache:false,
cacheNode: 'server',
customGenerateUrl: ()=>{
return url_for('biganimal.providers');
}
}),
}, {
nodeInfo: props.nodeInfo,
nodeData: props.nodeData,
hostIP: props.hostIP,
classes: classes,
bigAnimalProviders: props.bigAnimalProviders,
});
setBigAnimalClusterType(bigAnimalClusterTypeSchema);
}, [props.cloudProvider]);
return <SchemaView
formType={'dialog'}
getInitData={() => { /*This is intentional (SonarQube)*/ }}
viewHelperProps={{ mode: 'create' }}
schema={bigAnimalClusterType}
showFooter={false}
isTabView={false}
onDataChange={(isChanged, changedData) => {
props.setBigAnimalClusterTypeData(changedData);
}}
/>;
}
BigAnimalClusterType.propTypes = {
nodeInfo: PropTypes.object,
nodeData: PropTypes.object,
cloudProvider: PropTypes.string,
setBigAnimalClusterTypeData: PropTypes.func,
hostIP: PropTypes.string,
bigAnimalProviders: PropTypes.object,
};
// BigAnimal Instance
export function BigAnimalInstance(props) {
const [bigAnimalInstance, setBigAnimalInstance] = React.useState();
React.useMemo(() => {
const bigAnimalSchema = new BigAnimalClusterSchema({
regions: ()=>getNodeAjaxOptions('biganimal_regions', pgAdmin.Browser.Nodes['server'], props.nodeInfo, props.nodeData, {
regions: (provider_id)=>getNodeAjaxOptions('biganimal_regions', pgAdmin.Browser.Nodes['server'], props.nodeInfo, props.nodeData, {
useCache:false,
cacheNode: 'server',
customGenerateUrl: ()=>{
return url_for('biganimal.regions');
return url_for('biganimal.regions', {'provider_id': provider_id || 0});
}
}),
instance_types: (region_id)=>{
instance_types: (region_id, provider_id)=>{
if (isEmptyString(region_id)) return [];
return getNodeAjaxOptions('biganimal_instance_types', pgAdmin.Browser.Nodes['server'],
props.nodeInfo, props.nodeData, {
useCache:false,
cacheNode: 'server',
customGenerateUrl: ()=>{
return url_for('biganimal.instance_types', {'region_id': region_id || 0});
return url_for('biganimal.instance_types', {'region_id': region_id || 0, 'provider_id': provider_id || 0});
}
});
},
volume_types: (region_id)=>{
volume_types: (region_id, provider_id)=>{
if (isEmptyString(region_id)) return [];
return getNodeAjaxOptions('biganimal_volume_types', pgAdmin.Browser.Nodes['server'],
props.nodeInfo, props.nodeData, {
useCache:false,
cacheNode: 'server',
customGenerateUrl: ()=>{
return url_for('biganimal.volume_types', {'region_id': region_id || 0});
return url_for('biganimal.volume_types', {'region_id': region_id || 0, 'provider_id': provider_id || 0});
}
});
},
volume_properties: (region_id, volume_type)=>{
volume_properties: (region_id, provider_id, volume_type)=>{
if (isEmptyString(region_id) || isEmptyString(volume_type)) return [];
return getNodeAjaxOptions('biganimal_volume_properties', pgAdmin.Browser.Nodes['server'],
props.nodeInfo, props.nodeData, {
useCache:false,
cacheNode: 'server',
customGenerateUrl: ()=>{
return url_for('biganimal.volume_properties', {'region_id': region_id || 0, 'volume_type': volume_type || ''});
return url_for('biganimal.volume_properties', {'region_id': region_id || 0, 'provider_id': provider_id || 0, 'volume_type': volume_type || ''});
}
});
},
@@ -70,6 +154,7 @@ export function BigAnimalInstance(props) {
nodeInfo: props.nodeInfo,
nodeData: props.nodeData,
hostIP: props.hostIP,
provider: props.bigAnimalClusterTypeData.provider,
});
setBigAnimalInstance(bigAnimalSchema);
}, [props.cloudProvider]);
@@ -92,10 +177,11 @@ BigAnimalInstance.propTypes = {
cloudProvider: PropTypes.string,
setBigAnimalInstanceData: PropTypes.func,
hostIP: PropTypes.string,
bigAnimalClusterTypeData: PropTypes.object,
};
// BigAnimal Instance
// BigAnimal Database
export function BigAnimalDatabase(props) {
const [bigAnimalDatabase, setBigAnimalDatabase] = React.useState();
@@ -108,15 +194,18 @@ export function BigAnimalDatabase(props) {
return url_for('biganimal.db_types');
}
}),
db_versions: (db_type)=>getNodeAjaxOptions('biganimal_db_versions', pgAdmin.Browser.Nodes['server'], props.nodeInfo, props.nodeData, {
db_versions: (cluster_type, pg_type)=>getNodeAjaxOptions('biganimal_db_versions', pgAdmin.Browser.Nodes['server'], props.nodeInfo, props.nodeData, {
useCache:false,
cacheNode: 'server',
customGenerateUrl: ()=>{
return url_for('biganimal.db_versions', {'db_type': db_type || 'pg'});
return url_for('biganimal.db_versions', {'cluster_type': cluster_type || 'single', 'pg_type': pg_type || 'pg'});
}
}),
server_groups: ()=>getNodeListById(pgAdmin.Browser.Nodes['server_group'], props.nodeInfo, props.nodeData),
}, {gid: props.nodeInfo['server_group']._id});
}, {
gid: props.nodeInfo['server_group']._id,
cluster_type: props.bigAnimalClusterTypeData.cluster_type,
});
setBigAnimalDatabase(bigAnimalDBSchema);
}, [props.cloudProvider]);
@@ -137,12 +226,13 @@ BigAnimalDatabase.propTypes = {
nodeData: PropTypes.object,
cloudProvider: PropTypes.string,
setBigAnimalDatabaseData: PropTypes.func,
bigAnimalClusterTypeData: PropTypes.object
};
export function validateBigAnimal() {
return new Promise((resolve, reject)=>{
let _url = url_for('biganimal.verification') ;
let _url = url_for('biganimal.verification');
axiosApi.get(_url)
.then((res) => {
if (res.data.data) {
@@ -162,7 +252,7 @@ function createData(name, value) {
return { name, value };
}
export function getBigAnimalSummary(cloud, bigAnimalInstanceData, bigAnimalDatabaseData) {
export function getBigAnimalSummary(cloud, bigAnimalClusterTypeData, bigAnimalInstanceData, bigAnimalDatabaseData) {
const rows1 = [
createData(gettext('Cloud'), cloud),
createData(gettext('Instance name'), bigAnimalInstanceData.name),
@@ -174,49 +264,72 @@ export function getBigAnimalSummary(cloud, bigAnimalInstanceData, bigAnimalDatab
let instance_size = bigAnimalInstanceData.instance_size.split('||');
const rows2 = [
createData(gettext('Cluster type'), bigAnimalClusterTypeData.cluster_type),
createData(gettext('No. of Standby Replicas'), bigAnimalClusterTypeData.replicas),
createData(gettext('Provider'), bigAnimalClusterTypeData.provider),
];
const rows3 = [
createData(gettext('Instance type'), bigAnimalInstanceData.instance_type),
createData(gettext('Instance series'), bigAnimalInstanceData.instance_series),
createData(gettext('Instance size'), instance_size[0]),
];
const rows3 = [
const rows4 = [
createData(gettext('Volume type'), bigAnimalInstanceData.volume_type),
createData(gettext('Volume properties'), bigAnimalInstanceData.volume_properties),
createData(gettext('Volume size'), bigAnimalInstanceData.volume_size),
createData(gettext('Volume IOPS'), bigAnimalInstanceData.volume_IOPS),
];
const rows4 = [
const rows5 = [
createData(gettext('Password'), 'xxxxxxx'),
createData(gettext('Database Type'), bigAnimalDatabaseData.database_type),
createData(gettext('Database Version'), bigAnimalDatabaseData.postgres_version),
createData(gettext('High Availability'), bigAnimalDatabaseData.high_availability),
createData(gettext('No. of Standby Replicas'), bigAnimalDatabaseData.replicas),
];
return [rows1, rows2, rows3, rows4];
return [rows1, rows2, rows3, rows4, rows5];
}
export function validateBigAnimalStep2(cloudInstanceDetails) {
export function validateBigAnimalStep2(cloudTypeDetails) {
let isError = false;
if (isEmptyString(cloudInstanceDetails.name) ||
isEmptyString(cloudInstanceDetails.region) || isEmptyString(cloudInstanceDetails.instance_type) ||
isEmptyString(cloudInstanceDetails.instance_series)|| isEmptyString(cloudInstanceDetails.instance_size) ||
isEmptyString(cloudInstanceDetails.volume_type)|| isEmptyString(cloudInstanceDetails.volume_properties)) {
if (isEmptyString(cloudTypeDetails.cluster_type) || isEmptyString(cloudTypeDetails.provider) ||
(cloudTypeDetails.cluster_type == 'ha' && cloudTypeDetails.replicas == 0)
) {
isError = true;
}
return isError;
}
export function validateBigAnimalStep3(cloudDBDetails, nodeInfo) {
export function validateBigAnimalStep3(cloudDetails) {
let isError = false;
if (isEmptyString(cloudDetails.name) ||
isEmptyString(cloudDetails.region) || isEmptyString(cloudDetails.instance_type) ||
isEmptyString(cloudDetails.instance_series)|| isEmptyString(cloudDetails.instance_size) ||
isEmptyString(cloudDetails.volume_type) || (cloudDetails.provider != 'aws' && isEmptyString(cloudDetails.volume_properties)) ) {
isError = true;
}
if (cloudDetails.provider == 'aws') {
if (isEmptyString(cloudDetails.volume_IOPS) || isEmptyString(cloudDetails.volume_IOPS) || (cloudDetails.volume_IOPS != 'io2' &&
(cloudDetails.volume_size < 1 || cloudDetails.volume_size > 16384)) ||
(cloudDetails.volume_IOPS == 'io2' && (cloudDetails.volume_size < 4 || cloudDetails.volume_size > 16384)) ||
(cloudDetails.volume_IOPS != 'io2' && cloudDetails.volume_IOPS != 3000) ||
(cloudDetails.volume_IOPS == 'io2' && (cloudDetails.volume_IOPS < 100 || cloudDetails.volume_IOPS > 2000))) {
isError = true;
}
}
return isError;
}
export function validateBigAnimalStep4(cloudDBDetails, nodeInfo) {
let isError = false;
if (isEmptyString(cloudDBDetails.password) ||
isEmptyString(cloudDBDetails.database_type) || isEmptyString(cloudDBDetails.postgres_version)) {
isError = true;
}
if(cloudDBDetails.high_availability && (isEmptyString(cloudDBDetails.replicas) || cloudDBDetails.replicas <= 0)) {
isError = true;
}
if (isEmptyString(cloudDBDetails.gid)) cloudDBDetails.gid = nodeInfo['server_group']._id;
return isError;
}

View File

@@ -0,0 +1,468 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import gettext from 'sources/gettext';
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
import { isEmptyString } from 'sources/validators';
import { CLOUD_PROVIDERS } from './cloud_constants';
class BigAnimalClusterTypeSchema extends BaseUISchema {
constructor(fieldOptions = {}, initValues = {}) {
super({
oid: undefined,
cluster_type: '',
replicas: 0,
provider: '',
...initValues
});
this.fieldOptions = {
...fieldOptions,
};
this.initValues = initValues;
}
get idAttribute() {
return 'oid';
}
validate(data, setErrMsg) {
if (data.cluster_type == 'ha' && data.replicas == 0) {
setErrMsg('replicas', gettext('Please select number of stand by replicas.'));
return true;
}
return false;
}
get baseFields() {
return [
{
id: 'cluster_type', label: gettext('Cluster type'), noEmpty: true,
type: () => {
return {
type: 'toggle',
options: [
{'label': gettext('Single Node'), value: 'single'},
{'label': gettext('High Availability'), value: 'ha'},
{'label': gettext('Extreme High Availability'), value: 'eha'},
],
};
},
}, {
id: 'replicas', label: gettext('Number of standby replicas'), type: 'select',
mode: ['create'], deps: ['cluster_type'],
controlProps: { allowClear: false },
helpMessage: gettext('Adding standby replicas will increase your number of CPUs, as well as your cost.'),
options: [
{'label': gettext('1'), 'value': 1},
{'label': gettext('2'), 'value': 2},
],
disabled: (state) => {
return state.cluster_type != 'ha';
}
}, { id: 'provider', label: gettext('Cluster provider'), noEmpty: true,
type: 'toggle', className: this.initValues.classes.providerHeight,
options: this.initValues.bigAnimalProviders,
},
];
}
}
class BigAnimalInstanceSchema extends BaseUISchema {
constructor(fieldOptions = {}, initValues={}) {
super({
oid: undefined,
instance_type: '',
instance_series: '',
instance_size: '',
...initValues
});
this.fieldOptions = {
...fieldOptions,
};
this.initValues = initValues;
}
get idAttribute() {
return 'oid';
}
get baseFields() {
return [
{
id: 'instance_type', label: gettext('Instance type'),
mode: ['create'],
deps: [['region']],
type: (state) => {
return {
type: 'select',
options: ()=>this.fieldOptions.instance_types(state.region, state.provider),
optionsReloadBasis: state.region,
optionsLoaded: (options) => { state.instanceData = options; },
controlProps: {
allowClear: false,
filter: (options) => {
if (options.length == 0) return;
let _types = _.uniq(_.map(options, 'category')),
_options = [];
_.forEach(_types, (region) => {
_options.push({
'label': region,
'value': region
});
});
return _options;
},
}
};
},
noEmpty: true,
},{
id: 'instance_series', label: gettext('Instance series'),
mode: ['create'], deps: ['instance_type'],
type: (state) => {
return {
type: 'select',
options: state.instanceData,
optionsReloadBasis: state.instance_type,
controlProps: {
allowClear: false,
filter: (options) => {
if (options.length == 0) return;
let _types = _.filter(options, {'category': state.instance_type}),
_options = [];
_types = _.uniq(_.map(_types, 'familyName'));
_.forEach(_types, (value) => {
_options.push({
'label': value,
'value': value
});
});
return _options;
},
}
};
},
noEmpty: true,
},{
id: 'instance_size', label: gettext('Instance size'),
mode: ['create'], deps: ['instance_series'],
type: (state) => {
return {
type: 'select',
options: state.instanceData,
optionsReloadBasis: state.instance_series,
controlProps: {
allowClear: false,
filter: (options) => {
if (options.length == 0) return;
let _types = _.filter(options, {'familyName': state.instance_series}),
_options = [];
_.forEach(_types, (value) => {
_options.push({
'label': value.instanceTypeName + ' (' + value.cpu + 'vCPU, ' + value.ram + 'GB RAM)',
'value': value.instanceTypeName + ' (' + value.cpu + 'vCPU, ' + value.ram + 'GB RAM)' + '||' + value.instanceTypeId,
});
});
return _options;
},
}
};
}, noEmpty: true,
},
];
}
}
class BigAnimalVolumeSchema extends BaseUISchema {
constructor(fieldOptions = {}, initValues = {}) {
super({
oid: undefined,
volume_type: '',
volume_properties: '',
volume_size: 4,
volume_IOPS: '',
...initValues
});
this.fieldOptions = {
...fieldOptions,
};
this.initValues = initValues;
this.volumeType = '';
}
get idAttribute() {
return 'oid';
}
validate(data, setErrMsg) {
if (data.provider != CLOUD_PROVIDERS.AWS && isEmptyString(data.volume_properties)) {
setErrMsg('replicas', gettext('Please select volume properties.'));
return true;
}
if (data.provider == CLOUD_PROVIDERS.AWS) {
if (isEmptyString(data.volume_IOPS)) {
setErrMsg('replicas', gettext('Please select volume IOPS.'));
return true;
}
if (!isEmptyString(data.volume_size)) {
if( data.volume_IOPS != 'io2' && (data.volume_size < 1 || data.volume_size > 16384)) {
setErrMsg('replicas', gettext('Please enter the volume size in the range between 1 tp 16384.'));
return true;
}
if (data.volume_IOPS == 'io2' && (data.volume_size < 4 || data.volume_size > 16384)) {
setErrMsg('replicas', gettext('Please enter the volume size in the range between 4 tp 16384.'));
return true;
}
}
if (!isEmptyString(data.volume_IOPS)) {
if(data.volume_IOPS != 'io2' && data.volume_IOPS != 3000) {
setErrMsg('replicas', gettext('Please enter the volume IOPS 3000.'));
return true;
}
if(data.volume_IOPS == 'io2' && (data.volume_IOPS < 100 || data.volume_IOPS > 2000)) {
setErrMsg('replicas', gettext('Please enter the volume IOPS in the range between 100 tp 2000.'));
return true;
}
}
}
return false;
}
get baseFields() {
let obj = this;
return [
{
id: 'volume_type', label: gettext('Volume type'),
mode: ['create'], deps: [['region'], ['instance_series']],
type: (state) => {
return {
type: 'select',
options: ()=>this.fieldOptions.volume_types(state.region, state.provider),
optionsReloadBasis: state.region,
controlProps: {
allowClear: false,
filter: (options) => {
if (options.length == 0) return;
return options.filter((option) => {
return (option.supportedInstanceFamilyNames.includes(state.instance_series));
});
},
}
};
}, noEmpty: true,
},{
id: 'volume_properties', label: gettext('Volume properties'),
mode: ['create'], deps: ['volume_type', 'provider'],
type: (state) => {
return {
type: 'select',
options: ()=>this.fieldOptions.volume_properties(state.region, state.provider, state.volume_type),
optionsReloadBasis: state.volume_type,
};
},
visible: (state) => {
return state.provider !== CLOUD_PROVIDERS.AWS;
},
}, {
id: 'volume_size', label: gettext('Size'), type: 'text',
mode: ['create'], noEmpty: true, deps: ['volume_type'],
depChange: (state, source)=> {
obj.volumeType = state.volume_type;
if (source[0] !== 'volume_size') {
if(state.volume_type == 'io2' || state.provider === CLOUD_PROVIDERS.AZURE) {
return {volume_size: 4};
} else {
return {volume_size: 1};
}
}
},
visible: (state) => {
return state.provider === CLOUD_PROVIDERS.AWS;
},
helpMessage: obj.volumeType == 'io2' ? gettext('Size (4-16,384 GiB)') : gettext('Size (1-16,384 GiB)')
}, {
id: 'volume_IOPS', label: gettext('IOPS'), type: 'text',
mode: ['create'],
helpMessage: obj.volumeType == 'io2' ? gettext('IOPS (100-2,000)') : gettext('IOPS (3,000-3,000)'),
visible: (state) => {
return state.provider === CLOUD_PROVIDERS.AWS;
}, deps: ['volume_type'],
depChange: (state, source) => {
obj.volumeType = state.volume_type;
if (source[0] !== 'volume_IOPS') {
if (state.provider === CLOUD_PROVIDERS.AWS) {
if(state.volume_type === 'io2') {
return {volume_IOPS: 100};
} else {
return {volume_IOPS: 3000};
}
} else {
return {volume_IOPS: 120};
}
}
},
},
];
}
}
class BigAnimalDatabaseSchema extends BaseUISchema {
constructor(fieldOptions = {}, initValues = {}) {
super({
oid: undefined,
password: '',
confirm_password: '',
database_type: '',
postgres_version: '',
high_availability: false,
replicas: 0,
...initValues
});
this.fieldOptions = {
...fieldOptions,
};
this.initValues = initValues;
}
validate(data, setErrMsg) {
if(!isEmptyString(data.password) && !isEmptyString(data.confirm_password)
&& data.password != data.confirm_password) {
setErrMsg('confirm_password', gettext('Passwords do not match.'));
return true;
}
if (!isEmptyString(data.confirm_password) && data.confirm_password.length < 12) {
setErrMsg('confirm_password', gettext('Password must be 12 characters or more.'));
return true;
}
if (data.high_availability && (isEmptyString(data.replicas) || data.replicas <= 0)) {
setErrMsg('replicas', gettext('Please select number of stand by replicas.'));
return true;
}
return false;
}
get idAttribute() {
return 'oid';
}
get baseFields() {
return [
{
id: 'gid', label: gettext('pgAdmin server group'), type: 'select',
options: this.fieldOptions.server_groups,
mode: ['create'],
controlProps: { allowClear: false },
noEmpty: true,
}, {
id: 'database_type', label: gettext('Database type'), mode: ['create'],
type: 'select',
options: this.fieldOptions.db_types,
noEmpty: true, orientation: 'vertical',
},{
id: 'postgres_version', label: gettext('Database version'),
mode: ['create'], noEmpty: true, deps: ['database_type'],
type: (state) => {
return {
type: 'select',
options: ()=>this.fieldOptions.db_versions(this.initValues.cluster_typ, state.database_type),
optionsReloadBasis: state.database_type,
};
},
},{
id: 'password', label: gettext('Database password'), type: 'password',
mode: ['create'], noEmpty: true,
},{
id: 'confirm_password', label: gettext('Confirm password'), type: 'password',
mode: ['create'], noEmpty: true,
},
];
}
}
class BigAnimalClusterSchema extends BaseUISchema {
constructor(fieldOptions = {}, initValues = {}) {
super({
oid: undefined,
name: '',
region: '',
cloud_type: 'public',
biganimal_public_ip: initValues.hostIP,
...initValues
});
this.fieldOptions = {
...fieldOptions,
};
this.initValues = initValues;
this.instance_types = new BigAnimalInstanceSchema({
instance_types: this.fieldOptions.instance_types,
});
this.volume_types = new BigAnimalVolumeSchema({
volume_types: this.fieldOptions.volume_types,
volume_properties: this.fieldOptions.volume_properties
});
}
get idAttribute() {
return 'oid';
}
get baseFields() {
return [
{
id: 'name', label: gettext('Cluster name'), type: 'text',
mode: ['create'], noEmpty: true,
},{
id: 'region', label: gettext('Region'),
controlProps: { allowClear: false },
noEmpty: true,
mode: ['create'],
dep: [this.initValues.provider],
type: () => {
return {
type: 'select',
options: ()=>this.fieldOptions.regions(this.initValues.provider),
optionsReloadBasis: this.initValues.provider,
};
},
},{
id: 'biganimal_public_ip', label: gettext('Public IP range'), type: 'text',
mode: ['create'],
helpMessage: gettext('IP address range for allowed inbound traffic, for example: 127.0.0.1/32. Add multiple IP addresses/ranges separated with commas. Leave blank for 0.0.0.0/0'),
},{
type: 'nested-fieldset', label: gettext('Instance Type'),
mode: ['create'], deps: ['region'],
schema: this.instance_types,
},{
type: 'nested-fieldset', label: gettext('Storage'),
mode: ['create'], deps: ['region'],
schema: this.volume_types,
}
];
}
}
export {
BigAnimalClusterSchema,
BigAnimalDatabaseSchema,
BigAnimalClusterTypeSchema
};

View File

@@ -69,8 +69,8 @@ export function FinalSummary(props) {
summaryHeader = ['Cloud Details', 'Version and Instance Details', 'Storage Details', 'Database Details'];
if (props.cloudProvider == 'biganimal') {
summary = getBigAnimalSummary(props.cloudProvider, props.instanceData, props.databaseData);
summaryHeader[1] = 'Version Details';
summary = getBigAnimalSummary(props.cloudProvider, props.clusterTypeData, props.instanceData, props.databaseData);
summaryHeader = ['Cloud Details', 'Cluster Details' ,'Version Details', 'Storage Details', 'Database Details'];
} else if(props.cloudProvider == 'azure') {
summaryHeader.push('Network Connectivity','Availability');
summary = getAzureSummary(props.cloudProvider, props.instanceData, props.databaseData);
@@ -107,4 +107,5 @@ FinalSummary.propTypes = {
cloudProvider: PropTypes.string,
instanceData: PropTypes.object,
databaseData: PropTypes.object,
clusterTypeData: PropTypes.object,
};

View File

@@ -0,0 +1,15 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
export const CLOUD_PROVIDERS = {
AZURE: 'azure',
BIGANIMAL: 'biganimal',
AWS: 'aws',
RDS: 'rds',
};

View File

@@ -1,678 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import gettext from 'sources/gettext';
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
import { isEmptyString } from 'sources/validators';
class CloudInstanceDetailsSchema extends BaseUISchema {
constructor(fieldOptions = {}, initValues = {}) {
super({
oid: undefined,
name: '',
public_ip: initValues.hostIP,
high_availability: false,
...initValues
});
this.fieldOptions = {
...fieldOptions,
};
this.initValues = initValues;
}
get idAttribute() {
return 'oid';
}
get baseFields() {
return [
{
id: 'name', label: gettext('Instance name'), type: 'text',
mode: ['create'], noEmpty: true,
}, {
id: 'public_ip', label: gettext('Public IP range'), type: 'text',
mode: ['create'],
helpMessage: gettext('IP address range for allowed inbound traffic, for example: 127.0.0.1/32. Add multiple IP addresses/ranges separated with commas.'),
}, {
type: 'nested-fieldset', label: gettext('Version & Instance'),
mode: ['create'],
schema: new InstanceSchema(this.fieldOptions.version,
this.fieldOptions.instance_type,
this.fieldOptions.getInstances),
}, {
type: 'nested-fieldset', label: gettext('Storage'),
mode: ['create'],
schema: new StorageSchema(),
}, {
type: 'nested-fieldset', label: gettext('Availability'),
mode: ['create'],
schema: new HighAvailablity(),
},
];
}
}
class CloudDBCredSchema extends BaseUISchema {
constructor(fieldOptions = {}, initValues = {}) {
super({
oid: null,
region: '',
access_key: '',
secret_access_key: '',
session_token: '',
is_valid_cred: false,
...initValues
});
this.fieldOptions = {
...fieldOptions,
};
}
get idAttribute() {
return 'oid';
}
get baseFields() {
return [
{
id: 'region', label: gettext('Region'),
type: 'select',
options: this.fieldOptions.regions,
controlProps: { allowClear: false },
noEmpty: true,
helpMessage: gettext('The cloud instance will be deployed in the selected region.')
},{
id: 'access_key', label: gettext('AWS access key'), type: 'text',
mode: ['create'], noEmpty: true,
}, {
id: 'secret_access_key', label: gettext('AWS secret access key'), type: 'password',
mode: ['create'], noEmpty: true,
}, {
id: 'session_token', label: gettext('AWS session token'), type: 'multiline',
mode: ['create'], noEmpty: false,
helpMessage: gettext('Temporary AWS session required session token.')
}
];
}
}
class DatabaseSchema extends BaseUISchema {
constructor(fieldOptions = {}, initValues={}) {
super({
oid: undefined,
gid: undefined,
db_name: '',
db_username: '',
db_password: '',
db_confirm_password: '',
db_port: 5432,
...initValues,
});
this.fieldOptions = {
...fieldOptions,
};
}
validate(data, setErrMsg) {
if(!isEmptyString(data.db_password) && !isEmptyString(data.db_confirm_password)
&& data.db_password != data.db_confirm_password) {
setErrMsg('db_confirm_password', gettext('Passwords do not match.'));
return true;
}
if (!isEmptyString(data.db_confirm_password) && data.db_confirm_password.length < 8) {
setErrMsg('db_confirm_password', gettext('Password must be 8 characters or more.'));
return true;
}
if (data.db_confirm_password.includes('\'') || data.db_confirm_password.includes('"') ||
data.db_confirm_password.includes('@') || data.db_confirm_password.includes('/')) {
setErrMsg('db_confirm_password', gettext('Invalid passowrd.'));
return true;
}
return false;
}
get idAttribute() {
return 'oid';
}
get baseFields() {
return [{
id: 'gid', label: gettext('pgAdmin server group'), type: 'select',
options: this.fieldOptions.server_groups,
mode: ['create'],
controlProps: { allowClear: false },
noEmpty: true,
}, {
id: 'db_name', label: gettext('Database name'), type: 'text',
mode: ['create'], noEmpty: true,
}, {
id: 'db_username', label: gettext('Username'), type: 'text',
mode: ['create'], noEmpty: true,
}, {
id: 'db_password', label: gettext('Password'), type: 'password',
mode: ['create'], noEmpty: true,
helpMessage: gettext('At least 8 printable ASCII characters. Cannot contain any of the following: / \(slash\), \'\(single quote\), "\(double quote\) and @ \(at sign\).')
}, {
id: 'db_confirm_password', label: gettext('Confirm password'),
type: 'password',
mode: ['create'], noEmpty: true,
}, {
id: 'db_port', label: gettext('Port'), type: 'text',
mode: ['create'], noEmpty: true,
}];
}
}
export class InstanceSchema extends BaseUISchema {
constructor(versionOpts, instanceOpts, getInstances) {
super({
db_version: '',
db_instance_class: 'm',
instance_type: '',
reload_instances: true,
});
this.versionOpts = versionOpts;
this.instanceOpts = instanceOpts;
this.getInstances = getInstances;
this.instanceData = [];
}
get baseFields() {
return [{
id: 'db_version', label: gettext('Database version'),
type: 'select',
options: this.versionOpts,
controlProps: { allowClear: false },
deps: ['name'],
noEmpty: true,
},{
id: 'db_instance_class', label: gettext('Instance class'),
type: 'select',
options: [
{'label': gettext('Standard classes (includes m classes)'), value: 'm'},
{'label': gettext('Memory optimized classes (includes r & x classes)'), value: 'x'},
{'label': gettext('Burstable classes (includes t classes)'), value: 't'},
], noEmpty: true
},{
id: 'instance_type', label: gettext('Instance type'),
options: this.instanceOpts,
deps: ['db_version', 'db_instance_class'],
depChange: (state, source)=> {
if (source[0] == 'db_instance_class') {
return {reload_instances: false};
} else {
state.instanceData = [];
return {reload_instances: true};
}
},
type: (state) => {
return {
type: 'select',
options: ()=>this.getInstances(state.db_version,
state.reload_instances, state.instanceData),
optionsLoaded: (options) => { state.instanceData = options; },
optionsReloadBasis: state.db_version + (state.db_instance_class || 'm'),
controlProps: {
allowClear: false,
filter: (options) => {
if (options.length == 0) return;
let pattern = 'db.m';
let pattern_1 = 'db.m';
if (state.db_instance_class) {
pattern = 'db.' + state.db_instance_class;
pattern_1 = 'db.' + state.db_instance_class;
}
if (state.db_instance_class == 'x') {
pattern_1 = 'db.' + 'r';
}
return options.filter((option) => {
return (option.value.includes(pattern) || option.value.includes(pattern_1));
});
},
}
};
},
}];
}
}
export class StorageSchema extends BaseUISchema {
constructor() {
super({
storage_type: 'io1',
storage_size: 100,
storage_IOPS: 3000,
storage_msg: 'Minimum: 20 GiB. Maximum: 16,384 GiB.'
});
}
get baseFields() {
return [
{
id: 'storage_type', label: gettext('Storage type'), type: 'select',
mode: ['create'],
options: [
{'label': gettext('General Purpose SSD (gp2)'), 'value': 'gp2'},
{'label': gettext('Provisioned IOPS SSD (io1)'), 'value': 'io1'},
{'label': gettext('Magnetic'), 'value': 'standard'}
], noEmpty: true,
},{
id: 'storage_size', label: gettext('Allocated storage'), type: 'text',
mode: ['create'], noEmpty: true, deps: ['storage_type'],
depChange: (state, source)=> {
if (source[0] !== 'storage_size')
if(state.storage_type === 'io1') {
return {storage_size: 100};
} else if(state.storage_type === 'gp2') {
return {storage_size: 20};
} else {
return {storage_size: 5};
}
},
helpMessage: gettext('Size in GiB.')
}, {
id: 'storage_IOPS', label: gettext('Provisioned IOPS'), type: 'text',
mode: ['create'],
visible: (state) => {
return state.storage_type === 'io1';
} , deps: ['storage_type'],
depChange: (state, source) => {
if (source[0] !== 'storage_IOPS') {
if(state.storage_type === 'io1') {
return {storage_IOPS: 3000};
}
}
},
},
];
}
}
export class HighAvailablity extends BaseUISchema {
constructor() {
super({
high_availability: false
});
}
get baseFields() {
return [
{
id: 'high_availability',
label: gettext('High availability'),
type: 'switch',
mode: ['create'],
helpMessage: gettext(
'Creates a standby in a different Availability Zone (AZ) to provide data redundancy, eliminate I/O freezes, and minimize latency spikes during system backups.'
),
},
];
}
}
class BigAnimalInstanceSchema extends BaseUISchema {
constructor(fieldOptions = {}, initValues={}) {
super({
oid: undefined,
instance_type: '',
instance_series: '',
instance_size: '',
...initValues
});
this.fieldOptions = {
...fieldOptions,
};
this.initValues = initValues;
}
get idAttribute() {
return 'oid';
}
get baseFields() {
return [
{
id: 'instance_type', label: gettext('Instance type'),
mode: ['create'],
deps: [['region']],
type: (state) => {
return {
type: 'select',
options: ()=>this.fieldOptions.instance_types(state.region),
optionsReloadBasis: state.region,
optionsLoaded: (options) => { state.instanceData = options; },
controlProps: {
allowClear: false,
filter: (options) => {
if (options.length == 0) return;
let _types = _.uniq(_.map(options, 'category')),
_options = [];
_.forEach(_types, (region) => {
_options.push({
'label': region,
'value': region
});
});
return _options;
},
}
};
},
noEmpty: true,
},{
id: 'instance_series', label: gettext('Instance series'),
mode: ['create'], deps: ['instance_type'],
type: (state) => {
return {
type: 'select',
options: state.instanceData,
optionsReloadBasis: state.instance_type,
controlProps: {
allowClear: false,
filter: (options) => {
if (options.length == 0) return;
let _types = _.filter(options, {'category': state.instance_type}),
_options = [];
_types = _.uniq(_.map(_types, 'familyName'));
_.forEach(_types, (value) => {
_options.push({
'label': value,
'value': value
});
});
return _options;
},
}
};
},
noEmpty: true,
},{
id: 'instance_size', label: gettext('Instance size'),
mode: ['create'], deps: ['instance_series'],
type: (state) => {
return {
type: 'select',
options: state.instanceData,
optionsReloadBasis: state.instance_series,
controlProps: {
allowClear: false,
filter: (options) => {
if (options.length == 0) return;
let _types = _.filter(options, {'familyName': state.instance_series}),
_options = [];
_.forEach(_types, (value) => {
_options.push({
'label': value.instanceTypeName + ' (' + value.cpu + 'vCPU, ' + value.ram + 'GB RAM)',
'value': value.instanceTypeName + ' (' + value.cpu + 'vCPU, ' + value.ram + 'GB RAM)' + '||' + value.instanceTypeId,
});
});
return _options;
},
}
};
}, noEmpty: true,
},
];
}
}
class BigAnimalVolumeSchema extends BaseUISchema {
constructor(fieldOptions = {}, initValues = {}) {
super({
oid: undefined,
volume_type: '',
volume_properties: '',
...initValues
});
this.fieldOptions = {
...fieldOptions,
};
this.initValues = initValues;
}
get idAttribute() {
return 'oid';
}
get baseFields() {
return [
{
id: 'volume_type', label: gettext('Volume type'),
mode: ['create'], deps: [['region']],
type: (state) => {
return {
type: 'select',
options: ()=>this.fieldOptions.volume_types(state.region),
optionsReloadBasis: state.region,
};
}, noEmpty: true,
},{
id: 'volume_properties', label: gettext('Volume properties'),
mode: ['create'], deps: ['volume_type'],
type: (state) => {
return {
type: 'select',
options: ()=>this.fieldOptions.volume_properties(state.region, state.volume_type),
optionsReloadBasis: state.volume_type,
};
}, noEmpty: true,
},
];
}
}
class BigAnimalDatabaseSchema extends BaseUISchema {
constructor(fieldOptions = {}, initValues = {}) {
super({
oid: undefined,
password: '',
confirm_password: '',
database_type: '',
postgres_version: '',
high_availability: false,
replicas: 0,
...initValues
});
this.fieldOptions = {
...fieldOptions,
};
this.initValues = initValues;
}
validate(data, setErrMsg) {
if(!isEmptyString(data.password) && !isEmptyString(data.confirm_password)
&& data.password != data.confirm_password) {
setErrMsg('confirm_password', gettext('Passwords do not match.'));
return true;
}
if (!isEmptyString(data.confirm_password) && data.confirm_password.length < 12) {
setErrMsg('confirm_password', gettext('Password must be 12 characters or more.'));
return true;
}
if (data.high_availability && (isEmptyString(data.replicas) || data.replicas <= 0)) {
setErrMsg('replicas', gettext('Please select number of stand by replicas.'));
return true;
}
return false;
}
get idAttribute() {
return 'oid';
}
get baseFields() {
return [
{
id: 'gid', label: gettext('pgAdmin server group'), type: 'select',
options: this.fieldOptions.server_groups,
mode: ['create'],
controlProps: { allowClear: false },
noEmpty: true,
}, {
id: 'database_type', label: gettext('Database type'), mode: ['create'],
type: 'select',
options: this.fieldOptions.db_types,
noEmpty: true, orientation: 'vertical',
},{
id: 'postgres_version', label: gettext('PostgreSQL version'),
mode: ['create'], noEmpty: true, deps: ['database_type'],
options: this.fieldOptions.db_versions,
type: (state) => {
return {
type: 'select',
options: ()=>this.fieldOptions.db_versions(state.database_type),
optionsReloadBasis: state.database_type,
};
},
},{
id: 'password', label: gettext('Database password'), type: 'password',
mode: ['create'], noEmpty: true,
},{
id: 'confirm_password', label: gettext('Confirm password'), type: 'password',
mode: ['create'], noEmpty: true,
},{
type: 'nested-fieldset', label: gettext('Availability'),
mode: ['create'],
schema: new BigAnimalHighAvailSchema(),
},
];
}
}
class BigAnimalClusterSchema extends BaseUISchema {
constructor(fieldOptions = {}, initValues = {}) {
super({
oid: undefined,
name: '',
region: '',
cloud_type: 'public',
biganimal_public_ip: initValues.hostIP,
...initValues
});
this.fieldOptions = {
...fieldOptions,
};
this.initValues = initValues;
this.instance_types = new BigAnimalInstanceSchema({
instance_types: this.fieldOptions.instance_types,
});
this.volume_types = new BigAnimalVolumeSchema({
volume_types: this.fieldOptions.volume_types,
volume_properties: this.fieldOptions.volume_properties
});
}
get idAttribute() {
return 'oid';
}
get baseFields() {
return [
{
id: 'name', label: gettext('Cluster name'), type: 'text',
mode: ['create'], noEmpty: true,
},{
id: 'region', label: gettext('Region'), type: 'select',
options: this.fieldOptions.regions,
controlProps: { allowClear: false },
noEmpty: true,
mode: ['create'],
},{
id: 'biganimal_public_ip', label: gettext('Public IP range'), type: 'text',
mode: ['create'],
helpMessage: gettext('IP address range for allowed inbound traffic, for example: 127.0.0.1/32. Add multiple IP addresses/ranges separated with commas. Leave blank for 0.0.0.0/0'),
},{
type: 'nested-fieldset', label: gettext('Instance Type'),
mode: ['create'], deps: ['region'],
schema: this.instance_types,
},{
type: 'nested-fieldset', label: gettext('Storage'),
mode: ['create'], deps: ['region'],
schema: this.volume_types,
}
];
}
}
class BigAnimalHighAvailSchema extends BaseUISchema {
constructor(fieldOptions = {}, initValues = {}) {
super({
oid: undefined,
high_availability: false,
replicas: 0,
...initValues
});
this.fieldOptions = {
...fieldOptions,
};
this.initValues = initValues;
}
get idAttribute() {
return 'oid';
}
get baseFields() {
return [
{
id: 'high_availability_note', type: 'note',
mode: ['create'],
text: gettext('High availability clusters are configured with one primary and up to two '
+ 'standby replicas. Clusters are configured across availability zones in regions with availability zones.'),
},{
id: 'high_availability', label: gettext('High availability'), type: 'switch',
mode: ['create']
},{
id: 'replicas', label: gettext('Number of standby replicas'), type: 'select',
mode: ['create'], deps: ['high_availability'],
controlProps: { allowClear: false },
helpMessage: gettext('Adding standby replicas will increase your number of CPUs, as well as your cost.'),
options: [
{'label': gettext('1'), 'value': 1},
{'label': gettext('2'), 'value': 2},
], noEmpty: true,
disabled: (state) => {
return !state.high_availability;
}
},
];
}
}
export {
CloudInstanceDetailsSchema,
CloudDBCredSchema,
DatabaseSchema,
BigAnimalClusterSchema,
BigAnimalDatabaseSchema
};