mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2024-11-22 00:37:36 -06:00
Port Subscriptions node to react. Fixes #6634
This commit is contained in:
parent
8dac933a66
commit
1f430227aa
@ -10,12 +10,13 @@
|
||||
import { getNodeAjaxOptions, getNodeListByName } from '../../../../../../static/js/node_ajax';
|
||||
import LanguageSchema from './language.ui';
|
||||
import { getNodePrivilegeRoleSchema } from '../../../../static/js/privilege.ui';
|
||||
import _ from 'lodash';
|
||||
|
||||
define('pgadmin.node.language', [
|
||||
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
|
||||
'sources/gettext', 'sources/url_for', 'jquery',
|
||||
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform',
|
||||
'pgadmin.browser.collection', 'pgadmin.browser.server.privilege',
|
||||
], function(gettext, url_for, $, _, pgAdmin, pgBrowser, Backform) {
|
||||
], function(gettext, url_for, $, pgAdmin, pgBrowser, Backform) {
|
||||
|
||||
// Extend the browser's collection class for languages collection
|
||||
if (!pgBrowser.Nodes['coll-language']) {
|
||||
|
@ -10,6 +10,7 @@ import gettext from 'sources/gettext';
|
||||
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
|
||||
import { isEmptyString } from 'sources/validators';
|
||||
import SecLabelSchema from '../../../../static/js/sec_label.ui';
|
||||
import _ from 'lodash';
|
||||
|
||||
export default class LanguageSchema extends BaseUISchema {
|
||||
constructor(getPrivilegeRoleSchema, fieldOptions={}, node_info, initValues) {
|
||||
@ -46,7 +47,7 @@ export default class LanguageSchema extends BaseUISchema {
|
||||
|
||||
}
|
||||
isDisabled(state){
|
||||
if (this.templateList.some(code => code.tmplname === state.name)){
|
||||
if (this.templateList.some(template => template.tmplname === state.name)){
|
||||
this.isTemplate = false;
|
||||
return true;
|
||||
}else{
|
||||
|
@ -356,7 +356,7 @@ class SubscriptionView(PGChildNodeView, SchemaDiffObjectCompare):
|
||||
|
||||
def _fetch_properties(self, did, subid):
|
||||
"""
|
||||
This function fetch the properties of the extension.
|
||||
This function fetch the properties of the subscription.
|
||||
:param did:
|
||||
:param subid:
|
||||
:return:
|
||||
@ -372,11 +372,6 @@ class SubscriptionView(PGChildNodeView, SchemaDiffObjectCompare):
|
||||
|
||||
if len(res['rows']) == 0:
|
||||
return False, gone(self._NOT_FOUND_PUB_INFORMATION)
|
||||
if 'cur_pub' in res['rows'][0]:
|
||||
res['rows'][0]['cur_pub'] = ", ".join(str(elem) for elem in
|
||||
res['rows'][0]['cur_pub'])
|
||||
res['rows'][0]['pub'] = ", ".join(str(elem) for elem in
|
||||
res['rows'][0]['pub'])
|
||||
|
||||
return True, res['rows'][0]
|
||||
|
||||
@ -469,9 +464,6 @@ class SubscriptionView(PGChildNodeView, SchemaDiffObjectCompare):
|
||||
)
|
||||
|
||||
try:
|
||||
data['pub'] = json.loads(
|
||||
data['pub'], encoding='utf-8'
|
||||
)
|
||||
|
||||
sql = render_template("/".join([self.template_path,
|
||||
self._CREATE_SQL]),
|
||||
@ -676,36 +668,38 @@ class SubscriptionView(PGChildNodeView, SchemaDiffObjectCompare):
|
||||
passfile = connection_details['passfile'] if \
|
||||
'passfile' in connection_details and \
|
||||
connection_details['passfile'] != '' else None
|
||||
try:
|
||||
conn = psycopg2.connect(
|
||||
host=connection_details['host'],
|
||||
database=connection_details['db'],
|
||||
user=connection_details['username'],
|
||||
password=connection_details[
|
||||
'password'] if 'password' in connection_details else None,
|
||||
port=connection_details['port'] if
|
||||
connection_details['port'] else None,
|
||||
passfile=get_complete_file_path(passfile),
|
||||
connect_timeout=connection_details['connect_timeout'] if
|
||||
'connect_timeout' in connection_details and
|
||||
connection_details['connect_timeout'] else 0,
|
||||
sslmode=connection_details['sslmode'],
|
||||
sslcert=get_complete_file_path(connection_details['sslcert']),
|
||||
sslkey=get_complete_file_path(connection_details['sslkey']),
|
||||
sslrootcert=get_complete_file_path(
|
||||
connection_details['sslrootcert']),
|
||||
sslcompression=True if connection_details[
|
||||
'sslcompression'] else False,
|
||||
)
|
||||
# create a cursor
|
||||
cur = conn.cursor()
|
||||
cur.execute('SELECT pubname from pg_catalog.pg_publication')
|
||||
|
||||
conn = psycopg2.connect(
|
||||
host=connection_details['host'],
|
||||
database=connection_details['db'],
|
||||
user=connection_details['username'],
|
||||
password=connection_details[
|
||||
'password'] if 'password' in connection_details else None,
|
||||
port=connection_details['port'] if
|
||||
connection_details['port'] else None,
|
||||
passfile=get_complete_file_path(passfile),
|
||||
connect_timeout=connection_details['connect_timeout'] if
|
||||
'connect_timeout' in connection_details and
|
||||
connection_details['connect_timeout'] else 0,
|
||||
sslmode=connection_details['sslmode'],
|
||||
sslcert=get_complete_file_path(connection_details['sslcert']),
|
||||
sslkey=get_complete_file_path(connection_details['sslkey']),
|
||||
sslrootcert=get_complete_file_path(
|
||||
connection_details['sslrootcert']),
|
||||
sslcompression=True if connection_details[
|
||||
'sslcompression'] else False,
|
||||
)
|
||||
# create a cursor
|
||||
cur = conn.cursor()
|
||||
cur.execute('SELECT pubname from pg_catalog.pg_publication')
|
||||
publications = cur.fetchall()
|
||||
# Close the connection
|
||||
conn.close()
|
||||
|
||||
publications = cur.fetchall()
|
||||
# Close the connection
|
||||
conn.close()
|
||||
|
||||
return publications
|
||||
return publications, True
|
||||
except Exception as error:
|
||||
return error, False
|
||||
|
||||
@check_precondition
|
||||
def get_publications(self, gid, sid, did, *args, **kwargs):
|
||||
@ -733,19 +727,27 @@ class SubscriptionView(PGChildNodeView, SchemaDiffObjectCompare):
|
||||
if arg not in url_params and arg in params:
|
||||
url_params[arg] = params[arg]
|
||||
|
||||
res = self.get_connection(url_params)
|
||||
res, status = self.get_connection(url_params)
|
||||
|
||||
if status:
|
||||
result = []
|
||||
for pub in res:
|
||||
result.append({
|
||||
"value": pub[0],
|
||||
"label": pub[0]
|
||||
})
|
||||
return make_json_response(
|
||||
data=result,
|
||||
status=200
|
||||
)
|
||||
else:
|
||||
result = res.args[0]
|
||||
return make_json_response(
|
||||
errormsg=result,
|
||||
status=200
|
||||
)
|
||||
|
||||
result = []
|
||||
for pub in res:
|
||||
result.append({
|
||||
"value": pub[0],
|
||||
"label": pub[0]
|
||||
})
|
||||
|
||||
return make_json_response(
|
||||
data=result,
|
||||
status=200
|
||||
)
|
||||
|
||||
@check_precondition
|
||||
def sql(self, gid, sid, did, subid, json_resp=True):
|
||||
|
@ -6,13 +6,16 @@
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
import { getNodeListByName } from '../../../../../../static/js/node_ajax';
|
||||
import SubscriptionSchema from './subscription.ui';
|
||||
import getApiInstance from '../../../../../../../static/js/api_instance';
|
||||
import { pgAlertify } from '../../../../../../../../pgadmin/static/js/helpers/legacyConnector';
|
||||
import _ from 'lodash';
|
||||
|
||||
define('pgadmin.node.subscription', [
|
||||
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
|
||||
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform',
|
||||
'sources/browser/server_groups/servers/model_validation', 'pgadmin.alertifyjs', 'pgadmin.browser.collection',
|
||||
], function(gettext, url_for, $, _, pgAdmin, pgBrowser, Backform, modelValidation, Alertify) {
|
||||
var SSL_MODES = ['prefer', 'require', 'verify-ca', 'verify-full'];
|
||||
'sources/gettext', 'sources/url_for', 'jquery',
|
||||
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform', 'pgadmin.browser.collection',
|
||||
], function(gettext, url_for, $, pgAdmin, pgBrowser, Backform) {
|
||||
|
||||
// Extend the browser's collection class for subscriptions collection
|
||||
if (!pgBrowser.Nodes['coll-subscription']) {
|
||||
@ -137,406 +140,23 @@ define('pgadmin.node.subscription', [
|
||||
return false;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'host', label: gettext('Host name/address'), type: 'text', group: gettext('Connection'),
|
||||
mode: ['properties', 'edit', 'create'],
|
||||
control: Backform.InputControl.extend({
|
||||
onChange: function() {
|
||||
Backform.InputControl.prototype.onChange.apply(this, arguments);
|
||||
if (!this.model || !this.model.changed) {
|
||||
this.model.inform_text = undefined;
|
||||
return;
|
||||
}
|
||||
},
|
||||
}),
|
||||
},{
|
||||
id: 'port', label: gettext('Port'), type: 'int', group: gettext('Connection'),
|
||||
mode: ['properties', 'edit', 'create'], min: 1, max: 65535,
|
||||
control: Backform.InputControl.extend({
|
||||
onChange: function() {
|
||||
Backform.InputControl.prototype.onChange.apply(this, arguments);
|
||||
if (!this.model || !this.model.changed) {
|
||||
this.model.inform_text = undefined;
|
||||
return;
|
||||
}
|
||||
},
|
||||
}),
|
||||
},{
|
||||
id: 'username', label: gettext('Username'), type: 'text', group: gettext('Connection'),
|
||||
mode: ['properties', 'edit', 'create'],
|
||||
control: Backform.InputControl.extend({
|
||||
onChange: function() {
|
||||
Backform.InputControl.prototype.onChange.apply(this, arguments);
|
||||
if (!this.model || !this.model.changed) {
|
||||
this.model.inform_text = undefined;
|
||||
return;
|
||||
}
|
||||
},
|
||||
}),
|
||||
},{
|
||||
id: 'password', label: gettext('Password'), type: 'password', maxlength: null,
|
||||
group: gettext('Connection'), control: 'input', mode: ['create', 'edit'], deps: ['connect_now'],
|
||||
},{
|
||||
id: 'db', label: gettext('Database'), type: 'text', group: gettext('Connection'),
|
||||
mode: ['properties', 'edit', 'create'],
|
||||
},
|
||||
{
|
||||
id: 'connect_timeout', label: gettext('Connection timeout'), type: 'text',
|
||||
mode: ['properties', 'edit', 'create'],
|
||||
group: gettext('Connection'),
|
||||
},
|
||||
{
|
||||
id: 'passfile', label: gettext('Passfile'), type: 'text', group: gettext('Connection'),
|
||||
mode: ['properties', 'edit', 'create'],
|
||||
},
|
||||
{
|
||||
id: 'pub', label: gettext('Publication'), type: 'text', group: gettext('Connection'),
|
||||
mode: ['properties'],
|
||||
},
|
||||
{
|
||||
id: 'cur_pub', label: gettext('Current publication'), type: 'text', group: gettext('Connection'),
|
||||
mode: ['edit'], disabled:true,
|
||||
},
|
||||
{
|
||||
id: 'pub', label: gettext('Publication'), type: 'array', select2: { allowClear: true, multiple: true, width: '92%'},
|
||||
group: gettext('Connection'), mode: ['create', 'edit'], controlsClassName: 'pgadmin-controls pg-el-sm-11 pg-el-12',
|
||||
deps: ['all_table', 'host', 'port', 'username', 'db', 'password'], disabled: 'isAllConnectionDataEnter',
|
||||
helpMessage: gettext('Click the refresh button to get the publications'),
|
||||
control: Backform.Select2Control.extend({
|
||||
defaults: _.extend(Backform.Select2Control.prototype.defaults, {
|
||||
select2: {
|
||||
allowClear: true,
|
||||
selectOnBlur: true,
|
||||
tags: true,
|
||||
placeholder: gettext('Select an item...'),
|
||||
width: 'style',
|
||||
},
|
||||
}),
|
||||
template: _.template([
|
||||
'<label class="<%=Backform.controlLabelClassName%>" for="<%=cId%>"><%=label%></label>',
|
||||
'<div class="<%=Backform.controlsClassName%>">',
|
||||
'<div class="input-group">',
|
||||
' <select title="<%=name%>" id="<%=cId%>" class="<%=Backform.controlClassName%> <%=extraClasses.join(\' \')%>"',
|
||||
' name="<%=name%>" value="<%-value%>" <%=disabled ? "disabled" : ""%> <%=readonly ? "disabled" : ""%>',
|
||||
' <%=required ? "required" : ""%><%= select2.multiple ? " multiple>" : ">" %>',
|
||||
' <%=select2.first_empty ? " <option></option>" : ""%>',
|
||||
' <% for (var i=0; i < options.length; i++) {%>',
|
||||
' <% var option = options[i]; %>',
|
||||
' <option ',
|
||||
' <% if (option.image) { %> data-image=<%=option.image%> <%}%>',
|
||||
' value=<%- formatter.fromRaw(option.value) %>',
|
||||
' <% if (option.selected) {%>selected="selected"<%} else {%>',
|
||||
' <% if (!select2.multiple && option.value === rawValue) {%>selected="selected"<%}%>',
|
||||
' <% if (select2.multiple && rawValue && rawValue.indexOf(option.value) != -1){%>selected="selected" data-index="rawValue.indexOf(option.value)"<%}%>',
|
||||
' <%}%>',
|
||||
' <%= disabled ? "disabled" : ""%> <%=readonly ? "disabled" : ""%>><%-option.label%></option>',
|
||||
' <%}%>',
|
||||
' </select>',
|
||||
'<div class="input-group-append">',
|
||||
'<button class="btn btn-primary-icon fa fa-sync get_publication" <%=disabled ? "disabled" : ""%> <%=readonly ? "disabled" : ""%> aria-hidden="true" aria-label="' + gettext('Get Publication') + '" title="' + gettext('Get Publication') + '"></button>',
|
||||
'</div>',
|
||||
'</div>',
|
||||
'<% if (helpMessage && helpMessage.length) { %>',
|
||||
'<span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>',
|
||||
'<% } %>',
|
||||
'</div>',
|
||||
].join('\n')),
|
||||
|
||||
events: _.extend({}, Backform.Select2Control.prototype.events(), {
|
||||
'click .get_publication': 'getPublication',
|
||||
}),
|
||||
|
||||
render: function(){
|
||||
return Backform.Select2Control.prototype.render.apply(this, arguments);
|
||||
},
|
||||
|
||||
getPublication: function() {
|
||||
var self = this;
|
||||
var publication_url = pgBrowser.Nodes['database'].generate_url.apply(
|
||||
pgBrowser.Nodes['subscription'], [
|
||||
null, 'get_publications', this.field.get('node_data'), null,
|
||||
this.field.get('node_info'), pgBrowser.Nodes['database'].url_jump_after_node,
|
||||
]);
|
||||
var result = '';
|
||||
|
||||
$.ajax({
|
||||
url: publication_url,
|
||||
type: 'GET',
|
||||
data: self.model.toJSON(true, 'GET'),
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
})
|
||||
.done(function(res) {
|
||||
result = res.data;
|
||||
self.field.set('options', result);
|
||||
Backform.Select2Control.prototype.render.apply(self, arguments);
|
||||
|
||||
var transform = self.field.get('transform') || self.defaults.transform;
|
||||
if (transform && _.isFunction(transform)) {
|
||||
self.field.set('options', transform.bind(self, result));
|
||||
} else {
|
||||
self.field.set('options', result);
|
||||
}
|
||||
Alertify.info(
|
||||
gettext('Publication fetched successfully.')
|
||||
);
|
||||
|
||||
|
||||
})
|
||||
.fail(function(res) {
|
||||
Alertify.alert(
|
||||
gettext('Check connection?'),
|
||||
gettext(res.responseJSON.errormsg)
|
||||
);
|
||||
});
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'sslmode', label: gettext('SSL mode'), control: 'select2', group: gettext('SSL'),
|
||||
select2: {
|
||||
allowClear: false,
|
||||
minimumResultsForSearch: Infinity,
|
||||
},
|
||||
mode: ['properties', 'edit', 'create'],
|
||||
'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: 'text',
|
||||
group: gettext('SSL'), mode: ['edit', 'create'],
|
||||
disabled: 'isSSL', control: Backform.FileControl,
|
||||
dialog_type: 'select_file', supp_types: ['*'],
|
||||
deps: ['sslmode'],
|
||||
},{
|
||||
id: 'sslkey', label: gettext('Client certificate key'), type: 'text',
|
||||
group: gettext('SSL'), mode: ['edit', 'create'],
|
||||
disabled: 'isSSL', control: Backform.FileControl,
|
||||
dialog_type: 'select_file', supp_types: ['*'],
|
||||
deps: ['sslmode'],
|
||||
},{
|
||||
id: 'sslrootcert', label: gettext('Root certificate'), type: 'text',
|
||||
group: gettext('SSL'), mode: ['edit', 'create'],
|
||||
disabled: 'isSSL', control: Backform.FileControl,
|
||||
dialog_type: 'select_file', supp_types: ['*'],
|
||||
deps: ['sslmode'],
|
||||
},{
|
||||
id: 'sslcrl', label: gettext('Certificate revocation list'), type: 'text',
|
||||
group: gettext('SSL'), mode: ['edit', 'create'],
|
||||
disabled: 'isSSL', control: Backform.FileControl,
|
||||
dialog_type: 'select_file', supp_types: ['*'],
|
||||
deps: ['sslmode'],
|
||||
},{
|
||||
id: 'sslcompression', label: gettext('SSL compression?'), type: 'switch',
|
||||
mode: ['edit', 'create'], group: gettext('SSL'),
|
||||
'options': {'size': 'mini'},
|
||||
deps: ['sslmode'], disabled: 'isSSL',
|
||||
},{
|
||||
id: 'sslcert', label: gettext('Client certificate'), type: 'text',
|
||||
group: gettext('SSL'), mode: ['properties'],
|
||||
deps: ['sslmode'],
|
||||
visible: function(model) {
|
||||
var sslcert = model.get('sslcert');
|
||||
return !_.isUndefined(sslcert) && !_.isNull(sslcert);
|
||||
},
|
||||
},{
|
||||
id: 'sslkey', label: gettext('Client certificate key'), type: 'text',
|
||||
group: gettext('SSL'), mode: ['properties'],
|
||||
deps: ['sslmode'],
|
||||
visible: function(model) {
|
||||
var sslkey = model.get('sslkey');
|
||||
return !_.isUndefined(sslkey) && !_.isNull(sslkey);
|
||||
},
|
||||
},{
|
||||
id: 'sslrootcert', label: gettext('Root certificate'), type: 'text',
|
||||
group: gettext('SSL'), mode: ['properties'],
|
||||
deps: ['sslmode'],
|
||||
visible: function(model) {
|
||||
var sslrootcert = model.get('sslrootcert');
|
||||
return !_.isUndefined(sslrootcert) && !_.isNull(sslrootcert);
|
||||
},
|
||||
},{
|
||||
id: 'sslcrl', label: gettext('Certificate revocation list'), type: 'text',
|
||||
group: gettext('SSL'), mode: ['properties'],
|
||||
deps: ['sslmode'],
|
||||
visible: function(model) {
|
||||
var sslcrl = model.get('sslcrl');
|
||||
return !_.isUndefined(sslcrl) && !_.isNull(sslcrl);
|
||||
},
|
||||
},{
|
||||
id: 'sslcompression', label: gettext('SSL compression?'), type: 'switch',
|
||||
mode: ['properties'], group: gettext('SSL'),
|
||||
'options': {'size': 'mini'},
|
||||
deps: ['sslmode'], visible: function(model) {
|
||||
var sslmode = model.get('sslmode');
|
||||
return _.indexOf(SSL_MODES, sslmode) != -1;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'copy_data_after_refresh', label: gettext('Copy data?'),
|
||||
type: 'switch', mode: ['edit'],
|
||||
group: gettext('With'),
|
||||
readonly: 'isRefresh', deps :['refresh_pub'],
|
||||
helpMessage: gettext('Specifies whether the existing data in the publications that are being subscribed to should be copied once the replication starts.'),
|
||||
},
|
||||
{
|
||||
id: 'copy_data', label: gettext('Copy data?'),
|
||||
type: 'switch', mode: ['create'],
|
||||
group: gettext('With'),
|
||||
readonly: 'isConnect', deps :['connect'],
|
||||
helpMessage: gettext('Specifies whether the existing data in the publications that are being subscribed to should be copied once the replication starts.'),
|
||||
},
|
||||
{
|
||||
id: 'create_slot', label: gettext('Create slot?'),
|
||||
type: 'switch', mode: ['create'],
|
||||
group: gettext('With'),
|
||||
disabled: 'isSameDB',
|
||||
readonly: 'isConnect', deps :['connect', 'host', 'port'],
|
||||
helpMessage: gettext('Specifies whether the command should create the replication slot on the publisher.This field will be disabled and set to false if subscription connects to same database.Otherwise, the CREATE SUBSCRIPTION call will hang.'),
|
||||
|
||||
},
|
||||
{
|
||||
id: 'enabled', label: gettext('Enabled?'),
|
||||
type: 'switch', mode: ['create','edit', 'properties'],
|
||||
type: 'switch', mode: ['properties'],
|
||||
group: gettext('With'),
|
||||
readonly: 'isConnect', deps :['connect'],
|
||||
helpMessage: gettext('Specifies whether the subscription should be actively replicating, or whether it should be just setup but not started yet.'),
|
||||
},
|
||||
{
|
||||
id: 'refresh_pub', label: gettext('Refresh publication?'),
|
||||
type: 'switch', mode: ['edit'],
|
||||
group: gettext('With'),
|
||||
helpMessage: gettext('Fetch missing table information from publisher.'),
|
||||
deps:['enabled'], disabled: function(m){
|
||||
if (m.get('enabled'))
|
||||
return false;
|
||||
setTimeout( function() {
|
||||
m.set('refresh_pub', false);
|
||||
}, 10);
|
||||
return true;
|
||||
},
|
||||
},{
|
||||
id: 'connect', label: gettext('Connect?'),
|
||||
type: 'switch', mode: ['create'],
|
||||
group: gettext('With'),
|
||||
disabled: 'isDisable', deps:['enabled', 'create_slot', 'copy_data'],
|
||||
helpMessage: gettext('Specifies whether the CREATE SUBSCRIPTION should connect to the publisher at all. Setting this to false will change default values of enabled, create_slot and copy_data to false.'),
|
||||
},
|
||||
{
|
||||
id: 'slot_name', label: gettext('Slot name'),
|
||||
type: 'text', mode: ['create','edit', 'properties'],
|
||||
group: gettext('With'),
|
||||
helpMessage: gettext('Name of the replication slot to use. The default behavior is to use the name of the subscription for the slot name.'),
|
||||
},
|
||||
{
|
||||
id: 'sync', label: gettext('Synchronous commit'), control: 'select2', deps:['event'],
|
||||
group: gettext('With'), type: 'text',
|
||||
helpMessage: gettext('The value of this parameter overrides the synchronous_commit setting. The default value is off.'),
|
||||
select2: {
|
||||
width: '100%',
|
||||
allowClear: false,
|
||||
},
|
||||
options:[
|
||||
{label: 'local', value: 'local'},
|
||||
{label: 'remote_write', value: 'remote_write'},
|
||||
{label: 'remote_apply', value: 'remote_apply'},
|
||||
{label: 'on', value: 'on'},
|
||||
{label: 'off', value: 'off'},
|
||||
],
|
||||
id: 'pub', label: gettext('Publication'), type: 'text', group: gettext('Connection'),
|
||||
mode: ['properties'],
|
||||
},
|
||||
],
|
||||
isDisable:function(m){
|
||||
if (m.isNew())
|
||||
return false;
|
||||
return true;
|
||||
},
|
||||
isSameDB:function(m){
|
||||
let host = m.attributes['host'],
|
||||
port = m.attributes['port'];
|
||||
|
||||
if ((m.attributes['host'] == 'localhost' || m.attributes['host'] == '127.0.0.1') &&
|
||||
(m.node_info.server.host == 'localhost' || m.node_info.server.host == '127.0.0.1')){
|
||||
host = m.node_info.server.host;
|
||||
}
|
||||
if (host == m.node_info.server.host && port == m.node_info.server.port){
|
||||
setTimeout( function() {
|
||||
m.set('create_slot', false);
|
||||
}, 10);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
isAllConnectionDataEnter: function(m){
|
||||
let host = m.get('host'),
|
||||
db = m.get('db'),
|
||||
port = m.get('port'),
|
||||
username = m.get('username');
|
||||
if ((!_.isUndefined(host) && host) && (!_.isUndefined(db) && db) && (!_.isUndefined(port) && port) && (!_.isUndefined(username) && username))
|
||||
return false;
|
||||
return true;
|
||||
},
|
||||
isConnect: function(m){
|
||||
if(!m.get('connect')){
|
||||
setTimeout( function() {
|
||||
m.set('copy_data', false);
|
||||
m.set('create_slot', false);
|
||||
m.set('enabled', false);
|
||||
}, 10);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
isRefresh: function(m){
|
||||
if (!m.get('refresh_pub') || _.isUndefined(m.get('refresh_pub'))){
|
||||
setTimeout( function() {
|
||||
m.set('copy_data_after_refresh', false);
|
||||
}, 10);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
isSSL: function(model) {
|
||||
var ssl_mode = model.get('sslmode');
|
||||
return _.indexOf(SSL_MODES, ssl_mode) == -1;
|
||||
},
|
||||
sessChanged: function() {
|
||||
if (!this.isNew() && _.isUndefined(this.attributes['refresh_pub']))
|
||||
return false;
|
||||
return pgBrowser.DataModel.prototype.sessChanged.apply(this);
|
||||
},
|
||||
/* validate function is used to validate the input given by
|
||||
* the user. In case of error, message will be displayed on
|
||||
* the GUI for the respective control.
|
||||
*/
|
||||
validate: function() {
|
||||
var msg;
|
||||
this.errorModel.clear();
|
||||
var name = this.get('name'),
|
||||
slot_name = this.get('slot_name');
|
||||
|
||||
if (_.isUndefined(name) || _.isNull(name) ||
|
||||
String(name).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Name cannot be empty.');
|
||||
this.errorModel.set('name', msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
if (!_.isUndefined(slot_name) && !_.isNull(slot_name)){
|
||||
if(/^[a-zA-Z0-9_]+$/.test(slot_name) == false){
|
||||
msg = gettext('Replication slot name may only contain lower case letters, numbers, and the underscore character.');
|
||||
this.errorModel.set('name', msg);
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
||||
const validateModel = new modelValidation.ModelValidation(this);
|
||||
return validateModel.validate();
|
||||
},
|
||||
canCreate: function(itemData, item) {
|
||||
var treeData = this.getTreeNodeHierarchy(item),
|
||||
server = treeData['server'];
|
||||
@ -548,8 +168,58 @@ define('pgadmin.node.subscription', [
|
||||
// by default we want to allow create menu
|
||||
return true;
|
||||
},
|
||||
|
||||
}),
|
||||
getSchema: function(treeNodeInfo, itemNodeData){
|
||||
return new SubscriptionSchema(
|
||||
{
|
||||
role:()=>getNodeListByName('role', treeNodeInfo, itemNodeData),
|
||||
getPublication: (host, password, port, username, db,
|
||||
connectTimeout, passfile, sslmode,
|
||||
sslcompression, sslcert, sslkey,
|
||||
sslrootcert, sslcrl) =>
|
||||
{
|
||||
return new Promise((resolve, reject)=>{
|
||||
const api = getApiInstance();
|
||||
if(host != undefined && port!= undefined && username!= undefined && db != undefined){
|
||||
var _url = pgBrowser.Nodes['cast'].generate_url.apply(
|
||||
pgBrowser.Nodes['subscription'], [
|
||||
null, 'get_publications', itemNodeData, false,
|
||||
treeNodeInfo,
|
||||
]);
|
||||
api.get(_url, {
|
||||
params: {host, password, port, username, db,
|
||||
connectTimeout, passfile, sslmode,
|
||||
sslcompression, sslcert, sslkey,
|
||||
sslrootcert, sslcrl},
|
||||
})
|
||||
.then(res=>{
|
||||
if ((res.data.errormsg === '') && !_.isNull(res.data.data)){
|
||||
resolve(res.data.data);
|
||||
pgAlertify().info(
|
||||
gettext('Publication fetched successfully.')
|
||||
);
|
||||
}else if(!_.isNull(res.data.errormsg) && _.isNull(res.data.data)){
|
||||
reject(res.data.errormsg);
|
||||
pgAlertify().alert(
|
||||
gettext('Check connection?'),
|
||||
gettext(res.data.errormsg)
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((err)=>{
|
||||
reject(err);
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
},{
|
||||
node_info: treeNodeInfo.server,
|
||||
},
|
||||
{
|
||||
subowner: pgBrowser.serverInfo[treeNodeInfo.server._id].user.name,
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
return pgBrowser.Nodes['coll-subscription'];
|
||||
|
@ -0,0 +1,448 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
import gettext from 'sources/gettext';
|
||||
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
|
||||
import { isEmptyString } from 'sources/validators';
|
||||
import _ from 'lodash';
|
||||
|
||||
export default class SubscriptionSchema extends BaseUISchema{
|
||||
constructor(fieldOptions={}, node_info, initValues) {
|
||||
super({
|
||||
name: undefined,
|
||||
subowner: undefined,
|
||||
pubtable: undefined,
|
||||
connect_timeout: 10,
|
||||
pub:[],
|
||||
enabled:true,
|
||||
create_slot: true,
|
||||
copy_data:true,
|
||||
connect:true,
|
||||
copy_data_after_refresh:false,
|
||||
sync:'off',
|
||||
refresh_pub: false,
|
||||
password: '',
|
||||
sslmode: 'prefer',
|
||||
sslcompression: false,
|
||||
sslcert: '',
|
||||
sslkey: '',
|
||||
sslrootcert: '',
|
||||
sslcrl: '',
|
||||
host: '',
|
||||
port: 5432,
|
||||
db: 'postgres',
|
||||
...initValues,
|
||||
});
|
||||
|
||||
this.fieldOptions = {
|
||||
role: [],
|
||||
publicationTable: [],
|
||||
...fieldOptions,
|
||||
};
|
||||
this.node_info = node_info;
|
||||
}
|
||||
get idAttribute() {
|
||||
return 'oid';
|
||||
}
|
||||
|
||||
get SSL_MODES() { return ['prefer', 'require', 'verify-ca', 'verify-full']; }
|
||||
|
||||
isDisable(){
|
||||
if (this.isNew())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
isSameDB(state){
|
||||
let host = state.host,
|
||||
port = state.port;
|
||||
|
||||
if ((state.host == 'localhost' || state.host == '127.0.0.1') &&
|
||||
(this.node_info['node_info'].host == 'localhost' || this.node_info['node_info'].host == '127.0.0.1')){
|
||||
host = this.node_info['node_info'].host;
|
||||
}
|
||||
if (host == this.node_info['node_info'].host && port == this.node_info['node_info'].port){
|
||||
state.create_slot = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
isAllConnectionDataEnter(state){
|
||||
let host = state.host,
|
||||
db = state.db,
|
||||
port = state.port,
|
||||
username = state.username;
|
||||
if ((!_.isUndefined(host) && host) && (!_.isUndefined(db) && db) && (!_.isUndefined(port) && port) && (!_.isUndefined(username) && username))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
isConnect(state){
|
||||
if(!_.isUndefined(state.connect) && !state.connect){
|
||||
state.copy_data = false;
|
||||
state.create_slot = false;
|
||||
state.enabled = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
isRefresh(state){
|
||||
if (!state.refresh_pub || _.isUndefined(state.refresh_pub)){
|
||||
state.copy_data_after_refresh = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
isSSL(state) {
|
||||
return this.SSL_MODES.indexOf(state.sslmode) == -1;
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
let obj = this;
|
||||
return [{
|
||||
id: 'name', label: gettext('Name'), type: 'text',
|
||||
mode: ['properties', 'create', 'edit'], noEmpty: true,
|
||||
visible: function() {
|
||||
if(!_.isUndefined(this.node_info['node_info'])
|
||||
&& !_.isUndefined(this.node_info['node_info'].version)
|
||||
&& this.node_info['node_info'].version >= 100000) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string', mode: ['properties'],
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
id: 'subowner', label: gettext('Owner'),
|
||||
options: this.fieldOptions.role,
|
||||
type: 'select',
|
||||
mode: ['edit', 'properties', 'create'], controlProps: { allowClear: false},
|
||||
disabled: function(){
|
||||
if(obj.isNew())
|
||||
return true;
|
||||
return false;
|
||||
},
|
||||
},{
|
||||
id: 'host', label: gettext('Host name/address'), type: 'text', group: gettext('Connection'),
|
||||
mode: ['properties', 'edit', 'create'],
|
||||
},
|
||||
{
|
||||
id: 'port', label: gettext('Port'), type: 'int', group: gettext('Connection'),
|
||||
mode: ['properties', 'edit', 'create'], min: 1, max: 65535,
|
||||
depChange: (state)=>{
|
||||
if(obj.origData.port != state.port && !obj.isNew(state) && state.connected){
|
||||
obj.informText = gettext(
|
||||
'To apply changes to the connection configuration, please disconnect from the server and then reconnect.'
|
||||
);
|
||||
} else {
|
||||
obj.informText = undefined;
|
||||
}
|
||||
}
|
||||
},{
|
||||
id: 'db', label: gettext('Database'), type: 'text', group: gettext('Connection'),
|
||||
mode: ['properties', 'edit', 'create'], readonly: obj.isConnected, disabled: obj.isShared,
|
||||
noEmpty: true,
|
||||
},{
|
||||
id: 'username', label: gettext('Username'), type: 'text', group: gettext('Connection'),
|
||||
mode: ['properties', 'edit', 'create'],
|
||||
depChange: (state)=>{
|
||||
if(obj.origData.username != state.username && !obj.isNew(state) && state.connected){
|
||||
obj.informText = gettext(
|
||||
'To apply changes to the connection configuration, please disconnect from the server and then reconnect.'
|
||||
);
|
||||
} else {
|
||||
obj.informText = undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'password', label: gettext('Password'), type: 'password', maxlength: null,
|
||||
group: gettext('Connection'),
|
||||
mode: ['create', 'edit'],
|
||||
deps: ['connect_now'],
|
||||
},
|
||||
{
|
||||
id: 'connect_timeout', label: gettext('Connection timeout'), type: 'text',
|
||||
mode: ['properties', 'edit', 'create'],
|
||||
group: gettext('Connection'),
|
||||
},
|
||||
{
|
||||
id: 'passfile', label: gettext('Passfile'), type: 'text', group: gettext('Connection'),
|
||||
mode: ['properties', 'edit', 'create'],
|
||||
},
|
||||
{
|
||||
id: 'pub', label: gettext('Publication'), type: 'text', group: gettext('Connection'),
|
||||
mode: ['properties'],
|
||||
},
|
||||
{
|
||||
id: 'pub', label: gettext('Publication'),
|
||||
group: gettext('Connection'), mode: ['create', 'edit'],
|
||||
deps: ['all_table', 'host', 'port', 'username', 'db', 'password'], disabled: obj.isAllConnectionDataEnter,
|
||||
helpMessage: gettext('Click the refresh button to get the publications'),
|
||||
type: (state)=>{
|
||||
return {
|
||||
type: 'select-refresh',
|
||||
controlProps: { allowClear: true, multiple: true, creatable: true, getOptionsOnRefresh: ()=>{
|
||||
return obj.fieldOptions.getPublication(state.host, state.password, state.port, state.username, state.db,
|
||||
state.connect_timeout, state.passfile, state.sslmode,
|
||||
state.sslcompression, state.sslcert, state.sslkey,
|
||||
state.sslrootcert, state.sslcrl);
|
||||
}},
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
id: 'sslmode', label: gettext('SSL mode'), type: 'select', group: gettext('SSL'),
|
||||
controlProps: {
|
||||
allowClear: false,
|
||||
},
|
||||
mode: ['properties', 'edit', 'create'],
|
||||
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,
|
||||
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,
|
||||
controlProps: {
|
||||
dialogType: 'select_file', supportedTypes: ['*'],
|
||||
},
|
||||
deps: ['sslmode'],
|
||||
},{
|
||||
id: 'sslrootcert', label: gettext('Root certificate'), type: 'file',
|
||||
group: gettext('SSL'), mode: ['edit', 'create'],
|
||||
disabled: obj.isSSL,
|
||||
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,
|
||||
controlProps: {
|
||||
dialogType: 'select_file', supportedTypes: ['*'],
|
||||
},
|
||||
deps: ['sslmode'],
|
||||
},
|
||||
{
|
||||
id: 'sslcompression', label: gettext('SSL compression?'), type: 'switch',
|
||||
mode: ['edit', 'create'], group: gettext('SSL'),
|
||||
disabled: obj.isSSL,
|
||||
deps: ['sslmode'],
|
||||
},
|
||||
{
|
||||
id: 'sslcert', label: gettext('Client certificate'), type: 'text',
|
||||
group: gettext('SSL'), mode: ['properties'],
|
||||
deps: ['sslmode'],
|
||||
visible: function(state) {
|
||||
var 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) {
|
||||
var 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) {
|
||||
var 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) {
|
||||
var 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: 'copy_data_after_refresh', label: gettext('Copy data?'),
|
||||
type: 'switch', mode: ['edit'],
|
||||
group: gettext('With'),
|
||||
readonly: obj.isRefresh, deps :['refresh_pub'],
|
||||
helpMessage: gettext('Specifies whether the existing data in the publications that are being subscribed to should be copied once the replication starts.'),
|
||||
},
|
||||
{
|
||||
id: 'copy_data', label: gettext('Copy data?'),
|
||||
type: 'switch', mode: ['create'],
|
||||
group: gettext('With'),
|
||||
readonly: obj.isConnect, deps :['connect'],
|
||||
helpMessage: gettext('Specifies whether the existing data in the publications that are being subscribed to should be copied once the replication starts.'),
|
||||
},
|
||||
{
|
||||
id: 'create_slot', label: gettext('Create slot?'),
|
||||
type: 'switch', mode: ['create'],
|
||||
group: gettext('With'),
|
||||
disabled: obj.isSameDB,
|
||||
readonly: obj.isConnect, deps :['connect', 'host', 'port'],
|
||||
helpMessage: gettext('Specifies whether the command should create the replication slot on the publisher.This field will be disabled and set to false if subscription connects to same database.Otherwise, the CREATE SUBSCRIPTION call will hang.'),
|
||||
|
||||
},
|
||||
{
|
||||
id: 'enabled', label: gettext('Enabled?'),
|
||||
type: 'switch', mode: ['create','edit', 'properties'],
|
||||
group: gettext('With'),
|
||||
readonly: obj.isConnect, deps :['connect'],
|
||||
helpMessage: gettext('Specifies whether the subscription should be actively replicating, or whether it should be just setup but not started yet.'),
|
||||
},
|
||||
{
|
||||
id: 'refresh_pub', label: gettext('Refresh publication?'),
|
||||
type: 'switch', mode: ['edit'],
|
||||
group: gettext('With'),
|
||||
helpMessage: gettext('Fetch missing table information from publisher.'),
|
||||
deps:['enabled'], disabled: function(state){
|
||||
if (state.enabled)
|
||||
return false;
|
||||
state.refresh_pub = false;
|
||||
return true;
|
||||
},
|
||||
},{
|
||||
id: 'connect', label: gettext('Connect?'),
|
||||
type: 'switch', mode: ['create'],
|
||||
group: gettext('With'),
|
||||
disabled: obj.isDisable, deps:['enabled', 'create_slot', 'copy_data'],
|
||||
helpMessage: gettext('Specifies whether the CREATE SUBSCRIPTION should connect to the publisher at all. Setting this to false will change default values of enabled, create_slot and copy_data to false.'),
|
||||
},
|
||||
{
|
||||
id: 'slot_name', label: gettext('Slot name'),
|
||||
type: 'text', mode: ['create','edit', 'properties'],
|
||||
group: gettext('With'),
|
||||
helpMessage: gettext('Name of the replication slot to use. The default behavior is to use the name of the subscription for the slot name.'),
|
||||
},
|
||||
{
|
||||
id: 'sync', label: gettext('Synchronous commit'), control: 'select2', deps:['event'],
|
||||
group: gettext('With'), type: 'select',
|
||||
helpMessage: gettext('The value of this parameter overrides the synchronous_commit setting. The default value is off.'),
|
||||
controlProps: {
|
||||
width: '100%',
|
||||
allowClear: false,
|
||||
},
|
||||
options:[
|
||||
{label: 'local', value: 'local'},
|
||||
{label: 'remote_write', value: 'remote_write'},
|
||||
{label: 'remote_apply', value: 'remote_apply'},
|
||||
{label: 'on', value: 'on'},
|
||||
{label: 'off', value: 'off'},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
validate(state, setError) {
|
||||
let errmsg = null;
|
||||
errmsg = gettext('Either Host name, Address must be specified.');
|
||||
if(isEmptyString(state.host)) {
|
||||
setError('host', errmsg);
|
||||
return true;
|
||||
} else {
|
||||
errmsg = null;
|
||||
setError('host', errmsg);
|
||||
}
|
||||
if(isEmptyString(state.username)) {
|
||||
errmsg = gettext('Username must be specified.');
|
||||
setError('username', errmsg);
|
||||
return true;
|
||||
} else {
|
||||
errmsg = null;
|
||||
setError('username', errmsg);
|
||||
}
|
||||
|
||||
if(isEmptyString(state.port)) {
|
||||
errmsg = gettext('Port must be specified.');
|
||||
setError('port', errmsg);
|
||||
return true;
|
||||
} else {
|
||||
errmsg = null;
|
||||
setError('port', errmsg);
|
||||
}
|
||||
|
||||
if(isEmptyString(state.pub)) {
|
||||
errmsg = gettext('Publication must be specified.');
|
||||
setError('pub', errmsg);
|
||||
return true;
|
||||
} else {
|
||||
errmsg = null;
|
||||
setError('pub', errmsg);
|
||||
}
|
||||
|
||||
if (state.use_ssh_tunnel) {
|
||||
if(isEmptyString(state.tunnel_host)) {
|
||||
errmsg = gettext('SSH Tunnel host must be specified.');
|
||||
setError('tunnel_host', errmsg);
|
||||
return true;
|
||||
} else {
|
||||
errmsg = null;
|
||||
setError('tunnel_host', errmsg);
|
||||
}
|
||||
|
||||
if(isEmptyString(state.tunnel_port)) {
|
||||
errmsg = gettext('SSH Tunnel port must be specified.');
|
||||
setError('tunnel_port', errmsg);
|
||||
return true;
|
||||
} else {
|
||||
errmsg = null;
|
||||
setError('tunnel_port', errmsg);
|
||||
}
|
||||
|
||||
if(isEmptyString(state.tunnel_username)) {
|
||||
errmsg = gettext('SSH Tunnel username must be specified.');
|
||||
setError('tunnel_username', errmsg);
|
||||
return true;
|
||||
} else {
|
||||
errmsg = null;
|
||||
setError('tunnel_username', errmsg);
|
||||
}
|
||||
|
||||
if (state.tunnel_authentication) {
|
||||
if(isEmptyString(state.tunnel_identity_file)) {
|
||||
errmsg = gettext('SSH Tunnel identity file must be specified.');
|
||||
setError('tunnel_identity_file', errmsg);
|
||||
return true;
|
||||
} else {
|
||||
errmsg = null;
|
||||
setError('tunnel_identity_file', errmsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@ -2,7 +2,6 @@ SELECT sub.oid as oid,
|
||||
subname as name,
|
||||
subpublications as pub,
|
||||
sub.subsynccommit as sync,
|
||||
subpublications as cur_pub,
|
||||
pga.rolname as subowner,
|
||||
subslotname as slot_name,
|
||||
subenabled as enabled,
|
||||
|
@ -17,6 +17,7 @@ import Privilege from '../components/Privilege';
|
||||
import { evalFunc } from 'sources/utils';
|
||||
import PropTypes from 'prop-types';
|
||||
import CustomPropTypes from '../custom_prop_types';
|
||||
import { SelectRefresh} from '../components/SelectRefresh';
|
||||
|
||||
/* Control mapping for form view */
|
||||
function MappedFormControlBase({type, value, id, onChange, className, visible, inputRef, noLabel, ...props}) {
|
||||
@ -74,6 +75,8 @@ function MappedFormControlBase({type, value, id, onChange, className, visible, i
|
||||
return <FormInputText name={name} value={value} onChange={onTextChange} className={className} type='password' inputRef={inputRef} {...props}/>;
|
||||
case 'select':
|
||||
return <FormInputSelect name={name} value={value} onChange={onTextChange} className={className} {...props} />;
|
||||
case 'select-refresh':
|
||||
return <SelectRefresh name={name} value={value} onChange={onTextChange} className={className} {...props} />;
|
||||
case 'switch':
|
||||
return <FormInputSwitch name={name} value={value}
|
||||
onChange={(e)=>onTextChange(e.target.checked, e.target.name)} className={className}
|
||||
|
42
web/pgadmin/static/js/components/SelectRefresh.jsx
Normal file
42
web/pgadmin/static/js/components/SelectRefresh.jsx
Normal file
@ -0,0 +1,42 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Box} from '@material-ui/core';
|
||||
import {InputSelect, FormInput} from './FormComponents';
|
||||
import PropTypes from 'prop-types';
|
||||
import CustomPropTypes from '../custom_prop_types';
|
||||
import RefreshIcon from '@material-ui/icons/Refresh';
|
||||
import { PgIconButton } from './Buttons';
|
||||
|
||||
export function SelectRefresh({ required, className, label, helpMessage, testcid, controlProps, ...props }){
|
||||
const [options, setOptions] = useState([]);
|
||||
const [optionsReloadBasis, setOptionsReloadBasis] = useState(false);
|
||||
const {getOptionsOnRefresh, ...selectControlProps} = controlProps;
|
||||
|
||||
const onRefreshClick = ()=>{
|
||||
getOptionsOnRefresh && getOptionsOnRefresh()
|
||||
.then((res)=>{
|
||||
setOptions(res);
|
||||
setOptionsReloadBasis((prevVal)=>!prevVal);
|
||||
});
|
||||
};
|
||||
return (
|
||||
<FormInput required={required} label={label} className={className} helpMessage={helpMessage} testcid={testcid}>
|
||||
<Box display="flex" >
|
||||
<Box flexGrow="1">
|
||||
<InputSelect {...props} options={options} optionsReloadBasis={optionsReloadBasis} controlProps={selectControlProps}/>
|
||||
</Box>
|
||||
<Box>
|
||||
<PgIconButton onClick={onRefreshClick} icon={<RefreshIcon />} title={label||''}/>
|
||||
</Box>
|
||||
</Box>
|
||||
</FormInput>
|
||||
);
|
||||
}
|
||||
|
||||
SelectRefresh.propTypes = {
|
||||
required: PropTypes.bool,
|
||||
label: PropTypes.string,
|
||||
className: CustomPropTypes.className,
|
||||
helpMessage: PropTypes.string,
|
||||
testcid: PropTypes.string,
|
||||
controlProps: PropTypes.object,
|
||||
};
|
69
web/regression/javascript/components/SelectRefresh.spec.js
Normal file
69
web/regression/javascript/components/SelectRefresh.spec.js
Normal file
@ -0,0 +1,69 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import jasmineEnzyme from 'jasmine-enzyme';
|
||||
import React from 'react';
|
||||
import '../helper/enzyme.helper';
|
||||
import { withTheme } from '../fake_theme';
|
||||
import { createMount } from '@material-ui/core/test-utils';
|
||||
import { FormHelperText, InputLabel } from '@material-ui/core';
|
||||
|
||||
import {SelectRefresh} from 'sources/components/SelectRefresh';
|
||||
|
||||
/* MUI Components need to be wrapped in Theme for theme vars */
|
||||
describe('components SelectRefresh', ()=>{
|
||||
let mount;
|
||||
|
||||
/* Use createMount so that material ui components gets the required context */
|
||||
/* https://material-ui.com/guides/testing/#api */
|
||||
beforeAll(()=>{
|
||||
mount = createMount();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
mount.cleanUp();
|
||||
});
|
||||
|
||||
beforeEach(()=>{
|
||||
jasmineEnzyme();
|
||||
});
|
||||
|
||||
describe('SelectRefresh', ()=>{
|
||||
let ThemedSelectRefresh = withTheme(SelectRefresh), ctrl, onChange=jasmine.createSpy('onChange'),
|
||||
ctrlMount = (props)=>{
|
||||
ctrl?.unmount();
|
||||
ctrl = mount(
|
||||
<ThemedSelectRefresh
|
||||
label="First"
|
||||
className="someClass"
|
||||
testcid="inpCid"
|
||||
helpMessage="some help message"
|
||||
/* InputSelect */
|
||||
readonly={false}
|
||||
disabled={false}
|
||||
value={1}
|
||||
onChange={onChange}
|
||||
controlProps={{
|
||||
getOptionsOnRefresh: ()=>{}
|
||||
}}
|
||||
{...props}
|
||||
/>);
|
||||
};
|
||||
|
||||
beforeEach(()=>{
|
||||
ctrlMount();
|
||||
});
|
||||
|
||||
it('accessibility', ()=>{
|
||||
expect(ctrl.find(InputLabel)).toHaveProp('htmlFor', 'inpCid');
|
||||
expect(ctrl.find(FormHelperText)).toHaveProp('id', 'hinpCid');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
@ -95,6 +95,16 @@ describe('CastSchema', ()=>{
|
||||
/>);
|
||||
});
|
||||
|
||||
it('srctyp depChange', ()=>{
|
||||
let depChange = _.find(schemaObj.fields, (f)=>f.id=='srctyp').depChange;
|
||||
depChange({srctyp: 'abc', trgtyp: 'abc'});
|
||||
});
|
||||
|
||||
it('trgtyp depChange', ()=>{
|
||||
let depChange = _.find(schemaObj.fields, (f)=>f.id=='trgtyp').depChange;
|
||||
depChange({srctyp: 'abc', trgtyp: 'abc'});
|
||||
});
|
||||
|
||||
it('validate', ()=>{
|
||||
let state = {};
|
||||
let setError = jasmine.createSpy('setError');
|
||||
|
@ -0,0 +1,167 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import jasmineEnzyme from 'jasmine-enzyme';
|
||||
import React from 'react';
|
||||
import '../helper/enzyme.helper';
|
||||
import { createMount } from '@material-ui/core/test-utils';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import {messages} from '../fake_messages';
|
||||
import SchemaView from '../../../pgadmin/static/js/SchemaView';
|
||||
import SubscriptionSchema from '../../../pgadmin/browser/server_groups/servers/databases/subscriptions/static/js/subscription.ui';
|
||||
|
||||
describe('SubscriptionSchema', ()=>{
|
||||
let mount;
|
||||
let schemaObj = new SubscriptionSchema(
|
||||
{
|
||||
getPublication: ()=>[],
|
||||
role: ()=>[],
|
||||
},
|
||||
{
|
||||
node_info: {
|
||||
connected: true,
|
||||
user: {id: 10, name: 'postgres', is_superuser: true, can_create_role: true, can_create_db: true},
|
||||
user_id: 1,
|
||||
user_name: 'postgres',
|
||||
version: 130005,
|
||||
server: {host: '127.0.0.1', port: 5432},
|
||||
},
|
||||
},
|
||||
{
|
||||
subowner : 'postgres'
|
||||
}
|
||||
);
|
||||
let getInitData = ()=>Promise.resolve({});
|
||||
|
||||
/* Use createMount so that material ui components gets the required context */
|
||||
/* https://material-ui.com/guides/testing/#api */
|
||||
beforeAll(()=>{
|
||||
mount = createMount();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
mount.cleanUp();
|
||||
});
|
||||
|
||||
beforeEach(()=>{
|
||||
jasmineEnzyme();
|
||||
/* messages used by validators */
|
||||
pgAdmin.Browser = pgAdmin.Browser || {};
|
||||
pgAdmin.Browser.messages = pgAdmin.Browser.messages || messages;
|
||||
pgAdmin.Browser.utils = pgAdmin.Browser.utils || {};
|
||||
});
|
||||
|
||||
it('create', ()=>{
|
||||
mount(<SchemaView
|
||||
formType='dialog'
|
||||
schema={schemaObj}
|
||||
viewHelperProps={{
|
||||
mode: 'create',
|
||||
}}
|
||||
onSave={()=>{}}
|
||||
onClose={()=>{}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
onDataChange={()=>{}}
|
||||
confirmOnCloseReset={false}
|
||||
hasSQL={false}
|
||||
disableSqlHelp={false}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('edit', ()=>{
|
||||
mount(<SchemaView
|
||||
formType='dialog'
|
||||
schema={schemaObj}
|
||||
getInitData={getInitData}
|
||||
viewHelperProps={{
|
||||
mode: 'create',
|
||||
}}
|
||||
onSave={()=>{}}
|
||||
onClose={()=>{}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
onDataChange={()=>{}}
|
||||
confirmOnCloseReset={false}
|
||||
hasSQL={false}
|
||||
disableSqlHelp={false}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('properties', ()=>{
|
||||
mount(<SchemaView
|
||||
formType='tab'
|
||||
schema={schemaObj}
|
||||
getInitData={getInitData}
|
||||
viewHelperProps={{
|
||||
mode: 'properties',
|
||||
}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
/>);
|
||||
});
|
||||
|
||||
|
||||
it('copy_data_after_refresh readonly', ()=>{
|
||||
let isReadonly = _.find(schemaObj.fields, (f)=>f.id=='copy_data_after_refresh').readonly;
|
||||
isReadonly({host: '127.0.0.1', port : 5432});
|
||||
});
|
||||
|
||||
it('copy_data_after_refresh readonly', ()=>{
|
||||
let isReadonly = _.find(schemaObj.fields, (f)=>f.id=='copy_data_after_refresh').readonly;
|
||||
isReadonly({refresh_pub : true});
|
||||
});
|
||||
|
||||
it('validate', ()=>{
|
||||
let state = {};
|
||||
let setError = jasmine.createSpy('setError');
|
||||
|
||||
state.host = null;
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('host', 'Either Host name, Address must be specified.');
|
||||
|
||||
state.host = '127.0.0.1';
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('username', 'Username must be specified.');
|
||||
|
||||
state.username = 'postgres';
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('port', 'Port must be specified.');
|
||||
|
||||
state.port = 5432;
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('pub', 'Publication must be specified.');
|
||||
|
||||
state.pub = 'testPub';
|
||||
state.use_ssh_tunnel = 'Require';
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('tunnel_host', 'SSH Tunnel host must be specified.');
|
||||
|
||||
state.tunnel_host = 'localhost';
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('tunnel_port', 'SSH Tunnel port must be specified.');
|
||||
|
||||
state.tunnel_port = 8080;
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('tunnel_username', 'SSH Tunnel username must be specified.');
|
||||
|
||||
state.tunnel_username = 'jasmine';
|
||||
state.tunnel_authentication = true;
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('tunnel_identity_file', 'SSH Tunnel identity file must be specified.');
|
||||
|
||||
state.tunnel_identity_file = '/file/path/xyz.pem';
|
||||
expect(schemaObj.validate(state, setError)).toBeFalse();
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user