codekingpro/portable-devtools
114k
1/////////////////////////////////////////////////////////////2//3// pgAdmin 4 - PostgreSQL Tools4//5// Copyright (C) 2013 - 2024, The pgAdmin Development Team6// This software is released under the PostgreSQL Licence7//8//////////////////////////////////////////////////////////////9 10import gettext from 'sources/gettext';11import url_for from 'sources/url_for';12import React from 'react';13import { Box, Paper } from '@mui/material';14import { makeStyles } from '@mui/styles';15import Wizard from '../../../../static/js/helpers/wizard/Wizard';16import WizardStep from '../../../../static/js/helpers/wizard/WizardStep';17import {FormFooterMessage, MESSAGE_TYPE } from '../../../../static/js/components/FormComponents';18import getApiInstance from '../../../../static/js/api_instance';19import PropTypes from 'prop-types';20import pgAdmin from 'sources/pgadmin';21import {ToggleButtons, FinalSummary} from './cloud_components';22import { PrimaryButton } from '../../../../static/js/components/Buttons';23import {AwsCredentials, AwsInstanceDetails, AwsDatabaseDetails, validateCloudStep1, validateCloudStep2, validateCloudStep3} from './aws';24import {BigAnimalInstance, BigAnimalDatabase, BigAnimalClusterType, validateBigAnimal, validateBigAnimalStep2, validateBigAnimalStep3, validateBigAnimalStep4} from './biganimal';25import { isEmptyString } from 'sources/validators';26import { AWSIcon, BigAnimalIcon, AzureIcon, GoogleCloudIcon } from '../../../../static/js/components/ExternalIcon';27import {AzureCredentials, AzureInstanceDetails, AzureDatabaseDetails, checkClusternameAvailbility, validateAzureStep2, validateAzureStep3} from './azure';28import { GoogleCredentials, GoogleInstanceDetails, GoogleDatabaseDetails, validateGoogleStep2, validateGoogleStep3 } from './google';29import EventBus from '../../../../static/js/helpers/EventBus';30import { CLOUD_PROVIDERS, CLOUD_PROVIDERS_LABELS } from './cloud_constants';31import { LAYOUT_EVENTS } from '../../../../static/js/helpers/Layout';32 33 34const useStyles = makeStyles(() =>35 ({36 messageBox: {37 marginBottom: '1em',38 display: 'flex',39 },40 messagePadding: {41 paddingTop: '10px',42 flex: 2.5,43 },44 buttonMarginEDB: {45 position: 'relative',46 top: '20%',47 },48 toggleButton: {49 height: '100px',50 },51 summaryContainer: {52 flexGrow: 1,53 minHeight: 0,54 overflow: 'auto',55 },56 boxText: {57 paddingBottom: '5px'58 },59 authButton: {60 marginLeft: '12em'61 }62 }),63);64 65export const CloudWizardEventsContext = React.createContext();66 67export default function CloudWizard({ nodeInfo, nodeData, onClose, cloudPanelId}) {68 const classes = useStyles();69 const eventBus = React.useRef(new EventBus());70 71 let steps = [gettext('Cloud Provider'), gettext('Credentials'), gettext('Cluster Type'),72 gettext('Instance Specification'), gettext('Database Details'), gettext('Review')];73 const [currentStep, setCurrentStep] = React.useState('');74 const [cloudSelection, setCloudSelection] = React.useState('');75 const [errMsg, setErrMsg] = React.useState('');76 const [cloudInstanceDetails, setCloudInstanceDetails] = React.useState({});77 const [cloudDBCred, setCloudDBCred] = React.useState({});78 const [cloudDBDetails, setCloudDBDetails] = React.useState({});79 const [callRDSAPI, setCallRDSAPI] = React.useState({});80 const [hostIP, setHostIP] = React.useState('127.0.0.1/32');81 const [cloudProvider, setCloudProvider] = React.useState('');82 const [verificationIntiated, setVerificationIntiated] = React.useState(false);83 84 const [bigAnimalInstanceData, setBigAnimalInstanceData] = React.useState({});85 const [bigAnimalDatabaseData, setBigAnimalDatabaseData] = React.useState({});86 const [bigAnimalClusterTypeData, setBigAnimalClusterTypeData] = React.useState({});87 88 const [azureCredData, setAzureCredData] = React.useState({});89 const [azureInstanceData, setAzureInstanceData] = React.useState({});90 const [azureDatabaseData, setAzureDatabaseData] = React.useState({});91 92 const [googleCredData, setGoogleCredData] = React.useState({});93 const [googleInstanceData, setGoogleInstanceData] = React.useState({});94 const [googleDatabaseData, setGoogleDatabaseData] = React.useState({});95 96 const axiosApi = getApiInstance();97 98 const [verificationURI, setVerificationURI] = React.useState('');99 const [verificationCode, setVerificationCode] = React.useState('');100 101 const authInterval = React.useRef();102 103 React.useEffect(()=>{104 eventBus.current.registerListener('SET_ERROR_MESSAGE_FOR_CLOUD_WIZARD', (msg) => {105 setErrMsg(msg);106 });107 108 eventBus.current.registerListener('SET_CRED_VERIFICATION_INITIATED', (initiated) => {109 setVerificationIntiated(initiated);110 });111 112 const onWizardClosing = (panelId)=>{113 if(panelId == cloudPanelId) {114 clearInterval(authInterval.current);115 onClose();116 }117 };118 pgAdmin.Browser.docker.eventBus.registerListener(LAYOUT_EVENTS.CLOSING, onWizardClosing);119 return ()=>{120 pgAdmin.Browser.docker.eventBus.deregisterListener(LAYOUT_EVENTS.CLOSING, onWizardClosing);121 };122 }, []);123 124 React.useEffect(() => {125 let _url = url_for('cloud.get_host_ip') ;126 axiosApi.get(_url)127 .then((res) => {128 if (res.data.data) {129 setHostIP(res.data.data);130 }131 })132 .catch((error) => {133 pgAdmin.Browser.notifier.error(gettext(`Error while getting the host ip: ${error.response.data.errormsg}`));134 });135 }, [cloudProvider]);136 137 const wizardStepChange = (data) => {138 setCurrentStep(data.currentStep);139 };140 141 const onSave = () => {142 let _url = url_for('cloud.deploy_on_cloud'),143 post_data = {};144 145 if (cloudProvider == CLOUD_PROVIDERS.AWS) {146 post_data = {147 gid: nodeInfo.server_group._id,148 cloud: cloudProvider,149 secret: cloudDBCred,150 instance_details:cloudInstanceDetails,151 db_details: cloudDBDetails152 };153 } else if(cloudProvider == CLOUD_PROVIDERS.AZURE){154 post_data = {155 gid: nodeInfo.server_group._id,156 secret: azureCredData,157 cloud: cloudProvider,158 instance_details:azureInstanceData,159 db_details: azureDatabaseData160 };161 }else if(cloudProvider == CLOUD_PROVIDERS.GOOGLE){162 post_data = {163 gid: nodeInfo.server_group._id,164 secret: googleCredData,165 cloud: cloudProvider,166 instance_details:googleInstanceData,167 db_details: googleDatabaseData168 };169 170 }else {171 post_data = {172 gid: nodeInfo.server_group._id,173 cloud: cloudProvider,174 cluster_details: bigAnimalClusterTypeData,175 instance_details: bigAnimalInstanceData,176 db_details: bigAnimalDatabaseData177 };178 }179 180 axiosApi.post(_url, post_data)181 .then((res) => {182 pgAdmin.Browser.Events.trigger('pgadmin:browser:tree:add', res.data.data.node, {'server_group': nodeInfo['server_group']});183 pgAdmin.Browser.BgProcessManager.startProcess(res.data.data.job_id, res.data.data.desc);184 onClose();185 })186 .catch((error) => {187 pgAdmin.Browser.notifier.error(gettext(`Error while saving cloud wizard data: ${error.response.data.errormsg}`));188 });189 };190 191 const disableNextCheck = () => {192 setCallRDSAPI(currentStep);193 let isError = (cloudProvider == '');194 switch(cloudProvider) {195 case CLOUD_PROVIDERS.AWS:196 switch (currentStep) {197 case 0:198 setCloudSelection(CLOUD_PROVIDERS.AWS);199 break;200 case 1:201 isError = validateCloudStep1(cloudDBCred);202 break;203 case 2:204 break;205 case 3:206 isError = validateCloudStep2(cloudInstanceDetails, hostIP);207 break;208 case 4:209 isError = validateCloudStep3(cloudDBDetails, nodeInfo);210 break;211 default:212 break;213 }214 break;215 case CLOUD_PROVIDERS.BIGANIMAL:216 switch (currentStep) {217 case 0:218 setCloudSelection(CLOUD_PROVIDERS.BIGANIMAL);219 break;220 case 1:221 isError = !verificationIntiated;222 break;223 case 2:224 isError = validateBigAnimalStep2(bigAnimalClusterTypeData);225 break;226 case 3:227 isError = validateBigAnimalStep3(bigAnimalInstanceData);228 break;229 case 4:230 isError = validateBigAnimalStep4(bigAnimalDatabaseData, nodeInfo);231 break;232 default:233 break;234 }235 break;236 case CLOUD_PROVIDERS.AZURE:237 switch (currentStep) {238 case 0:239 setCloudSelection(CLOUD_PROVIDERS.AZURE);240 break;241 case 1:242 isError = !verificationIntiated;243 break;244 case 2:245 break;246 case 3:247 isError = validateAzureStep2(azureInstanceData);248 break;249 case 4:250 isError = validateAzureStep3(azureDatabaseData, nodeInfo);251 break;252 default:253 break;254 }255 break;256 case CLOUD_PROVIDERS.GOOGLE:257 switch (currentStep) {258 case 0:259 setCloudSelection(CLOUD_PROVIDERS.GOOGLE);260 break;261 case 1:262 isError = !verificationIntiated;263 break;264 case 2:265 break;266 case 3:267 isError = validateGoogleStep2(googleInstanceData);268 break;269 case 4:270 isError = validateGoogleStep3(googleDatabaseData, nodeInfo);271 break;272 default:273 break;274 }275 }276 return isError;277 };278 279 const onBeforeBack = (activeStep) => {280 return new Promise((resolve)=>{281 if(activeStep == 3 && (cloudProvider == CLOUD_PROVIDERS.AWS || cloudProvider == CLOUD_PROVIDERS.AZURE || cloudProvider == CLOUD_PROVIDERS.GOOGLE)) {282 resolve(true);283 }284 else if(activeStep == 1 && (cloudProvider == CLOUD_PROVIDERS.AWS || cloudProvider == CLOUD_PROVIDERS.AZURE || cloudProvider == CLOUD_PROVIDERS.GOOGLE)) {285 setVerificationIntiated(false);286 }287 setErrMsg(['', '']);288 resolve();289 });290 };291 292 const onBeforeNext = (activeStep) => {293 return new Promise((resolve, reject)=>{294 if(activeStep == 1 && cloudProvider == CLOUD_PROVIDERS.AWS) {295 setErrMsg([MESSAGE_TYPE.INFO, gettext('Validating credentials...')]);296 let _url = url_for('rds.verify_credentials');297 const post_data = {298 cloud: cloudSelection,299 secret: cloudDBCred,300 };301 axiosApi.post(_url, post_data)302 .then((res) => {303 if(!res.data.success) {304 setErrMsg([MESSAGE_TYPE.ERROR, res.data.info]);305 reject(new Error(res.data.info));306 } else {307 setErrMsg(['', '']);308 if (activeStep == 1) {309 resolve(true);310 } else {311 resolve(false);312 }313 }314 })315 .catch(() => {316 setErrMsg([MESSAGE_TYPE.ERROR, gettext('Error while checking cloud credentials')]);317 reject(new Error(gettext('Error while checking cloud credentials')));318 });319 } else if(activeStep == 0 && cloudProvider == CLOUD_PROVIDERS.BIGANIMAL) {320 if (!isEmptyString(verificationURI)) { resolve(); return; }321 setErrMsg([MESSAGE_TYPE.INFO, gettext('Getting EDB BigAnimal verification URL...')]);322 validateBigAnimal()323 .then((res) => {324 setVerificationURI(res);325 setVerificationCode(res.substring(res.indexOf('=')+1));326 setErrMsg(['', '']);327 resolve();328 })329 .catch((error) => {330 setErrMsg([MESSAGE_TYPE.ERROR, gettext(error)]);331 reject(new Error(gettext(error)));332 });333 } else if (cloudProvider == CLOUD_PROVIDERS.AZURE) {334 if (activeStep == 1) {335 // Skip the current step336 setErrMsg(['', '']);337 resolve(true);338 } else if (activeStep == 2) {339 setErrMsg([MESSAGE_TYPE.INFO, gettext('Checking cluster name availability...')]);340 checkClusternameAvailbility(azureInstanceData.name)341 .then((res)=>{342 if (res.data && res.data.success == 0 ) {343 setErrMsg([MESSAGE_TYPE.ERROR, gettext('Specified cluster name is already used.')]);344 }else{345 setErrMsg(['', '']);346 }347 resolve();348 }).catch((error)=>{349 setErrMsg([MESSAGE_TYPE.ERROR, gettext(error)]);350 reject(new Error(gettext(error)));351 });352 } else {353 resolve();354 }355 }else if (cloudProvider == CLOUD_PROVIDERS.GOOGLE) {356 if (activeStep == 1) {357 // Skip the current step358 setErrMsg(['', '']);359 resolve(true);360 } else if (activeStep == 2) { resolve(true);} else {361 resolve();362 }363 }364 else {365 setErrMsg(['', '']);366 resolve();367 }368 });369 };370 371 const authenticateBigAnimal = () => {372 let loading_icon_url = url_for(373 'static', { 'filename': 'img/loading.gif'}374 );375 376 setErrMsg([MESSAGE_TYPE.INFO, gettext('EDB BigAnimal authentication process is in progress...') + '<img src="' + loading_icon_url + '" alt="' + gettext('Loading...') + '">']);377 let child = window.open(verificationURI, 'edb_biganimal_authentication');378 let _url = url_for('biganimal.verification_ack') ;379 let countdown = 60;380 authInterval.current = setInterval(() => {381 axiosApi.get(_url)382 .then((res) => {383 if (res.data && res.data.success == 1 ) {384 setErrMsg([MESSAGE_TYPE.SUCCESS, gettext('Authentication completed successfully. Click the Next button to proceed.')]);385 setVerificationIntiated(true);386 clearInterval(authInterval.current);387 } else if (res.data && res.data.success == 0 && res.data.errormsg == 'access_denied') {388 setErrMsg([MESSAGE_TYPE.INFO, gettext('Verification failed. Access Denied...')]);389 setVerificationIntiated(false);390 clearInterval(authInterval.current);391 } else if (res.data && res.data.success == 0 && res.data.errormsg == 'forbidden') {392 setErrMsg([MESSAGE_TYPE.INFO, gettext('Authentication completed successfully but you do not have permission to create the cluster.')]);393 setVerificationIntiated(false);394 clearInterval(authInterval.current);395 } else if (child.closed && !verificationIntiated && countdown <= 0) {396 setVerificationIntiated(false);397 setErrMsg([MESSAGE_TYPE.ERROR, gettext('Authentication is aborted.')]);398 clearInterval(authInterval.current);399 }400 authInterval.current = null;401 })402 .catch((error) => {403 setErrMsg([MESSAGE_TYPE.ERROR, gettext(`Error while verifying EDB BigAnimal: ${error.response.data.errormsg}`)]);404 });405 countdown = countdown - 1;406 }, 1000);407 };408 409 410 const onDialogHelp = () => {411 window.open(url_for('help.static', { 'filename': 'cloud_deployment.html' }), 'pgadmin_help');412 };413 414 const onErrClose = React.useCallback(()=>{415 setErrMsg([]);416 });417 418 let cloud_providers = [419 {label: gettext(CLOUD_PROVIDERS_LABELS.AWS), value: CLOUD_PROVIDERS.AWS, icon: <AWSIcon className={classes.icon} />},420 {label: gettext(CLOUD_PROVIDERS_LABELS.BIGANIMAL), value: CLOUD_PROVIDERS.BIGANIMAL, icon: <BigAnimalIcon className={classes.icon} />},421 {label: gettext(CLOUD_PROVIDERS_LABELS.AZURE), value: CLOUD_PROVIDERS.AZURE, icon: <AzureIcon className={classes.icon} /> },422 {label: gettext(CLOUD_PROVIDERS_LABELS.GOOGLE), value: CLOUD_PROVIDERS.GOOGLE, icon: <GoogleCloudIcon className={classes.icon} /> }];423 424 return (425 <CloudWizardEventsContext.Provider value={eventBus.current}>426 <Wizard427 title={gettext('Deploy Cloud Instance')}428 stepList={steps}429 disableNextStep={disableNextCheck}430 onStepChange={wizardStepChange}431 onSave={onSave}432 onHelp={onDialogHelp}433 beforeNext={onBeforeNext}434 beforeBack={onBeforeBack}>435 <WizardStep stepId={0}>436 <Box className={classes.messageBox}>437 <Box className={classes.messagePadding}>{gettext('Select a cloud provider for PostgreSQL database.')}</Box>438 </Box>439 <Box className={classes.messageBox}>440 <ToggleButtons cloudProvider={cloudProvider} setCloudProvider={setCloudProvider}441 options={cloud_providers}442 ></ToggleButtons>443 </Box>444 <FormFooterMessage type={errMsg[0]} message={errMsg[1]} onClose={onErrClose} />445 </WizardStep>446 <WizardStep stepId={1} >447 <Box className={classes.buttonMarginEDB}>448 {cloudProvider == CLOUD_PROVIDERS.BIGANIMAL && <Box className={classes.messageBox}>449 <Box>{gettext('The verification code to authenticate the pgAdmin to EDB BigAnimal is: ')} <strong>{verificationCode}</strong>450 <br/>{gettext('By clicking the below button, you will be redirected to the EDB BigAnimal authentication page in a new tab.')}451 </Box>452 </Box>}453 {cloudProvider == CLOUD_PROVIDERS.BIGANIMAL && <PrimaryButton onClick={authenticateBigAnimal} disabled={verificationIntiated}>454 {gettext('Click here to authenticate yourself to EDB BigAnimal')}455 </PrimaryButton>}456 {cloudProvider == CLOUD_PROVIDERS.BIGANIMAL && <Box className={classes.messageBox}>457 <Box ></Box>458 </Box>}459 </Box>460 {cloudProvider == CLOUD_PROVIDERS.AWS && <AwsCredentials cloudProvider={cloudProvider} nodeInfo={nodeInfo} nodeData={nodeData} setCloudDBCred={setCloudDBCred}/>}461 { cloudProvider == CLOUD_PROVIDERS.AZURE &&462 <Box flexGrow={1}>463 <AzureCredentials cloudProvider={cloudProvider} setAzureCredData={setAzureCredData}/>464 </Box>}465 <Box flexGrow={1}>466 {cloudProvider == CLOUD_PROVIDERS.GOOGLE && <GoogleCredentials cloudProvider={cloudProvider} setGoogleCredData={setGoogleCredData}/>}467 </Box>468 <FormFooterMessage type={errMsg[0]} message={errMsg[1]} onClose={onErrClose} />469 </WizardStep>470 <WizardStep stepId={2} >471 {cloudProvider == CLOUD_PROVIDERS.BIGANIMAL && callRDSAPI == 2 && <BigAnimalClusterType472 cloudProvider={cloudProvider}473 nodeInfo={nodeInfo}474 nodeData={nodeData}475 setBigAnimalClusterTypeData={setBigAnimalClusterTypeData}476 hostIP={hostIP}477 /> }478 <FormFooterMessage type={errMsg[0]} message={errMsg[1]} onClose={onErrClose} />479 </WizardStep>480 <WizardStep stepId={3} >481 {cloudProvider == CLOUD_PROVIDERS.AWS && callRDSAPI == 3 && <AwsInstanceDetails482 cloudProvider={cloudProvider}483 nodeInfo={nodeInfo}484 nodeData={nodeData}485 setCloudInstanceDetails={setCloudInstanceDetails}486 hostIP={hostIP} /> }487 {cloudProvider == CLOUD_PROVIDERS.BIGANIMAL && callRDSAPI == 3 && <BigAnimalInstance488 cloudProvider={cloudProvider}489 nodeInfo={nodeInfo}490 nodeData={nodeData}491 setBigAnimalInstanceData={setBigAnimalInstanceData}492 hostIP={hostIP}493 bigAnimalClusterTypeData={bigAnimalClusterTypeData}494 /> }495 {cloudProvider == CLOUD_PROVIDERS.AZURE && callRDSAPI == 3 && <AzureInstanceDetails496 cloudProvider={cloudProvider}497 nodeInfo={nodeInfo}498 nodeData={nodeData}499 setAzureInstanceData={setAzureInstanceData}500 hostIP={hostIP}501 azureInstanceData = {azureInstanceData}502 /> }503 {cloudProvider == CLOUD_PROVIDERS.GOOGLE && callRDSAPI == 3 && <GoogleInstanceDetails504 cloudProvider={cloudProvider}505 nodeInfo={nodeInfo}506 nodeData={nodeData}507 setGoogleInstanceData={setGoogleInstanceData}508 hostIP={hostIP}509 googleInstanceData = {googleInstanceData}510 /> }511 <FormFooterMessage type={errMsg[0]} message={errMsg[1]} onClose={onErrClose} />512 </WizardStep>513 <WizardStep stepId={4} >514 {cloudProvider == CLOUD_PROVIDERS.AWS && <AwsDatabaseDetails515 cloudProvider={cloudProvider}516 nodeInfo={nodeInfo}517 nodeData={nodeData}518 setCloudDBDetails={setCloudDBDetails}519 />520 }521 {cloudProvider == CLOUD_PROVIDERS.BIGANIMAL && callRDSAPI == 4 && <BigAnimalDatabase522 cloudProvider={cloudProvider}523 nodeInfo={nodeInfo}524 nodeData={nodeData}525 setBigAnimalDatabaseData={setBigAnimalDatabaseData}526 bigAnimalClusterTypeData={bigAnimalClusterTypeData}527 />528 }529 {cloudProvider == CLOUD_PROVIDERS.AZURE && <AzureDatabaseDetails530 cloudProvider={cloudProvider}531 nodeInfo={nodeInfo}532 nodeData={nodeData}533 setAzureDatabaseData={setAzureDatabaseData}534 />535 }536 {cloudProvider == CLOUD_PROVIDERS.GOOGLE && <GoogleDatabaseDetails537 cloudProvider={cloudProvider}538 nodeInfo={nodeInfo}539 nodeData={nodeData}540 setGoogleDatabaseData={setGoogleDatabaseData}541 />542 }543 </WizardStep>544 <WizardStep stepId={5} >545 <Box className={classes.boxText}>{gettext('Please review the details before creating the cloud instance.')}</Box>546 <Paper variant="outlined" elevation={0} className={classes.summaryContainer}>547 {cloudProvider == CLOUD_PROVIDERS.AWS && callRDSAPI == 5 && <FinalSummary548 cloudProvider={cloudProvider}549 instanceData={cloudInstanceDetails}550 databaseData={cloudDBDetails}551 />552 }553 {cloudProvider == CLOUD_PROVIDERS.BIGANIMAL && callRDSAPI == 5 && <FinalSummary554 cloudProvider={cloudProvider}555 instanceData={bigAnimalInstanceData}556 databaseData={bigAnimalDatabaseData}557 clusterTypeData={bigAnimalClusterTypeData}558 />559 }560 {cloudProvider == CLOUD_PROVIDERS.AZURE && callRDSAPI == 5 && <FinalSummary561 cloudProvider={cloudProvider}562 instanceData={azureInstanceData}563 databaseData={azureDatabaseData}564 />565 }566 {cloudProvider == CLOUD_PROVIDERS.GOOGLE && callRDSAPI == 5 && <FinalSummary567 cloudProvider={cloudProvider}568 instanceData={googleInstanceData}569 databaseData={googleDatabaseData}570 />571 }572 </Paper>573 </WizardStep>574 </Wizard>575 </CloudWizardEventsContext.Provider>576 );577}578 579CloudWizard.propTypes = {580 nodeInfo: PropTypes.object,581 nodeData: PropTypes.object,582 onClose: PropTypes.func,583 cloudPanelId: PropTypes.string,584};585 