1) Added support for setting PostgreSQL connection parameters. #4728

2) Fixed an issue where Kerberos authentication to the server is not imported/exported. #5732
3) Increase the length of the value column of the setting table. #5746
4) Upgrade Flask-Migrate to 4.0.0. #5525
This commit is contained in:
Akshay Joshi
2023-01-23 17:19:59 +05:30
committed by GitHub
parent 91049445dd
commit a7cf698d09
31 changed files with 594 additions and 616 deletions

View File

@@ -157,9 +157,19 @@ class ServerModule(sg.ServerGroupPluginModule):
server.tunnel_username = sharedserver.tunnel_username
server.tunnel_password = sharedserver.tunnel_password
server.save_password = sharedserver.save_password
server.passfile = sharedserver.passfile
if hasattr(server, 'connection_params') and \
hasattr(sharedserver, 'connection_params') and \
'passfile' in server.connection_params and \
'passfile' in sharedserver.connection_params:
server.connection_params['passfile'] = \
sharedserver.connection_params['passfile']
server.servergroup_id = sharedserver.servergroup_id
server.sslcert = sharedserver.sslcert
if hasattr(server, 'connection_params') and \
hasattr(sharedserver, 'connection_params') and \
'sslcert' in server.connection_params and \
'sslcert' in sharedserver.connection_params:
server.connection_params['sslcert'] = \
sharedserver.connection_params['sslcert']
server.username = sharedserver.username
server.server_owner = sharedserver.server_owner
server.password = sharedserver.password
@@ -346,29 +356,23 @@ class ServerModule(sg.ServerGroupPluginModule):
servergroup_id=gid,
name=data.name,
host=data.host,
hostaddr=data.hostaddr,
port=data.port,
maintenance_db=data.maintenance_db,
username=None,
save_password=0,
ssl_mode=data.ssl_mode,
comment=None,
role=data.role,
sslcert=None,
sslkey=None,
sslrootcert=None,
sslcrl=None,
bgcolor=data.bgcolor if data.bgcolor else None,
fgcolor=data.fgcolor if data.fgcolor else None,
service=data.service if data.service else None,
connect_timeout=0,
use_ssh_tunnel=data.use_ssh_tunnel,
tunnel_host=data.tunnel_host,
tunnel_port=22,
tunnel_username=None,
tunnel_authentication=0,
tunnel_identity_file=None,
shared=True
shared=True,
connection_params=data.connection_params
)
db.session.add(shared_server)
db.session.commit()
@@ -486,11 +490,53 @@ class ServerNode(PGChildNodeView):
)
data[field] = dummy_ssl_file
# For Desktop mode, we will allow to default
else:
data[field] = None
return flag, data
def convert_connection_parameter(self, params):
"""
This function is used to convert the connection parameter based
on the instance type.
"""
conn_params = None
# if params is of type list then it is coming from the frontend,
# and we have to convert it into the dict and store it into the
# database
if isinstance(params, list):
conn_params = {}
for item in params:
conn_params[item['name']] = item['value']
# if params is of type dict then it is coming from the database,
# and we have to convert it into the list of params to show on GUI.
elif isinstance(params, dict):
conn_params = []
for key, value in params.items():
if value is not None:
conn_params.append(
{'name': key, 'keyword': key, 'value': value})
return conn_params
def update_connection_parameter(self, data, server):
"""
This function is used to update the connection parameters.
"""
if 'connection_params' in data and \
hasattr(server, 'connection_params'):
existing_conn_params = getattr(server, 'connection_params')
new_conn_params = data['connection_params']
if 'deleted' in new_conn_params:
for item in new_conn_params['deleted']:
del existing_conn_params[item['name']]
if 'added' in new_conn_params:
for item in new_conn_params['added']:
existing_conn_params[item['name']] = item['value']
if 'changed' in new_conn_params:
for item in new_conn_params['changed']:
existing_conn_params[item['name']] = item['value']
data['connection_params'] = existing_conn_params
@login_required
def nodes(self, gid):
res = []
@@ -701,27 +747,18 @@ class ServerNode(PGChildNodeView):
config_param_map = {
'name': 'name',
'host': 'host',
'hostaddr': 'hostaddr',
'port': 'port',
'db': 'maintenance_db',
'username': 'username',
'sslmode': 'ssl_mode',
'gid': 'servergroup_id',
'comment': 'comment',
'role': 'role',
'db_res': 'db_res',
'passfile': 'passfile',
'passexec_cmd': 'passexec_cmd',
'passexec_expiration': 'passexec_expiration',
'sslcert': 'sslcert',
'sslkey': 'sslkey',
'sslrootcert': 'sslrootcert',
'sslcrl': 'sslcrl',
'sslcompression': 'sslcompression',
'bgcolor': 'bgcolor',
'fgcolor': 'fgcolor',
'service': 'service',
'connect_timeout': 'connect_timeout',
'use_ssh_tunnel': 'use_ssh_tunnel',
'tunnel_host': 'tunnel_host',
'tunnel_port': 'tunnel_port',
@@ -730,15 +767,14 @@ class ServerNode(PGChildNodeView):
'tunnel_identity_file': 'tunnel_identity_file',
'shared': 'shared',
'kerberos_conn': 'kerberos_conn',
'connection_params': 'connection_params'
}
disp_lbl = {
'name': gettext('name'),
'hostaddr': gettext('Host name/address'),
'port': gettext('Port'),
'db': gettext('Maintenance database'),
'username': gettext('Username'),
'sslmode': gettext('SSL Mode'),
'comment': gettext('Comments'),
'role': gettext('Role')
}
@@ -750,12 +786,16 @@ class ServerNode(PGChildNodeView):
if 'db_res' in data:
data['db_res'] = ','.join(data['db_res'])
hostaddr = data.get('hostaddr')
if hostaddr and not is_valid_ipaddress(hostaddr):
# Update connection parameter if any.
self.update_connection_parameter(data, server)
if 'connection_params' in data and \
'hostaddr' in data['connection_params'] and \
not is_valid_ipaddress(data['connection_params']['hostaddr']):
return make_json_response(
success=0,
status=400,
errormsg=gettext('Host address not valid')
errormsg=gettext('Not a valid Host address')
)
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
@@ -849,8 +889,7 @@ class ServerNode(PGChildNodeView):
if connected:
for arg in (
'hostaddr', 'db', 'sslmode',
'role', 'service'
'db', 'role', 'service'
):
if arg in data:
return forbidden(
@@ -911,10 +950,6 @@ class ServerNode(PGChildNodeView):
def properties(self, gid, sid):
"""Return list of attributes of a server"""
sslcert = None
sslkey = None
sslrootcert = None
sslcrl = None
server = Server.query.filter_by(
id=sid).first()
@@ -941,19 +976,13 @@ class ServerNode(PGChildNodeView):
shared_server)
server_owner = server.server_owner
is_ssl = True if server.ssl_mode in self.SSL_MODES else False
if is_ssl:
sslcert = server.sslcert
sslkey = server.sslkey
sslrootcert = server.sslrootcert
sslcrl = server.sslcrl
use_ssh_tunnel = 0
tunnel_host = None
tunnel_port = 22
tunnel_username = None
tunnel_authentication = 0
connection_params = \
self.convert_connection_parameter(server.connection_params)
if server.use_ssh_tunnel:
use_ssh_tunnel = server.use_ssh_tunnel
@@ -968,7 +997,6 @@ class ServerNode(PGChildNodeView):
'server_owner': server_owner,
'user_id': server.user_id,
'host': server.host,
'hostaddr': server.hostaddr,
'port': server.port,
'db': server.maintenance_db,
'shared': server.shared if config.SERVER_MODE else None,
@@ -979,26 +1007,16 @@ class ServerNode(PGChildNodeView):
'role': server.role,
'connected': connected,
'version': manager.ver,
'sslmode': server.ssl_mode,
'server_type': manager.server_type if connected else 'pg',
'bgcolor': server.bgcolor,
'fgcolor': server.fgcolor,
'db_res': server.db_res.split(',') if server.db_res else None,
'passfile': server.passfile if server.passfile else None,
'passexec_cmd':
server.passexec_cmd if server.passexec_cmd else None,
'passexec_expiration':
server.passexec_expiration if server.passexec_expiration
else None,
'sslcert': sslcert,
'sslkey': sslkey,
'sslrootcert': sslrootcert,
'sslcrl': sslcrl,
'sslcompression': True if is_ssl and server.sslcompression
else False,
'service': server.service if server.service else None,
'connect_timeout':
server.connect_timeout if server.connect_timeout else 0,
'use_ssh_tunnel': use_ssh_tunnel,
'tunnel_host': tunnel_host,
'tunnel_port': tunnel_port,
@@ -1009,7 +1027,9 @@ class ServerNode(PGChildNodeView):
'kerberos_conn': bool(server.kerberos_conn),
'gss_authenticated': manager.gss_authenticated,
'gss_encrypted': manager.gss_encrypted,
'cloud_status': server.cloud_status
'cloud_status': server.cloud_status,
'connection_params': connection_params,
'connection_string': manager.connection_string
}
return ajax_response(response)
@@ -1017,11 +1037,7 @@ class ServerNode(PGChildNodeView):
@login_required
def create(self, gid):
"""Add a server node to the settings database"""
required_args = [
'name',
'db',
'sslmode',
]
required_args = ['name', 'db']
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
@@ -1057,8 +1073,11 @@ class ServerNode(PGChildNodeView):
).format(arg)
)
hostaddr = data.get('hostaddr')
if hostaddr and not is_valid_ipaddress(data['hostaddr']):
connection_params = self.convert_connection_parameter(
data.get('connection_params', []))
if 'hostaddr' in connection_params and \
not is_valid_ipaddress(connection_params['hostaddr']):
return make_json_response(
success=0,
status=400,
@@ -1066,7 +1085,10 @@ class ServerNode(PGChildNodeView):
)
# To check ssl configuration
is_ssl, data = self.check_ssl_fields(data)
is_ssl, connection_params = self.check_ssl_fields(connection_params)
# set the connection params again in the data
if 'connection_params' in data:
data['connection_params'] = connection_params
server = None
@@ -1076,26 +1098,18 @@ class ServerNode(PGChildNodeView):
servergroup_id=data.get('gid', gid),
name=data.get('name'),
host=data.get('host', None),
hostaddr=hostaddr,
port=data.get('port'),
maintenance_db=data.get('db', None),
username=data.get('username'),
save_password=1 if data.get('save_password', False) and
config.ALLOW_SAVE_PASSWORD else 0,
ssl_mode=data.get('sslmode'),
comment=data.get('comment', None),
role=data.get('role', None),
db_res=','.join(data['db_res'])
if 'db_res' in data else None,
sslcert=data.get('sslcert', None),
sslkey=data.get('sslkey', None),
sslrootcert=data.get('sslrootcert', None),
sslcrl=data.get('sslcrl', None),
sslcompression=1 if is_ssl and data['sslcompression'] else 0,
bgcolor=data.get('bgcolor', None),
fgcolor=data.get('fgcolor', None),
service=data.get('service', None),
connect_timeout=data.get('connect_timeout', 0),
use_ssh_tunnel=1 if data.get('use_ssh_tunnel', False) else 0,
tunnel_host=data.get('tunnel_host', None),
tunnel_port=data.get('tunnel_port', 22),
@@ -1104,10 +1118,10 @@ class ServerNode(PGChildNodeView):
False) else 0,
tunnel_identity_file=data.get('tunnel_identity_file', None),
shared=data.get('shared', None),
passfile=data.get('passfile', None),
passexec_cmd=data.get('passexec_cmd', None),
passexec_expiration=data.get('passexec_expiration', None),
kerberos_conn=1 if data.get('kerberos_conn', False) else 0,
connection_params=connection_params
)
db.session.add(server)
db.session.commit()
@@ -1131,10 +1145,9 @@ class ServerNode(PGChildNodeView):
have_password = True
password = data['password']
password = encrypt(password, crypt_key)
elif 'passfile' in data and data["passfile"] != '':
elif 'passfile' in data['connection_params'] and \
data['connection_params']['passfile'] != '':
passfile = data['passfile']
setattr(server, 'passfile', passfile)
db.session.commit()
if 'tunnel_password' in data and data["tunnel_password"] != '':
have_tunnel_password = True
@@ -1391,14 +1404,20 @@ class ServerNode(PGChildNodeView):
return internal_server_error(errormsg=str(e))
if 'password' not in data and (server.kerberos_conn is False or
server.kerberos_conn is None):
passfile_param = None
if hasattr(server, 'connection_params') and \
'passfile' in server.connection_params:
passfile_param = server.connection_params['passfile']
conn_passwd = getattr(conn, 'password', None)
if conn_passwd is None and not server.save_password and \
server.passfile is None and \
passfile_param is None and \
server.passexec_cmd is None and \
server.service is None:
prompt_password = True
elif server.passfile and server.passfile != '':
passfile = server.passfile
elif passfile_param and passfile_param != '':
passfile = passfile_param
else:
password = conn_passwd or server.password
else:
@@ -1657,8 +1676,11 @@ class ServerNode(PGChildNodeView):
# If there is no password found for the server
# then check for pgpass file
if not server.password and not manager.password and \
server.passfile and manager.passfile and \
server.passfile == manager.passfile:
hasattr(server, 'connection_params') and \
'passfile' in server.connection_params and \
manager.get_connection_param_value('passfile') and \
server.connection_params['passfile'] == \
manager.get_connection_param_value('passfile'):
is_passfile = True
# Check for password only if there is no pgpass file used
@@ -1859,8 +1881,11 @@ class ServerNode(PGChildNodeView):
)
if (not server.password or not manager.password) and \
server.passfile and manager.passfile and \
server.passfile == manager.passfile:
hasattr(server, 'connection_params') and \
'passfile' in server.connection_params and \
manager.get_connection_param_value('passfile') and \
server.connection_params['passfile'] == \
manager.get_connection_param_value('passfile'):
is_pgpass = True
return make_json_response(
success=1,

View File

@@ -2252,9 +2252,10 @@ class MViewNode(ViewNode, VacuumSettings):
manager.export_password_env(p.id)
# Check for connection timeout and if it is greater than 0
# then set the environment variable PGCONNECT_TIMEOUT.
if manager.connect_timeout > 0:
timeout = manager.get_connection_param_value('connect_timeout')
if timeout and timeout > 0:
env = dict()
env['PGCONNECT_TIMEOUT'] = str(manager.connect_timeout)
env['PGCONNECT_TIMEOUT'] = str(timeout)
p.set_env_variables(server, env=env)
else:
p.set_env_variables(server)

View File

@@ -29,6 +29,7 @@ define('pgadmin.node.server', [
type: 'server',
dialogHelp: url_for('help.static', {'filename': 'server_dialog.html'}),
label: gettext('Server'),
width: pgBrowser.stdW.md + 'px',
canDrop: function(node){
let serverOwner = node.user_id;
return !(serverOwner != current_user.id && !_.isUndefined(serverOwner));

View File

@@ -9,15 +9,12 @@
import gettext from 'sources/gettext';
import _ from 'lodash';
import {Address4, Address6} from 'ip-address';
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
import pgAdmin from 'sources/pgadmin';
import {default as supportedServers} from 'pgadmin.server.supported_servers';
import current_user from 'pgadmin.user_management.current_user';
import { isEmptyString } from 'sources/validators';
import VariableSchema from './variable.ui';
export default class ServerSchema extends BaseUISchema {
constructor(serverGroupOptions=[], userId=0, initValues={}) {
@@ -27,9 +24,7 @@ export default class ServerSchema extends BaseUISchema {
name: '',
bgcolor: '',
fgcolor: '',
sslmode: 'prefer',
host: '',
hostaddr: '',
port: 5432,
db: 'postgres',
username: current_user.name,
@@ -38,14 +33,8 @@ export default class ServerSchema extends BaseUISchema {
password: undefined,
save_password: false,
db_res: [],
passfile: undefined,
passexec: undefined,
passexec_expiration: undefined,
sslcompression: false,
sslcert: undefined,
sslkey: undefined,
sslrootcert: undefined,
sslcrl: undefined,
service: undefined,
use_ssh_tunnel: 0,
tunnel_host: undefined,
@@ -55,16 +44,22 @@ export default class ServerSchema extends BaseUISchema {
tunnel_password: undefined,
tunnel_authentication: false,
save_tunnel_password: false,
connect_timeout: 10,
connection_string: undefined,
connection_params: [
{'name': 'sslmode', 'value': 'prefer', 'keyword': 'sslmode'},
{'name': 'connect_timeout', 'value': 10, 'keyword': 'connect_timeout'}],
...initValues,
});
this.serverGroupOptions = serverGroupOptions;
this.paramSchema = new VariableSchema(this.getConnectionParameters(), null, null, ['name', 'keyword', 'value']);
this.userId = userId;
_.bindAll(this, 'isShared', 'isSSL');
_.bindAll(this, 'isShared');
}
get SSL_MODES() { return ['prefer', 'require', 'verify-ca', 'verify-full']; }
initialise(state) {
this.paramSchema.setAllReadOnly(this.isConnected(state));
}
isShared(state) {
return !this.isNew(state) && this.userId != current_user.id && state.shared;
@@ -74,16 +69,6 @@ export default class ServerSchema extends BaseUISchema {
return Boolean(state.connected);
}
isSSL(state) {
return this.SSL_MODES.indexOf(state.sslmode) == -1;
}
isValidLib() {
// older version of libpq do not support 'passfile' parameter in
// connect method, valid libpq must have version >= 100000
return pgAdmin.Browser.utils.pg_libpq_version < 100000;
}
get baseFields() {
let obj = this;
return [
@@ -148,8 +133,10 @@ export default class ServerSchema extends BaseUISchema {
{
id: 'comment', label: gettext('Comments'), type: 'multiline', group: null,
mode: ['properties', 'edit', 'create'],
},
{
}, {
id: 'connection_string', label: gettext('Connection String'), type: 'multiline',
group: gettext('Connection'), mode: ['properties'], readonly: true,
}, {
id: 'host', label: gettext('Host name/address'), type: 'text', group: gettext('Connection'),
mode: ['properties', 'edit', 'create'], disabled: obj.isShared,
depChange: (state)=>{
@@ -228,103 +215,13 @@ export default class ServerSchema extends BaseUISchema {
id: 'service', label: gettext('Service'), type: 'text',
mode: ['properties', 'edit', 'create'], readonly: obj.isConnected,
group: gettext('Connection'),
},
{
id: 'sslmode', label: gettext('SSL mode'), type: 'select', group: gettext('SSL'),
controlProps: {
allowClear: false,
},
mode: ['properties', 'edit', 'create'], disabled: obj.isConnected,
options: [
{label: gettext('Allow'), value: 'allow'},
{label: gettext('Prefer'), value: 'prefer'},
{label: gettext('Require'), value: 'require'},
{label: gettext('Disable'), value: 'disable'},
{label: gettext('Verify-CA'), value: 'verify-ca'},
{label: gettext('Verify-Full'), value: 'verify-full'},
],
},
{
id: 'sslcert', label: gettext('Client certificate'), type: 'file',
group: gettext('SSL'), mode: ['edit', 'create'],
disabled: obj.isSSL, readonly: obj.isConnected,
controlProps: {
dialogType: 'select_file', supportedTypes: ['*'],
},
deps: ['sslmode'],
},
{
id: 'sslkey', label: gettext('Client certificate key'), type: 'file',
group: gettext('SSL'), mode: ['edit', 'create'],
disabled: obj.isSSL, readonly: obj.isConnected,
controlProps: {
dialogType: 'select_file', supportedTypes: ['*'],
},
deps: ['sslmode'],
},{
id: 'sslrootcert', label: gettext('Root certificate'), type: 'file',
group: gettext('SSL'), mode: ['edit', 'create'],
disabled: obj.isSSL, readonly: obj.isConnected,
controlProps: {
dialogType: 'select_file', supportedTypes: ['*'],
},
deps: ['sslmode'],
},{
id: 'sslcrl', label: gettext('Certificate revocation list'), type: 'file',
group: gettext('SSL'), mode: ['edit', 'create'],
disabled: obj.isSSL, readonly: obj.isConnected,
controlProps: {
dialogType: 'select_file', supportedTypes: ['*'],
},
deps: ['sslmode'],
},
{
id: 'sslcompression', label: gettext('SSL compression?'), type: 'switch',
mode: ['edit', 'create'], group: gettext('SSL'),
disabled: obj.isSSL, readonly: obj.isConnected,
deps: ['sslmode'],
},
{
id: 'sslcert', label: gettext('Client certificate'), type: 'text',
group: gettext('SSL'), mode: ['properties'],
deps: ['sslmode'],
visible: function(state) {
let sslcert = state.sslcert;
return !_.isUndefined(sslcert) && !_.isNull(sslcert);
},
},{
id: 'sslkey', label: gettext('Client certificate key'), type: 'text',
group: gettext('SSL'), mode: ['properties'],
deps: ['sslmode'],
visible: function(state) {
let sslkey = state.sslkey;
return !_.isUndefined(sslkey) && !_.isNull(sslkey);
},
},{
id: 'sslrootcert', label: gettext('Root certificate'), type: 'text',
group: gettext('SSL'), mode: ['properties'],
deps: ['sslmode'],
visible: function(state) {
let sslrootcert = state.sslrootcert;
return !_.isUndefined(sslrootcert) && !_.isNull(sslrootcert);
},
},{
id: 'sslcrl', label: gettext('Certificate revocation list'), type: 'text',
group: gettext('SSL'), mode: ['properties'],
deps: ['sslmode'],
visible: function(state) {
let sslcrl = state.sslcrl;
return !_.isUndefined(sslcrl) && !_.isNull(sslcrl);
},
},{
id: 'sslcompression', label: gettext('SSL compression?'), type: 'switch',
mode: ['properties'], group: gettext('SSL'),
deps: ['sslmode'],
visible: function(state) {
return _.indexOf(obj.SSL_MODES, state.sslmode) != -1;
},
},
{
}, {
id: 'connection_params', label: gettext('Connection Parameters'),
type: 'collection', group: gettext('Parameters'),
schema: this.paramSchema, mode: ['edit', 'create'], uniqueCol: ['name'],
canAdd: (state)=> !obj.isConnected(state), canEdit: false,
canDelete: (state)=> !obj.isConnected(state),
}, {
id: 'use_ssh_tunnel', label: gettext('Use SSH tunneling'), type: 'switch',
mode: ['properties', 'edit', 'create'], group: gettext('SSH Tunnel'),
disabled: function() {
@@ -401,9 +298,6 @@ export default class ServerSchema extends BaseUISchema {
disabled: function(state) {
return (!current_user.allow_save_tunnel_password || !state.use_ssh_tunnel);
},
}, {
id: 'hostaddr', label: gettext('Host address'), type: 'text', group: gettext('Advanced'),
mode: ['properties', 'edit', 'create'], readonly: obj.isConnected,
},
{
id: 'db_res', label: gettext('DB restriction'), type: 'select', group: gettext('Advanced'),
@@ -411,22 +305,6 @@ export default class ServerSchema extends BaseUISchema {
mode: ['properties', 'edit', 'create'], readonly: obj.isConnected, controlProps: {
multiple: true, allowClear: false, creatable: true, noDropdown: true, placeholder: 'Specify the databases to be restrict...'},
},
{
id: 'passfile', label: gettext('Password file'), type: 'file',
group: gettext('Advanced'), mode: ['edit', 'create'],
disabled: obj.isValidLib, readonly: obj.isConnected,
controlProps: {
dialogType: 'select_file', supportedTypes: ['*'],
},
},
{
id: 'passfile', label: gettext('Password file'), type: 'text',
group: gettext('Advanced'), mode: ['properties'],
visible: function(state) {
let passfile = state.passfile;
return !_.isUndefined(passfile) && !_.isNull(passfile);
},
},
{
id: 'passexec_cmd', label: gettext('Password exec command'), type: 'text',
group: gettext('Advanced'),
@@ -439,12 +317,6 @@ export default class ServerSchema extends BaseUISchema {
visible: function(state) {
return !_.isEmpty(state.passexec_cmd);
},
},
{
id: 'connect_timeout', label: gettext('Connection timeout (seconds)'),
type: 'int', group: gettext('Advanced'),
mode: ['properties', 'edit', 'create'], readonly: obj.isConnected,
min: 0,
}
];
}
@@ -453,30 +325,12 @@ export default class ServerSchema extends BaseUISchema {
let errmsg = null;
if (isEmptyString(state.service)) {
errmsg = gettext('Either Host name, Address or Service must be specified.');
if(isEmptyString(state.host) && isEmptyString(state.hostaddr)) {
errmsg = gettext('Either Host name or Service must be specified.');
if(isEmptyString(state.host)) {
setError('host', errmsg);
return true;
} else {
setError('host', null);
setError('hostaddr', null);
}
/* IP address validate */
if (state.hostaddr) {
try {
new Address4(state.hostaddr);
} catch(e) {
try {
new Address6(state.hostaddr);
} catch(ex) {
errmsg = gettext('Host address must be valid IPv4 or IPv6 address.');
setError('hostaddr', errmsg);
return true;
}
}
} else {
setError('hostaddr', null);
}
/* Hostname, IP address validate */
@@ -507,7 +361,7 @@ export default class ServerSchema extends BaseUISchema {
setError('port', null);
}
} else {
_.each(['host', 'hostaddr', 'db', 'username', 'port'], (item) => {
_.each(['host', 'db', 'username', 'port'], (item) => {
setError(item, null);
});
}
@@ -549,4 +403,92 @@ export default class ServerSchema extends BaseUISchema {
}
return false;
}
getConnectionParameters() {
return [{
'value': 'hostaddr', 'label': gettext('Host address'), 'vartype': 'string'
}, {
'value': 'passfile', 'label': gettext('Password file'), 'vartype': 'file'
}, {
'value': 'channel_binding', 'label': gettext('Channel binding'), 'vartype': 'enum',
'enumvals': [gettext('prefer'), gettext('require'), gettext('disable')],
'min_server_version': '13'
}, {
'value': 'connect_timeout', 'label': gettext('Connection timeout (seconds)'), 'vartype': 'integer'
}, {
'value': 'client_encoding', 'label': gettext('Client encoding'), 'vartype': 'string'
}, {
'value': 'options', 'label': gettext('Options'), 'vartype': 'string'
}, {
'value': 'application_name', 'label': gettext('Application name'), 'vartype': 'string'
}, {
'value': 'fallback_application_name', 'label': gettext('Fallback application name'), 'vartype': 'string'
}, {
'value': 'keepalives', 'label': gettext('Keepalives'), 'vartype': 'integer'
}, {
'value': 'keepalives_idle', 'label': gettext('Keepalives idle (seconds)'), 'vartype': 'integer'
}, {
'value': 'keepalives_interval', 'label': gettext('Keepalives interval (seconds)'), 'vartype': 'integer'
}, {
'value': 'keepalives_count', 'label': gettext('Keepalives count'), 'vartype': 'integer'
}, {
'value': 'tcp_user_timeout', 'label': gettext('TCP user timeout (milliseconds)'), 'vartype': 'integer',
'min_server_version': '12'
}, {
'value': 'tty', 'label': gettext('TTY'), 'vartype': 'string',
'max_server_version': '13'
}, {
'value': 'replication', 'label': gettext('Replication'), 'vartype': 'enum',
'enumvals': [gettext('on'), gettext('off'), gettext('database')],
'min_server_version': '11'
}, {
'value': 'gssencmode', 'label': gettext('GSS encmode'), 'vartype': 'enum',
'enumvals': [gettext('prefer'), gettext('require'), gettext('disable')],
'min_server_version': '12'
}, {
'value': 'sslmode', 'label': gettext('SSL mode'), 'vartype': 'enum',
'enumvals': [gettext('allow'), gettext('prefer'), gettext('require'),
gettext('disable'), gettext('verify-ca'), gettext('verify-full')]
}, {
'value': 'sslcompression', 'label': gettext('SSL compression?'), 'vartype': 'bool',
}, {
'value': 'sslcert', 'label': gettext('Client certificate'), 'vartype': 'file'
}, {
'value': 'sslkey', 'label': gettext('Client certificate key'), 'vartype': 'file'
}, {
'value': 'sslpassword', 'label': gettext('SSL password'), 'vartype': 'string',
'min_server_version': '13'
}, {
'value': 'sslrootcert', 'label': gettext('Root certificate'), 'vartype': 'file'
}, {
'value': 'sslcrl', 'label': gettext('Certificate revocation list'), 'vartype': 'file',
}, {
'value': 'sslcrldir', 'label': gettext('Certificate revocation list directory'), 'vartype': 'file',
'min_server_version': '14'
}, {
'value': 'sslsni', 'label': gettext('Server name indication'), 'vartype': 'bool',
'min_server_version': '14'
}, {
'value': 'requirepeer', 'label': gettext('Require peer'), 'vartype': 'string',
}, {
'value': 'ssl_min_protocol_version', 'label': gettext('SSL min protocol version'),
'vartype': 'enum', 'min_server_version': '13',
'enumvals': [gettext('TLSv1'), gettext('TLSv1.1'), gettext('TLSv1.2'),
gettext('TLSv1.3')]
}, {
'value': 'ssl_max_protocol_version', 'label': gettext('SSL max protocol version'),
'vartype': 'enum', 'min_server_version': '13',
'enumvals': [gettext('TLSv1'), gettext('TLSv1.1'), gettext('TLSv1.2'),
gettext('TLSv1.3')]
}, {
'value': 'krbsrvname', 'label': gettext('Kerberos service name'), 'vartype': 'string',
}, {
'value': 'gsslib', 'label': gettext('GSS library'), 'vartype': 'string',
}, {
'value': 'target_session_attrs', 'label': gettext('Target session attribute'),
'vartype': 'enum',
'enumvals': [gettext('any'), gettext('read-write'), gettext('read-only'),
gettext('primary'), gettext('standby'), gettext('prefer-standby')]
}];
}
}

View File

@@ -51,12 +51,18 @@ export default class VariableSchema extends BaseUISchema {
value: undefined,
role: null,
database: null,
keyword: null,
});
this.vnameOptions = vnameOptions;
this.databaseOptions = databaseOptions;
this.roleOptions = roleOptions;
this.varTypes = {};
this.keys = keys;
this.allReadOnly = false;
}
setAllReadOnly(isReadOnly) {
this.allReadOnly = isReadOnly;
}
setVarTypes(options) {
@@ -67,6 +73,19 @@ export default class VariableSchema extends BaseUISchema {
});
}
getPlaceHolderMsg(variable) {
let msg = '';
if (variable?.min_server_version && variable?.max_server_version) {
msg = gettext('%s <= Supported version >= %s', variable?.max_server_version, variable?.min_server_version);
} else if (variable?.min_server_version) {
msg = gettext('Supported version >= %s', variable?.min_server_version);
} else if (variable?.max_server_version) {
msg = gettext('Supported version <= %s', variable?.max_server_version);
}
return msg;
}
getValueFieldProps(variable) {
switch(variable?.vartype) {
case 'bool':
@@ -74,17 +93,44 @@ export default class VariableSchema extends BaseUISchema {
case 'enum':
return {
cell: 'select',
options: (variable.enumvals || []).map((val)=>({
options: (variable.enumvals || []).map((val)=>(typeof(val)=='string' ? {
label: val,
value: val
}))
}: val)),
controlProps: {
placeholder: this.getPlaceHolderMsg(variable)
}
};
case 'integer':
return 'int';
return {
cell: 'int',
controlProps: {
placeholder: this.getPlaceHolderMsg(variable)
}
};
case 'real':
return 'numeric';
return {
cell: 'numeric',
controlProps: {
placeholder: this.getPlaceHolderMsg(variable)
}
};
case 'string':
return 'text';
return {
cell: 'text',
controlProps: {
placeholder: this.getPlaceHolderMsg(variable)
}
};
case 'file':
return {
cell: 'file',
controlProps: {
dialogType: 'select_file',
supportedTypes: ['*'],
placeholder: this.getPlaceHolderMsg(variable)
}
};
default:
return '';
}
@@ -99,8 +145,8 @@ export default class VariableSchema extends BaseUISchema {
},
{
id: 'name', label: gettext('Name'), type:'text',
readonly: function(state) {
return !obj.isNew(state);
editable: function(state) {
return obj.isNew(state) || !obj.allReadOnly;
},
cell: ()=>({
cell: 'select',
@@ -109,9 +155,16 @@ export default class VariableSchema extends BaseUISchema {
controlProps: { allowClear: false },
}),
},
{
id: 'keyword', label: gettext('Keyword'), type: '', cell: '',
deps: ['name'], minWidth: 25,
depChange: (state, source, topState, actionObj)=>{
return { keyword: actionObj.value };
}
},
{
id: 'value', label: gettext('Value'), type: 'text',
deps: ['name'],
deps: ['name'], editable: !obj.allReadOnly,
depChange: (state, source)=>{
if(source[source.length-1] == 'name') {
let variable = this.varTypes[state.name];

View File

@@ -207,21 +207,6 @@
"status_code": 200
}
},
{
"name": "Add server with advanced properties",
"url": "/browser/server/obj/",
"is_positive_test": true,
"owner_server": true,
"test_data": {
"passfile": "test.pgpass",
"hostaddr": "127.0.0.1"
},
"mocking_required": false,
"mock_data": {},
"expected_data": {
"status_code": 200
}
},
{
"name": "Add server with background/foreground color",
"url": "/browser/server/obj/",
@@ -805,22 +790,6 @@
"status_code": 410
}
},
{
"name": "Update server with incorrect hostaddr",
"url": "/browser/server/obj/",
"is_positive_test": true,
"test_data": {
"comment": "PLACE_HOLDER",
"hostaddr": "PLACE_HOLDER",
"db_res": "PLACE_HOLDER",
"id": "PLACE_HOLDER"
},
"mocking_required": false,
"mock_data": {},
"expected_data": {
"status_code": 400
}
},
{
"name": "update a server , make server shared",
"url": "/browser/server/obj/",
@@ -938,21 +907,6 @@
"status_code": 200
}
},
{
"name": "update advanced properties of server",
"url": "/browser/server/obj/",
"is_positive_test": true,
"owner_server": true,
"test_data": {
"passfile": "test_01.pgpass",
"hostaddr": "127.0.0.1"
},
"mocking_required": false,
"mock_data": {},
"expected_data": {
"status_code": 200
}
},
{
"name": "remove ssl properties from server",
"url": "/browser/server/obj/",
@@ -972,21 +926,6 @@
"status_code": 200
}
},
{
"name": "remove advanced properties from server",
"url": "/browser/server/obj/",
"is_positive_test": true,
"owner_server": true,
"test_data": {
"passfile": "",
"hostaddr": ""
},
"mocking_required": false,
"mock_data": {},
"expected_data": {
"status_code": 200
}
},
{
"name": "Update server with background/foreground color",
"url": "/browser/server/obj/",
@@ -1046,22 +985,6 @@
"status_code": 410
}
},
{
"name": "Update server with incorrect hostaddr",
"url": "/browser/server/obj/",
"is_positive_test": true,
"test_data": {
"comment": "PLACE_HOLDER",
"hostaddr": "PLACE_HOLDER",
"db_res": "PLACE_HOLDER",
"id": "PLACE_HOLDER"
},
"mocking_required": false,
"mock_data": {},
"expected_data": {
"status_code": 400
}
},
{
"name": "update a server , make server shared",
"url": "/browser/server/obj/",

View File

@@ -20,11 +20,11 @@ def create_server(server, SERVER_GROUP):
server['shared'] = False
server_details = (1, SERVER_GROUP, server['name'], server['host'],
server['port'], server['db'], server['username'],
server['role'], server['sslmode'], server['comment'],
server['role'], server['comment'],
server['shared'])
cur.execute('INSERT INTO server (user_id, servergroup_id, name, host, '
'port, maintenance_db, username, role, ssl_mode,'
' comment, shared) VALUES (?,?,?,?,?,?,?,?,?,?,?)',
'port, maintenance_db, username, role,'
' comment, shared) VALUES (?,?,?,?,?,?,?,?,?,?)',
server_details)
server_id = cur.lastrowid
conn.commit()