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 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. 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 .. image:: images/cloud_biganimal_instance.png
:alt: Cloud Deployment Provider :alt: Cloud Deployment Provider
:align: center :align: center
@ -48,8 +60,11 @@ details.
* Use the *Volume type* field to select the instance storage type. * 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 .. 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 *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 .. image:: images/cloud_biganimal_review.png
:alt: Cloud Deployment Provider :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', parser_create_instance.add_argument('--volume-properties',
required=True, required=True,
help='storage properties') 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, parser_create_instance.add_argument('--private-network', required=True,
help='Private or Public Network') help='Private or Public Network')
parser_create_instance.add_argument('--public-ip', default='', parser_create_instance.add_argument('--public-ip', default='',
@ -76,6 +82,12 @@ class BigAnimalProvider(AbsProvider):
parser_create_instance.add_argument('--nodes', parser_create_instance.add_argument('--nodes',
required=True, required=True,
help='No of Nodes') 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): def cmd_create_instance(self, args):
""" Create a biganimal cluster """ """ Create a biganimal cluster """
@ -105,12 +117,14 @@ class BigAnimalProvider(AbsProvider):
'pgType': {'pgTypeId': args.db_type}, 'pgType': {'pgTypeId': args.db_type},
'pgVersion': {'pgVersionId': args.db_version}, 'pgVersion': {'pgVersionId': args.db_version},
'privateNetworking': private_network, 'privateNetworking': private_network,
'provider': {'cloudProviderId': 'azure'}, 'provider': {'cloudProviderId': args.cloud_provider},
'region': {'regionId': args.region}, 'region': {'regionId': args.region},
'replicas': 1, 'replicas': int(args.replicas),
'storage': { 'storage': {
'volumePropertiesId': args.volume_properties, 'volumePropertiesId': args.volume_properties,
'volumeTypeId': args.volume_type 'volumeTypeId': args.volume_type,
'iops': args.volume_IOPS,
'size': args.volume_size + ' Gi'
}, },
'clusterArchitecture': { 'clusterArchitecture': {
'clusterArchitectureId': args.cluster_arch, 'clusterArchitectureId': args.cluster_arch,

View File

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

View File

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

View File

@ -10,7 +10,7 @@
import React from 'react'; import React from 'react';
import pgAdmin from 'sources/pgadmin'; import pgAdmin from 'sources/pgadmin';
import { getNodeAjaxOptions, getNodeListById } from 'pgbrowser/node_ajax'; 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 SchemaView from '../../../../static/js/SchemaView';
import url_for from 'sources/url_for'; import url_for from 'sources/url_for';
import getApiInstance from '../../../../static/js/api_instance'; 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 React from 'react';
import pgAdmin from 'sources/pgadmin'; import pgAdmin from 'sources/pgadmin';
import { getNodeAjaxOptions, getNodeListById } from 'pgbrowser/node_ajax'; 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 SchemaView from '../../../../static/js/SchemaView';
import url_for from 'sources/url_for'; import url_for from 'sources/url_for';
import getApiInstance from '../../../../static/js/api_instance'; import getApiInstance from '../../../../static/js/api_instance';
import { isEmptyString } from 'sources/validators'; import { isEmptyString } from 'sources/validators';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import gettext from 'sources/gettext'; 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(); 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 // BigAnimal Instance
export function BigAnimalInstance(props) { export function BigAnimalInstance(props) {
const [bigAnimalInstance, setBigAnimalInstance] = React.useState(); const [bigAnimalInstance, setBigAnimalInstance] = React.useState();
React.useMemo(() => { React.useMemo(() => {
const bigAnimalSchema = new BigAnimalClusterSchema({ 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, useCache:false,
cacheNode: 'server', cacheNode: 'server',
customGenerateUrl: ()=>{ 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 []; if (isEmptyString(region_id)) return [];
return getNodeAjaxOptions('biganimal_instance_types', pgAdmin.Browser.Nodes['server'], return getNodeAjaxOptions('biganimal_instance_types', pgAdmin.Browser.Nodes['server'],
props.nodeInfo, props.nodeData, { props.nodeInfo, props.nodeData, {
useCache:false, useCache:false,
cacheNode: 'server', cacheNode: 'server',
customGenerateUrl: ()=>{ 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 []; if (isEmptyString(region_id)) return [];
return getNodeAjaxOptions('biganimal_volume_types', pgAdmin.Browser.Nodes['server'], return getNodeAjaxOptions('biganimal_volume_types', pgAdmin.Browser.Nodes['server'],
props.nodeInfo, props.nodeData, { props.nodeInfo, props.nodeData, {
useCache:false, useCache:false,
cacheNode: 'server', cacheNode: 'server',
customGenerateUrl: ()=>{ 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 []; if (isEmptyString(region_id) || isEmptyString(volume_type)) return [];
return getNodeAjaxOptions('biganimal_volume_properties', pgAdmin.Browser.Nodes['server'], return getNodeAjaxOptions('biganimal_volume_properties', pgAdmin.Browser.Nodes['server'],
props.nodeInfo, props.nodeData, { props.nodeInfo, props.nodeData, {
useCache:false, useCache:false,
cacheNode: 'server', cacheNode: 'server',
customGenerateUrl: ()=>{ 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, nodeInfo: props.nodeInfo,
nodeData: props.nodeData, nodeData: props.nodeData,
hostIP: props.hostIP, hostIP: props.hostIP,
provider: props.bigAnimalClusterTypeData.provider,
}); });
setBigAnimalInstance(bigAnimalSchema); setBigAnimalInstance(bigAnimalSchema);
}, [props.cloudProvider]); }, [props.cloudProvider]);
@ -92,10 +177,11 @@ BigAnimalInstance.propTypes = {
cloudProvider: PropTypes.string, cloudProvider: PropTypes.string,
setBigAnimalInstanceData: PropTypes.func, setBigAnimalInstanceData: PropTypes.func,
hostIP: PropTypes.string, hostIP: PropTypes.string,
bigAnimalClusterTypeData: PropTypes.object,
}; };
// BigAnimal Instance // BigAnimal Database
export function BigAnimalDatabase(props) { export function BigAnimalDatabase(props) {
const [bigAnimalDatabase, setBigAnimalDatabase] = React.useState(); const [bigAnimalDatabase, setBigAnimalDatabase] = React.useState();
@ -108,15 +194,18 @@ export function BigAnimalDatabase(props) {
return url_for('biganimal.db_types'); 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, useCache:false,
cacheNode: 'server', cacheNode: 'server',
customGenerateUrl: ()=>{ 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), 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); setBigAnimalDatabase(bigAnimalDBSchema);
}, [props.cloudProvider]); }, [props.cloudProvider]);
@ -137,6 +226,7 @@ BigAnimalDatabase.propTypes = {
nodeData: PropTypes.object, nodeData: PropTypes.object,
cloudProvider: PropTypes.string, cloudProvider: PropTypes.string,
setBigAnimalDatabaseData: PropTypes.func, setBigAnimalDatabaseData: PropTypes.func,
bigAnimalClusterTypeData: PropTypes.object
}; };
@ -162,7 +252,7 @@ function createData(name, value) {
return { name, value }; return { name, value };
} }
export function getBigAnimalSummary(cloud, bigAnimalInstanceData, bigAnimalDatabaseData) { export function getBigAnimalSummary(cloud, bigAnimalClusterTypeData, bigAnimalInstanceData, bigAnimalDatabaseData) {
const rows1 = [ const rows1 = [
createData(gettext('Cloud'), cloud), createData(gettext('Cloud'), cloud),
createData(gettext('Instance name'), bigAnimalInstanceData.name), createData(gettext('Instance name'), bigAnimalInstanceData.name),
@ -174,49 +264,72 @@ export function getBigAnimalSummary(cloud, bigAnimalInstanceData, bigAnimalDatab
let instance_size = bigAnimalInstanceData.instance_size.split('||'); let instance_size = bigAnimalInstanceData.instance_size.split('||');
const rows2 = [ 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 type'), bigAnimalInstanceData.instance_type),
createData(gettext('Instance series'), bigAnimalInstanceData.instance_series), createData(gettext('Instance series'), bigAnimalInstanceData.instance_series),
createData(gettext('Instance size'), instance_size[0]), createData(gettext('Instance size'), instance_size[0]),
]; ];
const rows3 = [ const rows4 = [
createData(gettext('Volume type'), bigAnimalInstanceData.volume_type), 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('Password'), 'xxxxxxx'),
createData(gettext('Database Type'), bigAnimalDatabaseData.database_type), createData(gettext('Database Type'), bigAnimalDatabaseData.database_type),
createData(gettext('Database Version'), bigAnimalDatabaseData.postgres_version), 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; let isError = false;
if (isEmptyString(cloudInstanceDetails.name) || if (isEmptyString(cloudTypeDetails.cluster_type) || isEmptyString(cloudTypeDetails.provider) ||
isEmptyString(cloudInstanceDetails.region) || isEmptyString(cloudInstanceDetails.instance_type) || (cloudTypeDetails.cluster_type == 'ha' && cloudTypeDetails.replicas == 0)
isEmptyString(cloudInstanceDetails.instance_series)|| isEmptyString(cloudInstanceDetails.instance_size) || ) {
isEmptyString(cloudInstanceDetails.volume_type)|| isEmptyString(cloudInstanceDetails.volume_properties)) {
isError = true; isError = true;
} }
return isError; 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; let isError = false;
if (isEmptyString(cloudDBDetails.password) || if (isEmptyString(cloudDBDetails.password) ||
isEmptyString(cloudDBDetails.database_type) || isEmptyString(cloudDBDetails.postgres_version)) { isEmptyString(cloudDBDetails.database_type) || isEmptyString(cloudDBDetails.postgres_version)) {
isError = true; isError = true;
} }
if(cloudDBDetails.high_availability && (isEmptyString(cloudDBDetails.replicas) || cloudDBDetails.replicas <= 0)) {
isError = true;
}
if (isEmptyString(cloudDBDetails.gid)) cloudDBDetails.gid = nodeInfo['server_group']._id; if (isEmptyString(cloudDBDetails.gid)) cloudDBDetails.gid = nodeInfo['server_group']._id;
return isError; 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']; summaryHeader = ['Cloud Details', 'Version and Instance Details', 'Storage Details', 'Database Details'];
if (props.cloudProvider == 'biganimal') { if (props.cloudProvider == 'biganimal') {
summary = getBigAnimalSummary(props.cloudProvider, props.instanceData, props.databaseData); summary = getBigAnimalSummary(props.cloudProvider, props.clusterTypeData, props.instanceData, props.databaseData);
summaryHeader[1] = 'Version Details'; summaryHeader = ['Cloud Details', 'Cluster Details' ,'Version Details', 'Storage Details', 'Database Details'];
} else if(props.cloudProvider == 'azure') { } else if(props.cloudProvider == 'azure') {
summaryHeader.push('Network Connectivity','Availability'); summaryHeader.push('Network Connectivity','Availability');
summary = getAzureSummary(props.cloudProvider, props.instanceData, props.databaseData); summary = getAzureSummary(props.cloudProvider, props.instanceData, props.databaseData);
@ -107,4 +107,5 @@ FinalSummary.propTypes = {
cloudProvider: PropTypes.string, cloudProvider: PropTypes.string,
instanceData: PropTypes.object, instanceData: PropTypes.object,
databaseData: 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 Azure from '../../img/azure.svg?svgr';
import SQLFileSvg from '../../img/sql_file.svg?svgr'; import SQLFileSvg from '../../img/sql_file.svg?svgr';
import MagicSvg from '../../img/magic.svg?svgr'; import MagicSvg from '../../img/magic.svg?svgr';
import MsAzure from '../../img/ms_azure.svg?svgr';
export default function ExternalIcon({Icon, ...props}) { export default function ExternalIcon({Icon, ...props}) {
return <Icon className={'MuiSvgIcon-root'} {...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" />; export const MagicIcon = ({style})=><ExternalIcon Icon={MagicSvg} style={{height: '1rem', ...style}} data-label="MagicIcon" />;
MagicIcon.propTypes = {style: PropTypes.object}; 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 = () => { const handleNext = () => {
// beforeNext should always return a promise // beforeNext should always return a promise
if(props.beforeNext) { if(props.beforeNext) {
props.beforeNext(activeStep).then(()=>{ props.beforeNext(activeStep).then((skipCurrentStep=false)=>{
if (skipCurrentStep) {
setActiveStep((prevActiveStep) => prevActiveStep + 2);
} else {
setActiveStep((prevActiveStep) => prevActiveStep + 1); setActiveStep((prevActiveStep) => prevActiveStep + 1);
}
}).catch(()=>{/*This is intentional (SonarQube)*/}); }).catch(()=>{/*This is intentional (SonarQube)*/});
} else { } else {
setActiveStep((prevActiveStep) => prevActiveStep + 1); setActiveStep((prevActiveStep) => prevActiveStep + 1);
@ -150,8 +154,12 @@ function Wizard({ stepList, onStepChange, onSave, className, ...props }) {
const handleBack = () => { const handleBack = () => {
// beforeBack should always return a promise // beforeBack should always return a promise
if(props.beforeBack) { if(props.beforeBack) {
props.beforeBack(activeStep).then(()=>{ 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); setActiveStep((prevActiveStep) => prevActiveStep - 1 < 0 ? prevActiveStep : prevActiveStep - 1);
}
}).catch(()=>{/*This is intentional (SonarQube)*/}); }).catch(()=>{/*This is intentional (SonarQube)*/});
} else { } else {
setActiveStep((prevActiveStep) => prevActiveStep - 1 < 0 ? prevActiveStep : prevActiveStep - 1); setActiveStep((prevActiveStep) => prevActiveStep - 1 < 0 ? prevActiveStep : prevActiveStep - 1);