2022-06-15 00:52:42 -05:00
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
2024-01-01 02:43:48 -06:00
// Copyright (C) 2013 - 2024, The pgAdmin Development Team
2022-06-15 00:52:42 -05:00
// 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 pgAdmin from 'sources/pgadmin' ;
class AzureCredSchema extends BaseUISchema {
2024-06-13 08:18:02 -05:00
constructor ( eventBus , fieldOptions = { } , initValues = { } ) {
2022-06-15 00:52:42 -05:00
super ( {
oid : null ,
auth _type : 'interactive_browser_credential' ,
azure _tenant _id : '' ,
azure _subscription _id : '' ,
2022-07-06 01:13:49 -05:00
is _authenticating : false ,
is _authenticated : false ,
auth _code : '' ,
2022-06-15 00:52:42 -05:00
... initValues ,
} ) ;
this . fieldOptions = {
... fieldOptions ,
} ;
2022-09-10 03:34:28 -05:00
this . eventBus = eventBus ;
2022-06-15 00:52:42 -05:00
}
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' ,
2022-08-17 06:47:13 -05:00
gettext ( 'Azure Tenant ID is required for Azure interactive authentication.' )
2022-06-15 00:52:42 -05:00
) ;
}
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' ,
} ,
] ,
2024-04-08 06:49:51 -05:00
disabled : pgAdmin . server _mode == 'True' ,
2022-06-15 00:52:42 -05:00
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 ) => {
2024-04-08 06:49:51 -05:00
return state . auth _type !== 'interactive_browser_credential' ;
2022-06-15 00:52:42 -05:00
} ,
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' ] ,
2022-07-06 01:13:49 -05:00
type : 'button' ,
2022-06-15 00:52:42 -05:00
btnName : gettext ( 'Click here to authenticate yourself to Microsoft Azure' ) ,
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 ) => {
2022-07-06 01:13:49 -05:00
if ( source == 'auth_type' || source == 'azure_tenant_id' ) {
return { is _authenticated : false , auth _code : '' } ;
}
if ( source == 'auth_btn' ) {
return { is _authenticating : true } ;
2022-06-15 00:52:42 -05:00
}
} ,
2022-07-06 01:13:49 -05:00
deferredDepChange : ( state , source ) => {
return new Promise ( ( resolve , reject ) => {
/* button clicked */
if ( source == 'auth_btn' ) {
obj . fieldOptions . authenticateAzure ( state . auth _type , state . azure _tenant _id )
. then ( ( ) => {
resolve ( ( ) => ( {
is _authenticated : true ,
is _authenticating : false ,
auth _code : ''
} ) ) ;
} )
. catch ( ( err ) => {
2024-06-10 07:34:32 -05:00
reject ( new Error ( err ) ) ;
2022-07-06 01:13:49 -05:00
} ) ;
}
} ) ;
} ,
2022-06-15 00:52:42 -05:00
disabled : ( state ) => {
if ( state . auth _type == 'interactive_browser_credential' && state . azure _tenant _id == '' ) {
return true ;
}
2022-07-06 01:13:49 -05:00
return state . is _authenticating || state . is _authenticated ;
2022-06-15 00:52:42 -05:00
} ,
} ,
2022-07-06 01:13:49 -05:00
{
id : 'is_authenticating' ,
visible : false ,
type : '' ,
deps : [ 'auth_btn' ] ,
deferredDepChange : ( state , source ) => {
return new Promise ( ( resolve , reject ) => {
if ( source == 'auth_btn' && state . auth _type == 'interactive_browser_credential' && state . is _authenticating ) {
obj . fieldOptions . getAuthCode ( )
. then ( ( res ) => {
resolve ( ( ) => {
return {
is _authenticating : false ,
auth _code : res . data . data . user _code ,
} ;
} ) ;
} )
. catch ( ( err ) => {
2024-06-10 07:34:32 -05:00
reject ( new Error ( err ) ) ;
2022-07-06 01:13:49 -05:00
} ) ;
}
} ) ;
} ,
} ,
{
id : 'auth_code' ,
mode : [ 'create' ] ,
deps : [ 'auth_btn' ] ,
type : ( state ) => ( {
type : 'note' ,
text : ` To complete the authenticatation, use a web browser to open the page https://microsoft.com/devicelogin and enter the code : <strong> ${ state . auth _code } </strong> ` ,
} ) ,
visible : ( state ) => {
return Boolean ( state . auth _code ) ;
} ,
controlProps : {
raw : true ,
}
}
2022-06-15 00:52:42 -05:00
] ;
}
}
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' ,
2022-08-17 06:47:13 -05:00
gettext ( 'The Admin username must be between 1-63 characters long, and must only contain alphabetic characters and numbers.' )
2022-06-15 00:52:42 -05:00
) ;
return true ;
}
if (
[ 'azure_superuser' , 'azure_pg_admin' , 'admin' , 'administrator' , 'root' , 'guest' , 'public' ] . includes ( data . db _username ) ||
data . db _username . startsWith ( 'pg_' ) ) {
2022-08-17 06:47:13 -05:00
setErrMsg ( 'db_username' , gettext ( 'Specified Admin username is not allowed.' ) ) ;
2022-06-15 00:52:42 -05:00
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 ) {
2022-09-08 09:26:02 -05:00
return this . validateDbUserName ( data , setErrMsg ) || this . validateDbPassword ( data , setErrMsg ) ;
2022-06-15 00:52:42 -05:00
}
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 (
2022-08-17 06:47:13 -05:00
'The admin username must be 1-63 characters long and can only contain characters, numbers and the underscore character. The username cannot be "azure_superuser", "azure_pg_admin", "admin", "administrator", "root", "guest", "public", or start with "pg_".'
2022-06-15 00:52:42 -05:00
) ,
} ,
{
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'
2024-04-29 01:14:17 -05:00
) , controlProps : { autoComplete : 'new-password' }
2022-06-15 00:52:42 -05:00
} ,
{
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 (
2022-08-17 06:47:13 -05:00
'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"'
2022-06-15 00:52:42 -05:00
) ,
} ,
] ;
}
}
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 ;
}
2022-08-17 06:47:13 -05:00
}
2022-06-15 00:52:42 -05:00
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 ) {
2024-06-12 07:39:06 -05:00
if ( ! isEmptyString ( data . name ) && ( ! /^[a-z0-9-]*$/ . test ( data . name ) || data . name . length < 3 ) ) {
2022-08-17 06:47:13 -05:00
setErr ( 'name' , gettext ( 'Name must be more than 2 characters and must only contain lowercase letters, numbers, and hyphens' ) ) ;
2022-06-15 00:52:42 -05:00
return true ;
}
2022-09-08 09:26:02 -05:00
return ( this . validateProjectDetails ( data , setErr ) || this . validateInstanceDetails ( data , setErr ) || this . validateNetworkDetails ( data , setErr ) ) ;
2022-06-15 00:52:42 -05:00
}
}
export { AzureCredSchema , AzureClusterSchema , AzureDatabaseSchema } ;