mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Added capability to deploy PostgreSQL servers on Microsoft Azure. Fixes #7178
This commit is contained in:
committed by
Akshay Joshi
parent
99c7a50fd6
commit
7e1e068370
@@ -21,12 +21,12 @@ import PropTypes from 'prop-types';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import {ToggleButtons, FinalSummary} from './cloud_components';
|
||||
import { PrimaryButton } from '../../../../static/js/components/Buttons';
|
||||
import {AwsCredentials, AwsInstanceDetails, AwsDatabaseDetails, validateCloudStep1,
|
||||
validateCloudStep2, validateCloudStep3} from './aws';
|
||||
import {BigAnimalInstance, BigAnimalDatabase, validateBigAnimal,
|
||||
validateBigAnimalStep2, validateBigAnimalStep3} from './biganimal';
|
||||
import {AwsCredentials, AwsInstanceDetails, AwsDatabaseDetails, validateCloudStep1, validateCloudStep2, validateCloudStep3} from './aws';
|
||||
import {BigAnimalInstance, BigAnimalDatabase, validateBigAnimal,validateBigAnimalStep2, validateBigAnimalStep3} from './biganimal';
|
||||
import { isEmptyString } from 'sources/validators';
|
||||
import { AWSIcon, BigAnimalIcon } from '../../../../static/js/components/ExternalIcon';
|
||||
import { AWSIcon, BigAnimalIcon, AzureIcon } from '../../../../static/js/components/ExternalIcon';
|
||||
import {AzureCredentials, AzureInstanceDetails, AzureDatabaseDetails, checkClusternameAvailbility, validateAzureStep2, validateAzureStep3} from './azure';
|
||||
import EventBus from '../../../../static/js/helpers/EventBus';
|
||||
|
||||
const useStyles = makeStyles(() =>
|
||||
({
|
||||
@@ -53,12 +53,20 @@ const useStyles = makeStyles(() =>
|
||||
boxText: {
|
||||
paddingBottom: '5px'
|
||||
},
|
||||
authButton: {
|
||||
marginLeft: '12em'
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
export const CloudWizardEventsContext = React.createContext();
|
||||
|
||||
|
||||
export default function CloudWizard({ nodeInfo, nodeData }) {
|
||||
const classes = useStyles();
|
||||
|
||||
const eventBus = React.useRef(new EventBus());
|
||||
|
||||
var steps = [gettext('Cloud Provider'), gettext('Credentials'),
|
||||
gettext('Instance Specification'), gettext('Database Details'), gettext('Review')];
|
||||
const [currentStep, setCurrentStep] = React.useState('');
|
||||
@@ -74,13 +82,27 @@ export default function CloudWizard({ nodeInfo, nodeData }) {
|
||||
const [bigAnimalInstanceData, setBigAnimalInstanceData] = React.useState({});
|
||||
const [bigAnimalDatabaseData, setBigAnimalDatabaseData] = React.useState({});
|
||||
|
||||
|
||||
const [azureCredData, setAzureCredData] = React.useState({});
|
||||
const [azureInstanceData, setAzureInstanceData] = React.useState({});
|
||||
const [azureDatabaseData, setAzureDatabaseData] = React.useState({});
|
||||
|
||||
const axiosApi = getApiInstance();
|
||||
|
||||
const [verificationURI, setVerificationURI] = React.useState('');
|
||||
const [verificationCode, setVerificationCode] = React.useState('');
|
||||
|
||||
React.useEffect(()=>{
|
||||
eventBus.current.registerListener('SET_ERROR_MESSAGE_FOR_CLOUD_WIZARD', (msg) => {
|
||||
setErrMsg(msg);
|
||||
});
|
||||
}, []);
|
||||
|
||||
React.useEffect(()=>{
|
||||
eventBus.current.registerListener('SET_CRED_VERIFICATION_INITIATED', (initiated) => {
|
||||
setVerificationIntiated(initiated);
|
||||
});
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
let _url = url_for('cloud.get_host_ip') ;
|
||||
axiosApi.get(_url)
|
||||
@@ -110,7 +132,16 @@ export default function CloudWizard({ nodeInfo, nodeData }) {
|
||||
instance_details:cloudInstanceDetails,
|
||||
db_details: cloudDBDetails
|
||||
};
|
||||
} else {
|
||||
} else if(cloudProvider == 'azure'){
|
||||
post_data = {
|
||||
gid: nodeInfo.server_group._id,
|
||||
secret: azureCredData,
|
||||
cloud: cloudProvider,
|
||||
instance_details:azureInstanceData,
|
||||
db_details: azureDatabaseData
|
||||
};
|
||||
|
||||
}else {
|
||||
post_data = {
|
||||
gid: nodeInfo.server_group._id,
|
||||
cloud: cloudProvider,
|
||||
@@ -170,6 +201,24 @@ export default function CloudWizard({ nodeInfo, nodeData }) {
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'azure':
|
||||
switch (currentStep) {
|
||||
case 0:
|
||||
setCloudSelection('azure');
|
||||
break;
|
||||
case 1:
|
||||
isError = !verificationIntiated;
|
||||
break;
|
||||
case 2:
|
||||
isError = validateAzureStep2(azureInstanceData);
|
||||
break;
|
||||
case 3:
|
||||
isError = validateAzureStep3(azureDatabaseData, nodeInfo);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return isError;
|
||||
};
|
||||
@@ -211,8 +260,23 @@ export default function CloudWizard({ nodeInfo, nodeData }) {
|
||||
setErrMsg([MESSAGE_TYPE.ERROR, gettext(error)]);
|
||||
reject();
|
||||
});
|
||||
} else if(activeStep == 2 && cloudProvider == 'azure'){
|
||||
setErrMsg([MESSAGE_TYPE.INFO, 'Checking cluster name availability...']);
|
||||
checkClusternameAvailbility(azureInstanceData.name)
|
||||
.then((res)=>{
|
||||
if (res.data && res.data.success == 0 ) {
|
||||
setErrMsg([MESSAGE_TYPE.ERROR, gettext('Specified cluster name is already used.')]);
|
||||
}else{
|
||||
setErrMsg(['', '']);
|
||||
}
|
||||
resolve();
|
||||
}).catch((error)=>{
|
||||
setErrMsg([MESSAGE_TYPE.ERROR, gettext(error)]);
|
||||
reject();
|
||||
});
|
||||
}
|
||||
else {
|
||||
setErrMsg(['', '']);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
@@ -262,96 +326,120 @@ export default function CloudWizard({ nodeInfo, nodeData }) {
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Wizard
|
||||
title={gettext('Deploy Cloud Instance')}
|
||||
stepList={steps}
|
||||
disableNextStep={disableNextCheck}
|
||||
onStepChange={wizardStepChange}
|
||||
onSave={onSave}
|
||||
onHelp={onDialogHelp}
|
||||
beforeNext={onBeforeNext}>
|
||||
<WizardStep stepId={0}>
|
||||
<Box className={classes.messageBox}>
|
||||
<Box className={classes.messagePadding}>{gettext('Select any option to deploy on cloud.')}</Box>
|
||||
</Box>
|
||||
<Box className={classes.messageBox}>
|
||||
<ToggleButtons cloudProvider={cloudProvider} setCloudProvider={setCloudProvider}
|
||||
options={[{label: 'Amazon RDS', value: 'rds', icon: <AWSIcon className={classes.icon} />}, {label: 'EDB BigAnimal', value: 'biganimal', icon: <BigAnimalIcon className={classes.icon} />}]}
|
||||
></ToggleButtons>
|
||||
</Box>
|
||||
<Box className={classes.messageBox}>
|
||||
<Box className={classes.messagePadding}>{gettext('More cloud providers are coming soon...')}</Box>
|
||||
</Box>
|
||||
<FormFooterMessage type={errMsg[0]} message={errMsg[1]} onClose={onErrClose} />
|
||||
</WizardStep>
|
||||
<WizardStep stepId={1} >
|
||||
<Box className={classes.buttonMarginEDB}>
|
||||
{cloudProvider == 'biganimal' && <Box className={classes.messageBox}>
|
||||
<Box>{gettext('The verification code to authenticate the pgAdmin to EDB BigAnimal is: ')} <strong>{verificationCode}</strong>
|
||||
<br/>{gettext('By clicking the below button, you will be redirected to the EDB BigAnimal authentication page in a new tab.')}
|
||||
</Box>
|
||||
</Box>}
|
||||
{cloudProvider == 'biganimal' && <PrimaryButton onClick={authenticateBigAnimal} disabled={verificationIntiated ? true: false}>
|
||||
{gettext('Click here to authenticate yourself to EDB BigAnimal')}
|
||||
</PrimaryButton>}
|
||||
{cloudProvider == 'biganimal' && <Box className={classes.messageBox}>
|
||||
<Box ></Box>
|
||||
</Box>}
|
||||
</Box>
|
||||
{cloudProvider == 'rds' && <AwsCredentials cloudProvider={cloudProvider} nodeInfo={nodeInfo} nodeData={nodeData} setCloudDBCred={setCloudDBCred}/>}
|
||||
<FormFooterMessage type={errMsg[0]} message={errMsg[1]} onClose={onErrClose} />
|
||||
</WizardStep>
|
||||
<WizardStep stepId={2} >
|
||||
{cloudProvider == 'rds' && callRDSAPI == 2 && <AwsInstanceDetails
|
||||
cloudProvider={cloudProvider}
|
||||
nodeInfo={nodeInfo}
|
||||
nodeData={nodeData}
|
||||
setCloudInstanceDetails={setCloudInstanceDetails}
|
||||
hostIP={hostIP} /> }
|
||||
{cloudProvider == 'biganimal' && callRDSAPI == 2 && <BigAnimalInstance
|
||||
cloudProvider={cloudProvider}
|
||||
nodeInfo={nodeInfo}
|
||||
nodeData={nodeData}
|
||||
setBigAnimalInstanceData={setBigAnimalInstanceData}
|
||||
hostIP={hostIP}
|
||||
/> }
|
||||
</WizardStep>
|
||||
<WizardStep stepId={3} >
|
||||
{cloudProvider == 'rds' && <AwsDatabaseDetails
|
||||
cloudProvider={cloudProvider}
|
||||
nodeInfo={nodeInfo}
|
||||
nodeData={nodeData}
|
||||
setCloudDBDetails={setCloudDBDetails}
|
||||
/>
|
||||
}
|
||||
{cloudProvider == 'biganimal' && callRDSAPI == 3 && <BigAnimalDatabase
|
||||
cloudProvider={cloudProvider}
|
||||
nodeInfo={nodeInfo}
|
||||
nodeData={nodeData}
|
||||
setBigAnimalDatabaseData={setBigAnimalDatabaseData}
|
||||
/>
|
||||
}
|
||||
</WizardStep>
|
||||
<WizardStep stepId={4} >
|
||||
<Box className={classes.boxText}>{gettext('Please review the details before creating the cloud instance.')}</Box>
|
||||
<Paper variant="outlined" elevation={0} className={classes.summaryContainer}>
|
||||
{cloudProvider == 'rds' && callRDSAPI == 4 && <FinalSummary
|
||||
<CloudWizardEventsContext.Provider value={eventBus.current}>
|
||||
<>
|
||||
<Wizard
|
||||
title={gettext('Deploy Cloud Instance')}
|
||||
stepList={steps}
|
||||
disableNextStep={disableNextCheck}
|
||||
onStepChange={wizardStepChange}
|
||||
onSave={onSave}
|
||||
onHelp={onDialogHelp}
|
||||
beforeNext={onBeforeNext}>
|
||||
<WizardStep stepId={0}>
|
||||
<Box className={classes.messageBox}>
|
||||
<Box className={classes.messagePadding}>{gettext('Select a cloud provider.')}</Box>
|
||||
</Box>
|
||||
<Box className={classes.messageBox}>
|
||||
<ToggleButtons cloudProvider={cloudProvider} setCloudProvider={setCloudProvider}
|
||||
options={[{label: 'Amazon RDS', value: 'rds', icon: <AWSIcon className={classes.icon} />}, {label: 'EDB BigAnimal', value: 'biganimal', icon: <BigAnimalIcon className={classes.icon} />}, {'label': 'Azure PostgreSQL', value: 'azure', icon: <AzureIcon className={classes.icon} /> }]}
|
||||
></ToggleButtons>
|
||||
</Box>
|
||||
<FormFooterMessage type={errMsg[0]} message={errMsg[1]} onClose={onErrClose} />
|
||||
</WizardStep>
|
||||
<WizardStep stepId={1} >
|
||||
<Box className={classes.buttonMarginEDB}>
|
||||
{cloudProvider == 'biganimal' && <Box className={classes.messageBox}>
|
||||
<Box>{gettext('The verification code to authenticate the pgAdmin to EDB BigAnimal is: ')} <strong>{verificationCode}</strong>
|
||||
<br/>{gettext('By clicking the below button, you will be redirected to the EDB BigAnimal authentication page in a new tab.')}
|
||||
</Box>
|
||||
</Box>}
|
||||
{cloudProvider == 'biganimal' && <PrimaryButton onClick={authenticateBigAnimal} disabled={verificationIntiated ? true: false}>
|
||||
{gettext('Click here to authenticate yourself to EDB BigAnimal')}
|
||||
</PrimaryButton>}
|
||||
{cloudProvider == 'biganimal' && <Box className={classes.messageBox}>
|
||||
<Box ></Box>
|
||||
</Box>}
|
||||
</Box>
|
||||
{cloudProvider == 'rds' && <AwsCredentials cloudProvider={cloudProvider} nodeInfo={nodeInfo} nodeData={nodeData} setCloudDBCred={setCloudDBCred}/>}
|
||||
<Box>
|
||||
{cloudProvider == 'azure' && <AzureCredentials cloudProvider={cloudProvider} nodeInfo={nodeInfo} nodeData={nodeData} setAzureCredData={setAzureCredData}/>}
|
||||
</Box>
|
||||
<FormFooterMessage type={errMsg[0]} message={errMsg[1]} onClose={onErrClose} />
|
||||
</WizardStep>
|
||||
<WizardStep stepId={2} >
|
||||
{cloudProvider == 'rds' && callRDSAPI == 2 && <AwsInstanceDetails
|
||||
cloudProvider={cloudProvider}
|
||||
instanceData={cloudInstanceDetails}
|
||||
databaseData={cloudDBDetails}
|
||||
nodeInfo={nodeInfo}
|
||||
nodeData={nodeData}
|
||||
setCloudInstanceDetails={setCloudInstanceDetails}
|
||||
hostIP={hostIP} /> }
|
||||
{cloudProvider == 'biganimal' && callRDSAPI == 2 && <BigAnimalInstance
|
||||
cloudProvider={cloudProvider}
|
||||
nodeInfo={nodeInfo}
|
||||
nodeData={nodeData}
|
||||
setBigAnimalInstanceData={setBigAnimalInstanceData}
|
||||
hostIP={hostIP}
|
||||
/> }
|
||||
{cloudProvider == 'azure' && callRDSAPI == 2 && <AzureInstanceDetails
|
||||
cloudProvider={cloudProvider}
|
||||
nodeInfo={nodeInfo}
|
||||
nodeData={nodeData}
|
||||
setAzureInstanceData={setAzureInstanceData}
|
||||
hostIP={hostIP}
|
||||
azureInstanceData = {azureInstanceData}
|
||||
/> }
|
||||
<FormFooterMessage type={errMsg[0]} message={errMsg[1]} onClose={onErrClose} />
|
||||
</WizardStep>
|
||||
<WizardStep stepId={3} >
|
||||
{cloudProvider == 'rds' && <AwsDatabaseDetails
|
||||
cloudProvider={cloudProvider}
|
||||
nodeInfo={nodeInfo}
|
||||
nodeData={nodeData}
|
||||
setCloudDBDetails={setCloudDBDetails}
|
||||
/>
|
||||
}
|
||||
{cloudProvider == 'biganimal' && callRDSAPI == 4 && <FinalSummary
|
||||
{cloudProvider == 'biganimal' && callRDSAPI == 3 && <BigAnimalDatabase
|
||||
cloudProvider={cloudProvider}
|
||||
instanceData={bigAnimalInstanceData}
|
||||
databaseData={bigAnimalDatabaseData}
|
||||
nodeInfo={nodeInfo}
|
||||
nodeData={nodeData}
|
||||
setBigAnimalDatabaseData={setBigAnimalDatabaseData}
|
||||
/>
|
||||
}
|
||||
</Paper>
|
||||
</WizardStep>
|
||||
</Wizard>
|
||||
</>
|
||||
{cloudProvider == 'azure' && <AzureDatabaseDetails
|
||||
cloudProvider={cloudProvider}
|
||||
nodeInfo={nodeInfo}
|
||||
nodeData={nodeData}
|
||||
setAzureDatabaseData={setAzureDatabaseData}
|
||||
/>
|
||||
}
|
||||
</WizardStep>
|
||||
<WizardStep stepId={4} >
|
||||
<Box className={classes.boxText}>{gettext('Please review the details before creating the cloud instance.')}</Box>
|
||||
<Paper variant="outlined" elevation={0} className={classes.summaryContainer}>
|
||||
{cloudProvider == 'rds' && callRDSAPI == 4 && <FinalSummary
|
||||
cloudProvider={cloudProvider}
|
||||
instanceData={cloudInstanceDetails}
|
||||
databaseData={cloudDBDetails}
|
||||
/>
|
||||
}
|
||||
{cloudProvider == 'biganimal' && callRDSAPI == 4 && <FinalSummary
|
||||
cloudProvider={cloudProvider}
|
||||
instanceData={bigAnimalInstanceData}
|
||||
databaseData={bigAnimalDatabaseData}
|
||||
/>
|
||||
}
|
||||
{cloudProvider == 'azure' && callRDSAPI == 4 && <FinalSummary
|
||||
cloudProvider={cloudProvider}
|
||||
instanceData={azureInstanceData}
|
||||
databaseData={azureDatabaseData}
|
||||
/>
|
||||
}
|
||||
</Paper>
|
||||
</WizardStep>
|
||||
</Wizard>
|
||||
</>
|
||||
</CloudWizardEventsContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -359,5 +447,3 @@ CloudWizard.propTypes = {
|
||||
nodeInfo: PropTypes.object,
|
||||
nodeData: PropTypes.object,
|
||||
};
|
||||
|
||||
|
||||
|
||||
298
web/pgadmin/misc/cloud/static/js/azure.js
Normal file
298
web/pgadmin/misc/cloud/static/js/azure.js
Normal file
@@ -0,0 +1,298 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
import React from 'react';
|
||||
import {AzureCredSchema, AzureClusterSchema, AzureDatabaseSchema} from './azure_schema.ui';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import { getNodeAjaxOptions, getNodeListById } from 'pgbrowser/node_ajax';
|
||||
import SchemaView from '../../../../static/js/SchemaView';
|
||||
import url_for from 'sources/url_for';
|
||||
import { isEmptyString } from 'sources/validators';
|
||||
import PropTypes from 'prop-types';
|
||||
import getApiInstance from '../../../../static/js/api_instance';
|
||||
import { CloudWizardEventsContext } from './CloudWizard';
|
||||
import {MESSAGE_TYPE } from '../../../../static/js/components/FormComponents';
|
||||
import gettext from 'sources/gettext';
|
||||
|
||||
// Azure credentials
|
||||
export function AzureCredentials(props) {
|
||||
const [cloudDBCredInstance, setCloudDBCredInstance] = React.useState();
|
||||
|
||||
var _eventBus = React.useContext(CloudWizardEventsContext);
|
||||
React.useMemo(() => {
|
||||
const azureCloudDBCredSchema = new AzureCredSchema({
|
||||
authenticateAzure:(auth_type, azure_tenant_id) => {
|
||||
let loading_icon_url = url_for(
|
||||
'static', { 'filename': 'img/loading.gif'}
|
||||
);
|
||||
const axiosApi = getApiInstance();
|
||||
_eventBus.fireEvent('SET_ERROR_MESSAGE_FOR_CLOUD_WIZARD', [MESSAGE_TYPE.INFO, 'Microsoft Azure authentication process is in progress..<img src="' + loading_icon_url + '" alt="' + gettext('Loading...') + '">']);
|
||||
let _url = url_for('azure.verify_credentials');
|
||||
const post_data = {
|
||||
cloud: 'azure',
|
||||
secret: {'auth_type':auth_type, 'azure_tenant_id':azure_tenant_id}
|
||||
};
|
||||
return new Promise((resolve, reject)=>{axiosApi.post(_url, post_data)
|
||||
.then((res) => {
|
||||
if (res.data && res.data.success == 1 ) {
|
||||
_eventBus.fireEvent('SET_ERROR_MESSAGE_FOR_CLOUD_WIZARD',[MESSAGE_TYPE.SUCCESS, gettext('Authentication completed successfully. Click the Next button to proceed.')]);
|
||||
_eventBus.fireEvent('SET_CRED_VERIFICATION_INITIATED',true);
|
||||
resolve(true);
|
||||
}
|
||||
else if (res.data && res.data.success == 0) {
|
||||
_eventBus.fireEvent('SET_ERROR_MESSAGE_FOR_CLOUD_WIZARD',[MESSAGE_TYPE.ERROR, res.data.errormsg]);
|
||||
_eventBus.fireEvent('SET_CRED_VERIFICATION_INITIATED',false);
|
||||
resolve(false);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
_eventBus.fireEvent('SET_ERROR_MESSAGE_FOR_CLOUD_WIZARD',[MESSAGE_TYPE.ERROR, gettext(`Error while verification Microsoft Azure: ${error.response.data.errormsg}`)]);
|
||||
reject(false);
|
||||
});});
|
||||
}
|
||||
});
|
||||
setCloudDBCredInstance(azureCloudDBCredSchema);
|
||||
}, [props.cloudProvider]);
|
||||
|
||||
return <SchemaView
|
||||
formType={'dialog'}
|
||||
getInitData={() => { /*This is intentional (SonarQube)*/ }}
|
||||
viewHelperProps={{ mode: 'create' }}
|
||||
schema={cloudDBCredInstance}
|
||||
showFooter={false}
|
||||
isTabView={false}
|
||||
onDataChange={(isChanged, changedData) => {
|
||||
props.setAzureCredData(changedData);
|
||||
}}
|
||||
/>;
|
||||
}
|
||||
AzureCredentials.propTypes = {
|
||||
nodeInfo: PropTypes.object,
|
||||
nodeData: PropTypes.object,
|
||||
cloudProvider: PropTypes.string,
|
||||
setAzureCredData: PropTypes.func
|
||||
};
|
||||
|
||||
|
||||
// Azure Instance
|
||||
export function AzureInstanceDetails(props) {
|
||||
const [azureInstanceSchema, setAzureInstanceSchema] = React.useState();
|
||||
|
||||
React.useMemo(() => {
|
||||
const AzureSchema = new AzureClusterSchema({
|
||||
subscriptions: () => getNodeAjaxOptions('get_subscriptions', {}, {}, {},{
|
||||
useCache:false,
|
||||
cacheNode: 'server',
|
||||
customGenerateUrl: ()=>{
|
||||
return url_for('azure.subscriptions');
|
||||
}
|
||||
}),
|
||||
resourceGroups: (subscription)=>getNodeAjaxOptions('ge_resource_groups', pgAdmin.Browser.Nodes['server'], props.nodeInfo, props.nodeData, {
|
||||
useCache:false,
|
||||
cacheNode: 'server',
|
||||
customGenerateUrl: ()=>{
|
||||
return url_for('azure.resource_groups', {'subscription_id': subscription});
|
||||
}
|
||||
}),
|
||||
regions: (subscription)=>getNodeAjaxOptions('get_regions', pgAdmin.Browser.Nodes['server'], props.nodeInfo, props.nodeData,{
|
||||
useCache:false,
|
||||
cacheNode: 'server',
|
||||
customGenerateUrl: ()=>{
|
||||
return url_for('azure.regions', {'subscription_id': subscription});
|
||||
}
|
||||
}),
|
||||
availabilityZones: (region)=>getNodeAjaxOptions('get_availability_zones', pgAdmin.Browser.Nodes['server'], props.nodeInfo, props.nodeData, {
|
||||
useCache:false,
|
||||
cacheNode: 'server',
|
||||
customGenerateUrl: ()=>{
|
||||
return url_for('azure.availability_zones', {'region_name': region});
|
||||
}
|
||||
}),
|
||||
versionOptions: (availabilityZone)=>getNodeAjaxOptions('get_db_versions', pgAdmin.Browser.Nodes['server'], props.nodeInfo, props.nodeData, {
|
||||
useCache:false,
|
||||
cacheNode: 'server',
|
||||
customGenerateUrl: ()=>{
|
||||
return url_for('azure.db_versions', {'availability_zone': availabilityZone});
|
||||
}
|
||||
}),
|
||||
instanceOptions: (dbVersion, availabilityZone)=>{
|
||||
if (isEmptyString(dbVersion) || isEmptyString(availabilityZone) ) return [];
|
||||
return getNodeAjaxOptions('get_instance_types', pgAdmin.Browser.Nodes['server'], props.nodeInfo, props.nodeData, {
|
||||
useCache:false,
|
||||
cacheNode: 'server',
|
||||
customGenerateUrl: ()=>{
|
||||
return url_for('azure.instance_types', {'availability_zone':availabilityZone, 'db_version': dbVersion});
|
||||
}
|
||||
});},
|
||||
storageOptions: (dbVersion, availabilityZone)=>{
|
||||
if (isEmptyString(dbVersion) || isEmptyString(availabilityZone) ) return [];
|
||||
return getNodeAjaxOptions('get_instance_types', pgAdmin.Browser.Nodes['server'], props.nodeInfo, props.nodeData, {
|
||||
useCache:false,
|
||||
cacheNode: 'server',
|
||||
customGenerateUrl: ()=>{
|
||||
return url_for('azure.storage_types', {'availability_zone':availabilityZone, 'db_version': dbVersion});
|
||||
}
|
||||
});
|
||||
},
|
||||
zoneRedundantHaSupported: (region)=>getNodeAjaxOptions('is_zone_redundant_ha_supported', pgAdmin.Browser.Nodes['server'], props.nodeInfo, props.nodeData,{
|
||||
useCache:false,
|
||||
cacheNode: 'server',
|
||||
customGenerateUrl: ()=>{
|
||||
return url_for('azure.zone_redundant_ha_supported', {'region_name': region});
|
||||
}
|
||||
}),
|
||||
}, {
|
||||
nodeInfo: props.nodeInfo,
|
||||
nodeData: props.nodeData,
|
||||
hostIP: props.hostIP,
|
||||
...props.azureInstanceData
|
||||
});
|
||||
setAzureInstanceSchema(AzureSchema);
|
||||
}, [props.cloudProvider]);
|
||||
|
||||
return <SchemaView
|
||||
formType={'dialog'}
|
||||
getInitData={() => { /*This is intentional (SonarQube)*/ }}
|
||||
viewHelperProps={{ mode: 'create' }}
|
||||
schema={azureInstanceSchema}
|
||||
showFooter={false}
|
||||
isTabView={false}
|
||||
onDataChange={(isChanged, changedData) => {
|
||||
props.setAzureInstanceData(changedData);
|
||||
}}
|
||||
/>;
|
||||
}
|
||||
AzureInstanceDetails.propTypes = {
|
||||
nodeInfo: PropTypes.object,
|
||||
nodeData: PropTypes.object,
|
||||
cloudProvider: PropTypes.string,
|
||||
setAzureInstanceData: PropTypes.func,
|
||||
hostIP: PropTypes.string,
|
||||
subscriptions: PropTypes.array,
|
||||
azureInstanceData: PropTypes.object
|
||||
};
|
||||
|
||||
|
||||
// Azure Database Details
|
||||
export function AzureDatabaseDetails(props) {
|
||||
const [azureDBInstance, setAzureDBInstance] = React.useState();
|
||||
|
||||
React.useMemo(() => {
|
||||
const azureDBSchema = new AzureDatabaseSchema({
|
||||
server_groups: ()=>getNodeListById(pgAdmin.Browser.Nodes['server_group'], props.nodeInfo, props.nodeData),
|
||||
},
|
||||
{
|
||||
gid: props.nodeInfo['server_group']._id,
|
||||
}
|
||||
);
|
||||
setAzureDBInstance(azureDBSchema);
|
||||
|
||||
}, [props.cloudProvider]);
|
||||
|
||||
return <SchemaView
|
||||
formType={'dialog'}
|
||||
getInitData={() => { /*This is intentional (SonarQube)*/ }}
|
||||
viewHelperProps={{ mode: 'create' }}
|
||||
schema={azureDBInstance}
|
||||
showFooter={false}
|
||||
isTabView={false}
|
||||
onDataChange={(isChanged, changedData) => {
|
||||
props.setAzureDatabaseData(changedData);
|
||||
}}
|
||||
/>;
|
||||
}
|
||||
AzureDatabaseDetails.propTypes = {
|
||||
nodeInfo: PropTypes.object,
|
||||
nodeData: PropTypes.object,
|
||||
cloudProvider: PropTypes.string,
|
||||
setAzureDatabaseData: PropTypes.func,
|
||||
};
|
||||
|
||||
|
||||
// Validation functions
|
||||
export function validateAzureStep2(cloudInstanceDetails) {
|
||||
let isError = false;
|
||||
if (isEmptyString(cloudInstanceDetails.name) ||
|
||||
isEmptyString(cloudInstanceDetails.db_version) || isEmptyString(cloudInstanceDetails.instance_type) ||
|
||||
isEmptyString(cloudInstanceDetails.region)|| isEmptyString(cloudInstanceDetails.storage_size) || isEmptyString(cloudInstanceDetails.public_ips)) {
|
||||
isError = true;
|
||||
}
|
||||
return isError;
|
||||
}
|
||||
|
||||
export function validateAzureStep3(cloudDBDetails, nodeInfo) {
|
||||
let isError = false;
|
||||
if (isEmptyString(cloudDBDetails.db_username) || isEmptyString(cloudDBDetails.db_password)) {
|
||||
isError = true;
|
||||
}
|
||||
if (isEmptyString(cloudDBDetails.gid)) cloudDBDetails.gid = nodeInfo['server_group']._id;
|
||||
return isError;
|
||||
}
|
||||
|
||||
|
||||
// Check cluster name avaiablity
|
||||
export function checkClusternameAvailbility(clusterName){
|
||||
return new Promise((resolve, reject)=>{
|
||||
let _url = url_for('azure.check_cluster_name_availability');
|
||||
const axiosApi = getApiInstance();
|
||||
axiosApi.get(_url, {
|
||||
params: {
|
||||
'name': clusterName,
|
||||
}
|
||||
}).then((res)=>{
|
||||
if (res.data) {
|
||||
resolve(res.data);
|
||||
}
|
||||
}).catch((error) => {
|
||||
reject(gettext(`Error while checking server name availability with Microsoft Azure: ${error.response.data.errormsg}`));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Summary creation
|
||||
function createData(name, value) {
|
||||
if (typeof(value) == 'boolean') {
|
||||
value = (value === true) ? 'True' : 'False';
|
||||
}
|
||||
return { name, value };
|
||||
}
|
||||
|
||||
// Summary section
|
||||
export function getAzureSummary(cloud, cloudInstanceDetails, cloudDBDetails) {
|
||||
const rows1 = [
|
||||
createData('Cloud', cloud),
|
||||
createData('Subscription', cloudInstanceDetails.subscription),
|
||||
createData('Resource group', cloudInstanceDetails.resource_group),
|
||||
createData('Region', cloudInstanceDetails.region),
|
||||
createData('Availability zone', cloudInstanceDetails.availability_zone),
|
||||
];
|
||||
|
||||
const rows2 = [
|
||||
createData('PostgreSQL version', cloudInstanceDetails.db_version),
|
||||
createData('Instance type', cloudInstanceDetails.instance_type),
|
||||
];
|
||||
|
||||
const rows3 = [
|
||||
createData('Allocated storage', cloudInstanceDetails.storage_size + ' GiB'),
|
||||
];
|
||||
|
||||
const rows4 = [
|
||||
createData('Username', cloudDBDetails.db_username),
|
||||
createData('Password', 'xxxxxxx'),
|
||||
];
|
||||
|
||||
const rows5 = [
|
||||
createData('Public IP', cloudInstanceDetails.public_ips),
|
||||
];
|
||||
|
||||
const rows6 = [
|
||||
createData('High availability', cloudInstanceDetails.high_availability),
|
||||
];
|
||||
|
||||
return [rows1, rows2, rows3, rows4, rows5, rows6];
|
||||
}
|
||||
683
web/pgadmin/misc/cloud/static/js/azure_schema.ui.js
Normal file
683
web/pgadmin/misc/cloud/static/js/azure_schema.ui.js
Normal file
@@ -0,0 +1,683 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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 { CloudWizardEventsContext } from './CloudWizard';
|
||||
import React from 'react';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
|
||||
class AzureCredSchema extends BaseUISchema {
|
||||
constructor(fieldOptions = {}, initValues = {}) {
|
||||
super({
|
||||
oid: null,
|
||||
auth_type: 'interactive_browser_credential',
|
||||
azure_tenant_id: '',
|
||||
azure_subscription_id: '',
|
||||
...initValues,
|
||||
});
|
||||
|
||||
this.fieldOptions = {
|
||||
...fieldOptions,
|
||||
};
|
||||
|
||||
this.eventBus = React.useContext(CloudWizardEventsContext);
|
||||
}
|
||||
|
||||
get idAttribute() {
|
||||
return 'oid';
|
||||
}
|
||||
|
||||
validate(state, setErrMsg) {
|
||||
let isError = false;
|
||||
if (state.auth_type == 'interactive_browser_credential' && state.azure_tenant_id == '') {
|
||||
isError = true;
|
||||
setErrMsg(
|
||||
'azure_tenant_id',
|
||||
gettext('Azure Tenant Id is required for Azure interactive authentication.')
|
||||
);
|
||||
}
|
||||
return isError;
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
let obj = this;
|
||||
return [
|
||||
{
|
||||
id: 'auth_type',
|
||||
label: gettext('Authenticate via'),
|
||||
type: 'toggle',
|
||||
mode: ['create'],
|
||||
noEmpty: true,
|
||||
options: [
|
||||
{
|
||||
label: gettext('Interactive Browser'),
|
||||
value: 'interactive_browser_credential',
|
||||
},
|
||||
{
|
||||
label: gettext('Azure CLI'),
|
||||
value: 'azure_cli_credential',
|
||||
},
|
||||
],
|
||||
disabled: pgAdmin.server_mode == 'True' ? true : false,
|
||||
helpMessage: gettext(
|
||||
'Azure CLI will use the currently logged in identity through the Azure CLI on the local machine. Interactive Browser will open a browser window to authenticate a user interactively.'
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'azure_tenant_id',
|
||||
label: gettext('Azure tenant id'),
|
||||
type: 'text',
|
||||
mode: ['create'],
|
||||
deps: ['auth_type'],
|
||||
helpMessage: gettext(
|
||||
'Enter the Azure tenant ID against which the user is authenticated.'
|
||||
),
|
||||
disabled: (state) => {
|
||||
return state.auth_type == 'interactive_browser_credential'
|
||||
? false
|
||||
: true;
|
||||
},
|
||||
depChange: (state) => {
|
||||
if (state.auth_type == 'azure_cli_credential') {
|
||||
state.azure_tenant_id = '';
|
||||
}
|
||||
this.eventBus.fireEvent('SET_CRED_VERIFICATION_INITIATED', false);
|
||||
this.eventBus.fireEvent('SET_ERROR_MESSAGE_FOR_CLOUD_WIZARD',['', '']);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'auth_btn',
|
||||
mode: ['create'],
|
||||
deps: ['auth_type', 'azure_tenant_id'],
|
||||
btnName: gettext('Click here to authenticate yourself to Microsoft Azure'),
|
||||
type: (state) => {
|
||||
return {
|
||||
type: 'button',
|
||||
onClick: () => {
|
||||
obj.fieldOptions.authenticateAzure(state.auth_type, state.azure_tenant_id).then((res)=>{state._disabled_auth_btn= res;});
|
||||
},
|
||||
};
|
||||
},
|
||||
helpMessage: gettext(
|
||||
'After clicking the button above you will be redirected to the Microsoft Azure authentication page in a new browser tab if the Interactive Browser option is selected.'
|
||||
),
|
||||
depChange: (state, source)=> {
|
||||
if(source[0] == 'auth_type' || source[0] == 'azure_tenant_id'){
|
||||
state._disabled_auth_btn = false;
|
||||
}
|
||||
},
|
||||
disabled: (state)=> {
|
||||
if(state.auth_type == 'interactive_browser_credential' && state.azure_tenant_id == ''){
|
||||
return true;
|
||||
}
|
||||
return state._disabled_auth_btn;
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class AzureProjectDetailsSchema extends BaseUISchema {
|
||||
constructor(fieldOptions = {}, initValues = {}) {
|
||||
super({
|
||||
oid: undefined,
|
||||
subscription: '',
|
||||
new_resource_group: false,
|
||||
resource_group: '',
|
||||
regions: '',
|
||||
availability_zones: '',
|
||||
high_availability: false,
|
||||
...initValues,
|
||||
});
|
||||
|
||||
this.fieldOptions = {
|
||||
...fieldOptions,
|
||||
};
|
||||
|
||||
this.initValues = initValues;
|
||||
}
|
||||
|
||||
get idAttribute() {
|
||||
return 'oid';
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
return [
|
||||
{
|
||||
id: 'subscription',
|
||||
label: gettext('Subscription'),
|
||||
mode: ['create'],
|
||||
type: () => {
|
||||
return {
|
||||
type: 'select',
|
||||
options: this.fieldOptions.subscriptions,
|
||||
controlProps: {
|
||||
allowClear: false,
|
||||
filter: (options) => {
|
||||
if (options.length == 0) return;
|
||||
let _options = [];
|
||||
options.forEach((option) => {
|
||||
_options.push({
|
||||
label:
|
||||
option.subscription_name + ' | ' + option.subscription_id,
|
||||
value: option.subscription_id,
|
||||
});
|
||||
});
|
||||
return _options;
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'resource_group',
|
||||
label: gettext('Resource group'),
|
||||
mode: ['create'],
|
||||
deps: ['subscription'],
|
||||
type: (state) => {
|
||||
return {
|
||||
type: 'select',
|
||||
options: state.subscription
|
||||
? () => this.fieldOptions.resourceGroups(state.subscription)
|
||||
: [],
|
||||
optionsReloadBasis: state.subscription,
|
||||
controlProps: {
|
||||
creatable: true,
|
||||
allowClear: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'region',
|
||||
label: gettext('Location'),
|
||||
mode: ['create'],
|
||||
deps: ['subscription'],
|
||||
type: (state) => {
|
||||
return {
|
||||
type: 'select',
|
||||
options: state.subscription
|
||||
? () => this.fieldOptions.regions(state.subscription)
|
||||
: [],
|
||||
optionsReloadBasis: state.subscription,
|
||||
allowClear: false,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'availability_zone',
|
||||
label: gettext('Availability zone'),
|
||||
deps: ['region'],
|
||||
allowClear: false,
|
||||
type: (state) => {
|
||||
return {
|
||||
type: 'select',
|
||||
options: state.region
|
||||
? () => this.fieldOptions.availabilityZones(state.region)
|
||||
: [],
|
||||
optionsReloadBasis: state.region,
|
||||
};
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class AzureInstanceSchema extends BaseUISchema {
|
||||
constructor(fieldOptions = {}, initValues = {}) {
|
||||
super({
|
||||
db_version: '',
|
||||
instance_type: '',
|
||||
storage_size: '',
|
||||
});
|
||||
|
||||
this.fieldOptions = {
|
||||
...fieldOptions,
|
||||
};
|
||||
|
||||
this.initValues = initValues;
|
||||
}
|
||||
|
||||
get idAttribute() {
|
||||
return 'oid';
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
return [
|
||||
{
|
||||
id: 'db_version',
|
||||
label: gettext('Database version'),
|
||||
deps: ['availability_zone'],
|
||||
type: (state) => {
|
||||
return {
|
||||
type: 'select',
|
||||
options: state.availability_zone
|
||||
? () => this.fieldOptions.versionOptions(state.availability_zone)
|
||||
: [],
|
||||
optionsReloadBasis: state.availability_zone,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'db_instance_class',
|
||||
label: gettext('Instance class'),
|
||||
type: 'select',
|
||||
options: [
|
||||
{
|
||||
label: gettext('Burstable (1-2 vCores) '),
|
||||
value: 'Burstable' },
|
||||
{
|
||||
label: gettext('General Purpose (2-64 vCores)'),
|
||||
value: 'GeneralPurpose',
|
||||
},
|
||||
{
|
||||
label: gettext('Memory Optimized (2-64 vCores)'),
|
||||
value: 'MemoryOptimized',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'instance_type',
|
||||
label: gettext('Instance type'),
|
||||
deps: ['db_version', 'db_instance_class'],
|
||||
depChange: (state, source)=>{
|
||||
if(source[0] == 'db_instance_class'){
|
||||
state.instance_type = undefined;
|
||||
}
|
||||
},
|
||||
type: (state) => {
|
||||
return {
|
||||
type: 'select',
|
||||
options: () => this.fieldOptions.instanceOptions(state.db_version,state.availability_zone),
|
||||
optionsReloadBasis: state.db_version + state.db_instance_class,
|
||||
controlProps: {
|
||||
allowClear: false,
|
||||
filter: (options) => {
|
||||
if (options.length == 0 || state.db_instance_class === undefined)
|
||||
return;
|
||||
let _options = [];
|
||||
options.forEach((option) => {
|
||||
if (option.type == state.db_instance_class) {
|
||||
_options.push({
|
||||
label: option.label,
|
||||
value: option.value,
|
||||
});
|
||||
}
|
||||
});
|
||||
return _options;
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'storage_size',
|
||||
label: gettext('Storage Size'),
|
||||
deps: ['db_version', 'db_instance_class'],
|
||||
type: (state) => {
|
||||
return {
|
||||
type: 'select',
|
||||
options: () => this.fieldOptions.storageOptions(state.db_version, state.availability_zone),
|
||||
optionsReloadBasis: state.db_version + (state.db_instance_class || 'Burstable'),
|
||||
controlProps: {
|
||||
allowClear: false,
|
||||
filter: (opts) => {
|
||||
if (opts.length == 0 || state.db_instance_class === undefined)
|
||||
return;
|
||||
let _options = [];
|
||||
opts.forEach((opt) => {
|
||||
if (opt.type == state.db_instance_class) {
|
||||
_options.push({
|
||||
label: opt.label,
|
||||
value: opt.value,
|
||||
});
|
||||
}
|
||||
});
|
||||
return _options;
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class AzureDatabaseSchema extends BaseUISchema {
|
||||
constructor(fieldOptions = {}, initValues = {}) {
|
||||
super({
|
||||
oid: undefined,
|
||||
gid: undefined,
|
||||
db_username: '',
|
||||
db_password: '',
|
||||
db_confirm_password: '',
|
||||
...initValues,
|
||||
});
|
||||
|
||||
this.fieldOptions = {
|
||||
...fieldOptions,
|
||||
};
|
||||
}
|
||||
|
||||
validateDbUserName(data, setErrMsg) {
|
||||
if (data.db_username.length < 1 && data.db_username.length > 63 && !/^[A-Za-z0-9]*$/.test(data.db_username)) {
|
||||
setErrMsg(
|
||||
'db_username',
|
||||
gettext('Admin username must be more than 1 character & less than 63 and must only contains characters and numbers.')
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
['azure_superuser', 'azure_pg_admin', 'admin', 'administrator', 'root', 'guest', 'public'].includes(data.db_username) ||
|
||||
data.db_username.startsWith('pg_')) {
|
||||
setErrMsg('db_username', gettext('Specified Admin username is not allowed'));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
validateDbPassword(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) && !/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[#$@!%&*?])[A-Za-z\d#$@!%&*?]{8,128}$/.test(data.db_confirm_password)) {
|
||||
setErrMsg(
|
||||
'db_confirm_password',
|
||||
gettext(
|
||||
'The password must be 8-128 characters long and must contain characters from three of the following categories - English uppercase letters, English lowercase letters, numbers (0-9), and non-alphanumeric characters (!, $, #, %, etc.)'
|
||||
)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
validate(data, setErrMsg) {
|
||||
if (this.validateDbUserName(data, setErrMsg) || this.validateDbPassword(data, setErrMsg)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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_username',
|
||||
label: gettext('Admin username'),
|
||||
type: 'text',
|
||||
mode: ['create'],
|
||||
noEmpty: true,
|
||||
helpMessage: gettext(
|
||||
'The admin username must be 1-63 characters long and can only contain character, numbers and the underscore character. The username cannot be "azure_superuser", "azure_pg_admin", "admin", "administrator", "root", "guest", "public", or start with "pg_".'
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'db_password',
|
||||
label: gettext('Password'),
|
||||
type: 'password',
|
||||
mode: ['create'],
|
||||
noEmpty: true,
|
||||
helpMessage: gettext(
|
||||
'The password must be 8-128 characters long and must contain characters from three of the following categories - English uppercase letters, English lowercase letters, numbers (0-9), and non-alphanumeric characters (!, $, #, %, etc.), and cannot contain all or part of the login name'
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'db_confirm_password',
|
||||
label: gettext('Confirm password'),
|
||||
type: 'password',
|
||||
mode: ['create'],
|
||||
noEmpty: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class AzureNetworkSchema extends BaseUISchema {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
return [
|
||||
{
|
||||
id: 'public_ips',
|
||||
label: gettext('Public IP range'),
|
||||
type: 'text',
|
||||
mode: ['create'],
|
||||
helpMessage: gettext(
|
||||
'List of IP Addresses or range of IP Addresses (start IP Address - end IP address) from which inbound traffic should be accepted. Add multiple IP addresses/ranges separated with commas, for example: "192.168.0.50, 192.168.0.100 - 192.168.0.200"'
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class AzureHighAvailabilitySchema extends BaseUISchema {
|
||||
constructor(fieldOptions = {}, initValues = {}) {
|
||||
super({
|
||||
oid: undefined,
|
||||
high_availability: false,
|
||||
...initValues,
|
||||
});
|
||||
|
||||
this.fieldOptions = {
|
||||
...fieldOptions,
|
||||
};
|
||||
this.initValues = initValues;
|
||||
}
|
||||
|
||||
get idAttribute() {
|
||||
return 'oid';
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
return [
|
||||
{
|
||||
id: 'high_availability',
|
||||
label: gettext('Zone redundant high availability'),
|
||||
type: 'switch',
|
||||
mode: ['create'],
|
||||
deps: ['region', 'db_instance_class'],
|
||||
depChange: (state, source, topState, actionObj) => {
|
||||
state._is_zone_redundant_ha_supported = false;
|
||||
if (state.region != actionObj.oldState.region) {
|
||||
state.high_availability = false;
|
||||
this.fieldOptions
|
||||
.zoneRedundantHaSupported(state.region)
|
||||
.then((res) => {
|
||||
state._is_zone_redundant_ha_supported = res.is_zone_redundant_ha_supported;
|
||||
});
|
||||
}
|
||||
if (state.db_instance_class != 'Burstable') {
|
||||
state._is_zone_redundant_ha_supported = true;
|
||||
}
|
||||
},
|
||||
disabled: (state) => {
|
||||
if (isEmptyString(state.region) || state.db_instance_class == 'Burstable') {
|
||||
state.high_availability = false;
|
||||
return true;
|
||||
} else {
|
||||
return !state._is_zone_redundant_ha_supported;
|
||||
}
|
||||
},
|
||||
helpMessage: gettext(
|
||||
'Zone redundant high availability deploys a standby replica in a different zone. The Burstable instance type does not support high availability.'
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class AzureClusterSchema extends BaseUISchema {
|
||||
constructor(fieldOptions = {}, initValues = {}) {
|
||||
super({
|
||||
oid: undefined,
|
||||
name: '',
|
||||
// Need to initilize child class init values in parent class itself
|
||||
public_ips: initValues?.hostIP.split('/')[0],
|
||||
db_instance_class: 'Burstable',
|
||||
...initValues,
|
||||
});
|
||||
|
||||
this.fieldOptions = {
|
||||
...fieldOptions,
|
||||
};
|
||||
this.initValues = initValues;
|
||||
|
||||
this.azureProjectDetails = new AzureProjectDetailsSchema(
|
||||
{
|
||||
subscriptions: this.fieldOptions.subscriptions,
|
||||
resourceGroups: this.fieldOptions.resourceGroups,
|
||||
regions: this.fieldOptions.regions,
|
||||
availabilityZones: this.fieldOptions.availabilityZones,
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
this.azureInstanceDetails = new AzureInstanceSchema(
|
||||
{
|
||||
versionOptions: this.fieldOptions.versionOptions,
|
||||
instanceOptions: this.fieldOptions.instanceOptions,
|
||||
storageOptions: this.fieldOptions.storageOptions,
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
this.azureNetworkSchema = new AzureNetworkSchema({}, {});
|
||||
|
||||
this.azureHighAvailabilitySchema = new AzureHighAvailabilitySchema(
|
||||
{
|
||||
zoneRedundantHaSupported: this.fieldOptions.zoneRedundantHaSupported,
|
||||
},
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
get idAttribute() {
|
||||
return 'oid';
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
return [
|
||||
{
|
||||
id: 'name',
|
||||
label: gettext('Cluster name'),
|
||||
type: 'text',
|
||||
mode: ['create'],
|
||||
noEmpty: true,
|
||||
},
|
||||
{
|
||||
type: 'nested-fieldset',
|
||||
label: gettext('Project Details'),
|
||||
mode: ['create'],
|
||||
schema: this.azureProjectDetails,
|
||||
},
|
||||
{
|
||||
type: 'nested-fieldset',
|
||||
label: gettext('Version & Instance'),
|
||||
mode: ['create'],
|
||||
schema: this.azureInstanceDetails,
|
||||
},
|
||||
{
|
||||
type: 'nested-fieldset',
|
||||
label: gettext('Network Connectivity'),
|
||||
mode: ['create'],
|
||||
schema: this.azureNetworkSchema,
|
||||
},
|
||||
{
|
||||
type: 'nested-fieldset',
|
||||
label: gettext('Availability'),
|
||||
mode: ['create'],
|
||||
schema: this.azureHighAvailabilitySchema,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
validateProjectDetails(data, setErr){
|
||||
if(isEmptyString(data.subscription)){
|
||||
setErr('subscription',gettext('Subscription cannot be empty.'));
|
||||
return true;
|
||||
}
|
||||
|
||||
if(isEmptyString(data.resource_group)){
|
||||
setErr('resource_group',gettext('Resource group cannot be empty.'));
|
||||
return true;
|
||||
}
|
||||
|
||||
if(isEmptyString(data.region)){
|
||||
setErr('region',gettext('Location cannot be empty.'));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
validateInstanceDetails(data, setErr){
|
||||
if(isEmptyString(data.availability_zone)){
|
||||
setErr('availability_zone',gettext('Availability zone cannot be empty.'));
|
||||
return true;
|
||||
}
|
||||
|
||||
if(isEmptyString(data.db_version)){
|
||||
setErr('db_version',gettext('Database version cannot be empty.'));
|
||||
return true;
|
||||
}
|
||||
|
||||
if(isEmptyString(data.db_instance_class)){
|
||||
setErr('db_instance_class',gettext('Instance class cannot be empty.'));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
validateNetworkDetails(data, setErr){
|
||||
if(isEmptyString(data.instance_type)){
|
||||
setErr('instance_type',gettext('Instance type cannot be empty.'));
|
||||
return true;
|
||||
}
|
||||
|
||||
if(isEmptyString(data.storage_size)){
|
||||
setErr('storage_size',gettext('Storage size cannot be empty.'));
|
||||
return true;
|
||||
}
|
||||
|
||||
if(isEmptyString(data.public_ips)){
|
||||
setErr('public_ips',gettext('Public IP range cannot be empty.'));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
validate(data, setErr) {
|
||||
if ( !isEmptyString(data.name) && (!/^[a-z0-9\-]*$/.test(data.name) || data.name.length < 3)) {
|
||||
setErr('name',gettext('Name must be more than 2 characters or more & must only contain lowercase letters, numbers, and hyphens'));
|
||||
return true;
|
||||
}
|
||||
|
||||
if(this.validateProjectDetails(data, setErr) || this.validateInstanceDetails(data, setErr) || this.validateNetworkDetails(data, setErr)){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export { AzureCredSchema, AzureClusterSchema, AzureDatabaseSchema };
|
||||
@@ -10,6 +10,7 @@ import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Theme from 'sources/Theme';
|
||||
import CloudWizard from './CloudWizard';
|
||||
import getApiInstance from '../../../../static/js/api_instance';
|
||||
|
||||
|
||||
// Cloud Wizard
|
||||
@@ -124,6 +125,15 @@ define('pgadmin.misc.cloud', [
|
||||
hooks: {
|
||||
// Triggered when the dialog is closed
|
||||
onclose: function () {
|
||||
if(event.target instanceof Object){
|
||||
const axiosApi = getApiInstance();
|
||||
let _url = url_for('cloud.clear_cloud_session');
|
||||
axiosApi.post(_url)
|
||||
.then(() => {})
|
||||
.catch((error) => {
|
||||
Alertify.error(gettext(`Error while clearing cloud wizard data: ${error.response.data.errormsg}`));
|
||||
});
|
||||
}
|
||||
// Clear the view and remove the react component.
|
||||
return setTimeout((function () {
|
||||
ReactDOM.unmountComponentAtNode(document.getElementById('cloudWizardDlg'));
|
||||
|
||||
@@ -14,6 +14,7 @@ import { DefaultButton, PrimaryButton } from '../../../../static/js/components/B
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import PropTypes from 'prop-types';
|
||||
import { getAWSSummary } from './aws';
|
||||
import {getAzureSummary} from './azure';
|
||||
import { getBigAnimalSummary } from './biganimal';
|
||||
import { commonTableStyles } from '../../../../static/js/Theme';
|
||||
import { Table, TableBody, TableCell, TableHead, TableRow } from '@material-ui/core';
|
||||
@@ -70,7 +71,10 @@ export function FinalSummary(props) {
|
||||
if (props.cloudProvider == 'biganimal') {
|
||||
summary = getBigAnimalSummary(props.cloudProvider, props.instanceData, props.databaseData);
|
||||
summaryHeader[1] = 'Version Details';
|
||||
} else {
|
||||
} else if(props.cloudProvider == 'azure'){
|
||||
summaryHeader.push('Network Connectivity','Availability');
|
||||
summary = getAzureSummary(props.cloudProvider, props.instanceData, props.databaseData);
|
||||
}else {
|
||||
summary = getAWSSummary(props.cloudProvider, props.instanceData, props.databaseData);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user