mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
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:
@@ -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));
|
||||
|
||||
@@ -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')]
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
Reference in New Issue
Block a user