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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1282 additions and 825 deletions

View File

@ -23,6 +23,18 @@ will be redirected to the new tab for the verification.
Once you confirm the one time code, the pgAdmin will automatically detect it
and the next button will be enabled. To proceed further, click on the next button.
.. image:: images/cloud_biganimal_cluster.png
:alt: Cloud Deployment Provider
:align: center
* Use the *Cluster type* field to choose a cluster type.
* Use the *No. of Standby Replicas* field to specify the replicas if you have selected the High Availability cluster.
* Use the *Cluster provider* field to coose the provider.
.. image:: images/cloud_biganimal_instance.png
:alt: Cloud Deployment Provider
:align: center
@ -48,8 +60,11 @@ details.
* Use the *Volume type* field to select the instance storage type.
* Use the *Volume properties* field to specify the storage capacity.
* Use the *Volume properties* field to specify the storage capacity. This field is specific to Azure.
* Use the *Volume size* field to specify the storage size. This field is specific to AWS.
* Use the *Volume IOPS* field to specify the storage IOPS. This field is specific to AWS.
.. image:: images/cloud_biganimal_database.png
@ -72,11 +87,6 @@ Use the fields from the Database Details tab to specify the Instance details.
* Use the *Confirm password* field to repeat the password.
* Use the *High Availability* field to create the cluster with high availability, which creates a cluster
with one primary and up to two standby replicas in different availability zones.
* Use the *Number of standby replicas* field to specify the standby replicas.
.. image:: images/cloud_biganimal_review.png
:alt: Cloud Deployment Provider

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 134 KiB

View File

@ -65,6 +65,12 @@ class BigAnimalProvider(AbsProvider):
parser_create_instance.add_argument('--volume-properties',
required=True,
help='storage properties')
parser_create_instance.add_argument('--volume-size',
required=True,
help='storage Size')
parser_create_instance.add_argument('--volume-IOPS',
required=True,
help='storage IOPS')
parser_create_instance.add_argument('--private-network', required=True,
help='Private or Public Network')
parser_create_instance.add_argument('--public-ip', default='',
@ -76,6 +82,12 @@ class BigAnimalProvider(AbsProvider):
parser_create_instance.add_argument('--nodes',
required=True,
help='No of Nodes')
parser_create_instance.add_argument('--replicas',
required=True,
help='No. of Stand By Replicas')
parser_create_instance.add_argument('--cloud-provider',
required=True,
help='Provider')
def cmd_create_instance(self, args):
""" Create a biganimal cluster """
@ -105,12 +117,14 @@ class BigAnimalProvider(AbsProvider):
'pgType': {'pgTypeId': args.db_type},
'pgVersion': {'pgVersionId': args.db_version},
'privateNetworking': private_network,
'provider': {'cloudProviderId': 'azure'},
'provider': {'cloudProviderId': args.cloud_provider},
'region': {'regionId': args.region},
'replicas': 1,
'replicas': int(args.replicas),
'storage': {
'volumePropertiesId': args.volume_properties,
'volumeTypeId': args.volume_type
'volumeTypeId': args.volume_type,
'iops': args.volume_IOPS,
'size': args.volume_size + ' Gi'
},
'clusterArchitecture': {
'clusterArchitectureId': args.cluster_arch,

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
};

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.0" id="katman_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 800 600" style="enable-background:new 0 0 800 600;" xml:space="preserve">
<style type="text/css">
.st0{fill:#706D6E;}
.st1{fill:#F1511B;}
.st2{fill:#80CC28;}
.st3{fill:#00ADEF;}
.st4{fill:#FBBC09;}
</style>
<g>
<path class="st0" d="M367.7,261.5l-3.3,9.3h-0.2c-0.6-2.2-1.6-5.3-3.1-9.2l-17.7-44.4h-17.3v70.6h11.4v-43.4c0-2.7-0.1-5.9-0.2-9.6
c0-1.9-0.3-3.4-0.3-4.5h0.3c0.4,2,1,4.1,1.6,6l21.2,51.5h8l21.1-52c0.5-1.2,1-3.5,1.4-5.6h0.2c-0.3,5.1-0.5,9.8-0.5,12.7v44.8h12.2
v-70.6h-16.6L367.7,261.5z M413.9,287.8h11.9v-50.6h-11.9V287.8z M420,215.7c-2,0-3.7,0.7-5.1,2c-1.4,1.3-2.1,3-2.1,5
c0,1.9,0.7,3.6,2.1,4.9c1.4,1.3,3.1,1.9,5.1,1.9c2,0,3.7-0.6,5.1-1.9c1.4-1.3,2.1-2.9,2.1-4.9c0-1.9-0.7-3.5-2.1-4.9
C423.8,216.4,422.1,215.7,420,215.7 M468,236.7c-2.2-0.5-4.4-0.7-6.7-0.7c-5.4,0-10.3,1.2-14.5,3.5c-4.1,2.3-7.3,5.6-9.5,9.8
c-2.2,4.2-3.3,9.1-3.3,14.5c0,4.8,1.1,9.1,3.2,13c2.1,3.9,5.1,6.9,8.9,9c3.8,2.1,8.2,3.2,13,3.2c5.7,0,10.5-1.1,14.3-3.3l0.2-0.1
v-10.9l-0.5,0.4c-1.8,1.3-3.8,2.3-5.8,3c-2.1,0.7-4,1.1-5.7,1.1c-4.7,0-8.4-1.5-11.1-4.3c-2.7-2.9-4.1-6.9-4.1-12
c0-5.1,1.4-9.3,4.3-12.4c2.8-3.1,6.6-4.6,11.1-4.6c3.9,0,7.7,1.3,11.3,3.9l0.5,0.4v-11.5l-0.2-0.1
C472.1,237.8,470.2,237.2,468,236.7 M507.2,236.3c-3,0-5.7,1-8,2.8c-2,1.7-3.5,3.9-4.6,6.8h-0.1v-8.7h-11.9v50.6h11.9v-25.9
c0-4.4,1-8,3-10.7c1.9-2.7,4.5-4.1,7.7-4.1c1.1,0,2.3,0.2,3.6,0.5c1.3,0.3,2.2,0.7,2.8,1.1l0.5,0.4v-12l-0.2-0.1
C510.7,236.6,509.1,236.3,507.2,236.3 M549,274.9c-2.2,2.8-5.6,4.2-10,4.2c-4.4,0-7.8-1.4-10.2-4.3c-2.5-2.9-3.7-7-3.7-12.2
c0-5.4,1.2-9.6,3.7-12.5c2.4-2.9,5.8-4.4,10.1-4.4c4.2,0,7.5,1.4,9.9,4.2c2.4,2.8,3.6,7,3.6,12.4
C552.3,267.9,551.2,272.1,549,274.9 M539.5,236c-8.3,0-14.9,2.5-19.7,7.3c-4.7,4.8-7.1,11.5-7.1,19.8c0,7.9,2.3,14.3,6.9,18.9
c4.6,4.6,10.9,7,18.6,7c8.1,0,14.6-2.5,19.3-7.4c4.7-4.9,7.1-11.5,7.1-19.6c0-8-2.2-14.5-6.7-19.1C553.6,238.3,547.3,236,539.5,236
M591.1,258.1c-3.8-1.5-6.2-2.8-7.1-3.7c-1-0.9-1.4-2.2-1.4-3.9c0-1.5,0.6-2.6,1.8-3.6c1.2-1,2.9-1.4,5.1-1.4c2,0,4.1,0.3,6.1,0.9
c2,0.6,3.8,1.4,5.3,2.5l0.5,0.3v-11l-0.2-0.1c-1.3-0.6-3.2-1.1-5.4-1.5c-2.2-0.4-4.2-0.6-5.9-0.6c-5.7,0-10.4,1.5-13.9,4.3
c-3.6,2.9-5.5,6.7-5.5,11.2c0,2.4,0.4,4.5,1.2,6.3c0.8,1.8,2,3.4,3.6,4.7c1.6,1.3,4,2.7,7.3,4.1c2.7,1.1,4.8,2.1,6.1,2.8
c1.3,0.7,2.2,1.5,2.7,2.2c0.5,0.7,0.8,1.7,0.8,2.9c0,3.4-2.5,5.1-7.8,5.1c-2,0-4.2-0.4-6.6-1.2c-2.4-0.8-4.7-1.9-6.7-3.4l-0.5-0.4
v11.6l0.2,0.1c1.7,0.8,3.9,1.5,6.4,2c2.5,0.5,4.8,0.8,6.8,0.8c6.2,0,11.1-1.5,14.7-4.3c3.7-2.9,5.5-6.8,5.5-11.5c0-3.4-1-6.3-3-8.7
C599.1,262.1,595.8,260,591.1,258.1 M645.5,274.9c-2.2,2.8-5.6,4.2-10,4.2c-4.4,0-7.8-1.4-10.3-4.3c-2.5-2.9-3.7-7-3.7-12.2
c0-5.4,1.2-9.6,3.7-12.5c2.4-2.9,5.8-4.4,10.1-4.4c4.2,0,7.5,1.4,9.9,4.2c2.4,2.8,3.6,7,3.6,12.4
C648.9,267.9,647.7,272.1,645.5,274.9 M636,236c-8.3,0-15,2.5-19.7,7.3c-4.7,4.8-7.1,11.5-7.1,19.8c0,7.9,2.3,14.3,6.9,18.9
c4.6,4.6,10.9,7,18.6,7c8.1,0,14.6-2.5,19.3-7.4c4.7-4.9,7.1-11.5,7.1-19.6c0-8-2.2-14.5-6.7-19.1C650.1,238.3,643.9,236,636,236
M724.7,246.9v-9.7h-12v-15.1l-0.4,0.1l-11.3,3.5l-0.2,0.1v11.4h-17.8v-6.4c0-3,0.7-5.2,2-6.7c1.3-1.5,3.2-2.2,5.5-2.2
c1.7,0,3.5,0.4,5.2,1.2l0.4,0.2V213l-0.2-0.1c-1.6-0.6-3.9-0.9-6.7-0.9c-3.5,0-6.7,0.8-9.5,2.3c-2.8,1.5-5,3.7-6.5,6.4
c-1.5,2.7-2.3,5.9-2.3,9.4v7h-8.4v9.7h8.4v40.9h12v-40.9h17.9v26c0,10.7,5,16.1,15,16.1c1.6,0,3.4-0.2,5.1-0.6
c1.8-0.4,3-0.8,3.7-1.2l0.2-0.1v-9.8l-0.5,0.3c-0.7,0.4-1.5,0.8-2.4,1c-1,0.3-1.8,0.4-2.4,0.4c-2.3,0-4.1-0.6-5.1-1.9
c-1.1-1.3-1.6-3.4-1.6-6.5v-23.9L724.7,246.9z"/>
<path class="st1" d="M186.5,296.9H107v-79.5h79.4L186.5,296.9z"/>
<path class="st2" d="M274.2,296.9h-79.5v-79.5h79.5V296.9z"/>
<path class="st3" d="M186.5,384.6H107v-79.5h79.4L186.5,384.6z"/>
<path class="st4" d="M274.2,384.6h-79.5v-79.5h79.5V384.6z"/>
<path class="st0" d="M365.8,356.2l-9.9-28.4c-0.3-0.9-0.6-2.4-0.9-4.5h-0.2c-0.3,1.9-0.6,3.4-1,4.5l-9.8,28.4H365.8z M388.2,384
h-12.9l-6.4-18.1h-28l-6.1,18.1h-12.9l26.6-71.1h13.3L388.2,384z M432.2,338l-27.4,37h27.3v9h-43.4v-4.3l28-37.5h-25.3v-9h40.9
L432.2,338z M484.1,384h-11.5v-8h-0.2c-3.3,6.2-8.5,9.2-15.6,9.2c-12,0-18-7.2-18-21.6v-30.4h11.5v29.2c0,9.1,3.5,13.7,10.6,13.7
c3.4,0,6.2-1.3,8.4-3.8c2.2-2.5,3.3-5.8,3.3-9.9v-29.2h11.5L484.1,384z M526.5,344.1c-1.4-1.1-3.4-1.6-6-1.6
c-3.4,0-6.2,1.5-8.5,4.6c-2.3,3.1-3.4,7.3-3.4,12.5V384h-11.5v-50.8h11.5v10.5h0.2c1.1-3.6,2.9-6.4,5.2-8.4c2.3-2,4.9-3,7.8-3
c2.1,0,3.7,0.3,4.8,0.9V344.1z M563.6,353.6c0-4.1-1-7.4-2.9-9.6c-1.9-2.3-4.6-3.4-7.9-3.4c-3.3,0-6.1,1.2-8.4,3.6
c-2.3,2.4-3.7,5.6-4.2,9.5L563.6,353.6z M574.7,361.7h-34.6c0.1,4.7,1.6,8.3,4.3,10.9c2.8,2.5,6.6,3.8,11.4,3.8
c5.4,0,10.4-1.6,14.9-4.9v9.3c-4.6,2.9-10.7,4.4-18.3,4.4c-7.5,0-13.3-2.3-17.6-6.9c-4.2-4.6-6.4-11.1-6.4-19.5
c0-7.9,2.3-14.3,7-19.3c4.7-5,10.5-7.5,17.4-7.5c6.9,0,12.3,2.2,16.1,6.7c3.8,4.5,5.7,10.7,5.7,18.6V361.7z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -19,6 +19,7 @@ import BigAnimal from '../../img/biganimal.svg?svgr';
import Azure from '../../img/azure.svg?svgr';
import SQLFileSvg from '../../img/sql_file.svg?svgr';
import MagicSvg from '../../img/magic.svg?svgr';
import MsAzure from '../../img/ms_azure.svg?svgr';
export default function ExternalIcon({Icon, ...props}) {
return <Icon className={'MuiSvgIcon-root'} {...props} />;
@ -84,3 +85,6 @@ SQLFileIcon.propTypes = {style: PropTypes.object};
export const MagicIcon = ({style})=><ExternalIcon Icon={MagicSvg} style={{height: '1rem', ...style}} data-label="MagicIcon" />;
MagicIcon.propTypes = {style: PropTypes.object};
export const MSAzureIcon = ({style})=><ExternalIcon Icon={MsAzure} style={{height: '6rem', width: '7rem', ...style}} data-label="MSAzureIcon" />;
MSAzureIcon.propTypes = {style: PropTypes.object};

View File

@ -139,8 +139,12 @@ function Wizard({ stepList, onStepChange, onSave, className, ...props }) {
const handleNext = () => {
// beforeNext should always return a promise
if(props.beforeNext) {
props.beforeNext(activeStep).then(()=>{
setActiveStep((prevActiveStep) => prevActiveStep + 1);
props.beforeNext(activeStep).then((skipCurrentStep=false)=>{
if (skipCurrentStep) {
setActiveStep((prevActiveStep) => prevActiveStep + 2);
} else {
setActiveStep((prevActiveStep) => prevActiveStep + 1);
}
}).catch(()=>{/*This is intentional (SonarQube)*/});
} else {
setActiveStep((prevActiveStep) => prevActiveStep + 1);
@ -150,8 +154,12 @@ function Wizard({ stepList, onStepChange, onSave, className, ...props }) {
const handleBack = () => {
// beforeBack should always return a promise
if(props.beforeBack) {
props.beforeBack(activeStep).then(()=>{
setActiveStep((prevActiveStep) => prevActiveStep - 1 < 0 ? prevActiveStep : prevActiveStep - 1);
props.beforeBack(activeStep).then((skipCurrentStep=false)=>{
if (skipCurrentStep) {
setActiveStep((prevActiveStep) => prevActiveStep - 1 < 0 ? prevActiveStep : prevActiveStep - 2);
} else {
setActiveStep((prevActiveStep) => prevActiveStep - 1 < 0 ? prevActiveStep : prevActiveStep - 1);
}
}).catch(()=>{/*This is intentional (SonarQube)*/});
} else {
setActiveStep((prevActiveStep) => prevActiveStep - 1 < 0 ? prevActiveStep : prevActiveStep - 1);