mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Port pgAgent and it's child nodes to react. Fixes #6645
This commit is contained in:
parent
1ce6c2aee1
commit
1b7a77f5cb
@ -747,8 +747,6 @@ class SubscriptionView(PGChildNodeView, SchemaDiffObjectCompare):
|
||||
status=200
|
||||
)
|
||||
|
||||
|
||||
|
||||
@check_precondition
|
||||
def sql(self, gid, sid, did, subid, json_resp=True):
|
||||
"""
|
||||
|
@ -258,6 +258,24 @@ SELECT EXISTS(
|
||||
)
|
||||
if not status:
|
||||
return internal_server_error(errormsg=rset)
|
||||
|
||||
# Create jscexceptions in the correct format that React control
|
||||
# required.
|
||||
for schedule in rset['rows']:
|
||||
if 'jexid' in schedule and schedule['jexid'] is not None \
|
||||
and len(schedule['jexid']) > 0:
|
||||
schedule['jscexceptions'] = []
|
||||
index = 0
|
||||
for exid in schedule['jexid']:
|
||||
schedule['jscexceptions'].append(
|
||||
{'jexid': exid,
|
||||
'jexdate': schedule['jexdate'][index],
|
||||
'jextime': schedule['jextime'][index]
|
||||
}
|
||||
)
|
||||
|
||||
index += 1
|
||||
|
||||
res['jschedules'] = rset['rows']
|
||||
else:
|
||||
res = rset['rows']
|
||||
|
@ -310,6 +310,22 @@ class JobScheduleView(PGChildNodeView):
|
||||
errormsg=gettext("Could not find the specified job step.")
|
||||
)
|
||||
|
||||
# Create jscexceptions in the correct format that React control
|
||||
# required.
|
||||
if 'jexid' in res['rows'][0] and res['rows'][0]['jexid'] is not None\
|
||||
and len(res['rows'][0]['jexid']) > 0:
|
||||
res['rows'][0]['jscexceptions'] = []
|
||||
index = 0
|
||||
for exid in res['rows'][0]['jexid']:
|
||||
res['rows'][0]['jscexceptions'].append(
|
||||
{'jexid': exid,
|
||||
'jexdate': res['rows'][0]['jexdate'][index],
|
||||
'jextime': res['rows'][0]['jextime'][index]
|
||||
}
|
||||
)
|
||||
|
||||
index += 1
|
||||
|
||||
return ajax_response(
|
||||
response=res['rows'][0],
|
||||
status=200
|
||||
|
@ -7,13 +7,13 @@
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import PgaJobScheduleSchema from './pga_schedule.ui';
|
||||
|
||||
define('pgadmin.node.pga_schedule', [
|
||||
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
|
||||
'sources/pgadmin', 'moment', 'pgadmin.browser', 'alertify',
|
||||
'pgadmin.backform', 'pgadmin.backgrid',
|
||||
'sources/pgadmin', 'moment', 'pgadmin.browser',
|
||||
], function(
|
||||
gettext, url_for, $, _, pgAdmin, moment, pgBrowser, Alertify, Backform,
|
||||
Backgrid
|
||||
gettext, url_for, $, _, pgAdmin, moment, pgBrowser
|
||||
) {
|
||||
|
||||
if (!pgBrowser.Nodes['coll-pga_schedule']) {
|
||||
@ -29,183 +29,6 @@ define('pgadmin.node.pga_schedule', [
|
||||
}
|
||||
|
||||
if (!pgBrowser.Nodes['pga_schedule']) {
|
||||
|
||||
var weekdays = [
|
||||
gettext('Sunday'), gettext('Monday'), gettext('Tuesday'),
|
||||
gettext('Wednesday'), gettext('Thursday'), gettext('Friday'),
|
||||
gettext('Saturday'),
|
||||
],
|
||||
monthdays = [
|
||||
gettext('1st'), gettext('2nd'), gettext('3rd'),
|
||||
gettext('4th'), gettext('5th'), gettext('6th'),
|
||||
gettext('7th'), gettext('8th'), gettext('9th'),
|
||||
gettext('10th'), gettext('11th'), gettext('12th'),
|
||||
gettext('13th'), gettext('14th'), gettext('15th'),
|
||||
gettext('16th'), gettext('17th'), gettext('18th'),
|
||||
gettext('19th'), gettext('20th'), gettext('21st'),
|
||||
gettext('22nd'), gettext('23rd'), gettext('24th'),
|
||||
gettext('25th'), gettext('26th'), gettext('27th'),
|
||||
gettext('28th'), gettext('29th'), gettext('30th'),
|
||||
gettext('31st'), gettext('Last day'),
|
||||
],
|
||||
months = [
|
||||
gettext('January'), gettext('February'), gettext('March'),
|
||||
gettext('April'), gettext('May'), gettext('June'),
|
||||
gettext('July'), gettext('August'), gettext('September'),
|
||||
gettext('October'), gettext('November'), gettext('December'),
|
||||
],
|
||||
hours = [
|
||||
gettext('00'), gettext('01'), gettext('02'), gettext('03'),
|
||||
gettext('04'), gettext('05'), gettext('06'), gettext('07'),
|
||||
gettext('08'), gettext('09'), gettext('10'), gettext('11'),
|
||||
gettext('12'), gettext('13'), gettext('14'), gettext('15'),
|
||||
gettext('16'), gettext('17'), gettext('18'), gettext('19'),
|
||||
gettext('20'), gettext('21'), gettext('22'), gettext('23'),
|
||||
],
|
||||
minutes = [
|
||||
gettext('00'), gettext('01'), gettext('02'), gettext('03'),
|
||||
gettext('04'), gettext('05'), gettext('06'), gettext('07'),
|
||||
gettext('08'), gettext('09'), gettext('10'), gettext('11'),
|
||||
gettext('12'), gettext('13'), gettext('14'), gettext('15'),
|
||||
gettext('16'), gettext('17'), gettext('18'), gettext('19'),
|
||||
gettext('20'), gettext('21'), gettext('22'), gettext('23'),
|
||||
gettext('24'), gettext('25'), gettext('26'), gettext('27'),
|
||||
gettext('28'), gettext('29'), gettext('30'), gettext('31'),
|
||||
gettext('32'), gettext('33'), gettext('34'), gettext('35'),
|
||||
gettext('36'), gettext('37'), gettext('38'), gettext('39'),
|
||||
gettext('40'), gettext('41'), gettext('42'), gettext('43'),
|
||||
gettext('44'), gettext('45'), gettext('46'), gettext('47'),
|
||||
gettext('48'), gettext('49'), gettext('50'), gettext('51'),
|
||||
gettext('52'), gettext('53'), gettext('54'), gettext('55'),
|
||||
gettext('56'), gettext('57'), gettext('58'), gettext('59'),
|
||||
],
|
||||
AnyDatetimeCell = Backgrid.Extension.MomentCell.extend({
|
||||
editor: Backgrid.Extension.DatetimePickerEditor,
|
||||
render: function() {
|
||||
this.$el.empty();
|
||||
var model = this.model;
|
||||
this.$el.text(this.formatter.fromRaw(model.get(this.column.get('name')), model) || gettext('<any>'));
|
||||
this.delegateEvents();
|
||||
|
||||
return this;
|
||||
},
|
||||
}),
|
||||
DatetimeCell = Backgrid.Extension.MomentCell.extend({
|
||||
editor: Backgrid.Extension.DatetimePickerEditor,
|
||||
}),
|
||||
BooleanArrayFormatter = function(selector, indexes) {
|
||||
var self = this;
|
||||
|
||||
self.selector = selector;
|
||||
self.indexes = indexes;
|
||||
|
||||
this.fromRaw = function(rawData) {
|
||||
if (!_.isArray(rawData)) {
|
||||
return rawData;
|
||||
}
|
||||
|
||||
var res = [], idx = 0, resIdx = [];
|
||||
|
||||
for (; idx < rawData.length; idx++) {
|
||||
if (!rawData[idx])
|
||||
continue;
|
||||
res.push(self.selector[idx]);
|
||||
resIdx.push(idx + 1);
|
||||
}
|
||||
|
||||
return self.indexes ? resIdx : res.join(', ');
|
||||
};
|
||||
this.toRaw = function(d) {
|
||||
if (!self.indexes)
|
||||
return d;
|
||||
var res = [], i = 0, l = self.selector.length;
|
||||
|
||||
for (; i < l; i++) {
|
||||
res.push(_.indexOf(d, String(i + 1)) != -1);
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
return self;
|
||||
},
|
||||
BooleanArrayOptions = function(ctrl) {
|
||||
var selector = ctrl.field.get('selector'),
|
||||
val = ctrl.model.get(ctrl.field.get('name')),
|
||||
res = [];
|
||||
|
||||
if (selector) {
|
||||
res = _.map(
|
||||
selector, function(v, i) {
|
||||
return {label: v, value: i + 1, selected: val[i]};
|
||||
}
|
||||
);
|
||||
}
|
||||
return res;
|
||||
},
|
||||
ExceptionModel = pgBrowser.Node.Model.extend({
|
||||
defaults: {
|
||||
jexid: undefined,
|
||||
jexdate: null,
|
||||
jextime: null,
|
||||
},
|
||||
idAttribute: 'jexid',
|
||||
schema: [{
|
||||
id: 'jexdate', type: 'text', label: gettext('Date'),
|
||||
editable: true, placeholder: gettext('<any>'),
|
||||
cell: AnyDatetimeCell, options: {format: 'YYYY-MM-DD'},
|
||||
displayFormat: 'YYYY-MM-DD', modelFormat: 'YYYY-MM-DD',
|
||||
cellHeaderClasses:'width_percent_50', allowEmpty: true,
|
||||
},{
|
||||
id: 'jextime', type: 'text', placeholder: gettext('<any>'),
|
||||
label: gettext('Time'), editable: true, cell: AnyDatetimeCell,
|
||||
options: {format: 'HH:mm', buttons: {
|
||||
showToday: false,
|
||||
}}, displayFormat: 'HH:mm',
|
||||
modelFormat: 'HH:mm:ss', displayInUTC: true, allowEmpty: true,
|
||||
cellHeaderClasses:'width_percent_50', modalInUTC: true,
|
||||
}],
|
||||
validate: function() {
|
||||
var self = this, exceptions = this.collection,
|
||||
errMsg, hasExceptionErr = false,
|
||||
d = (this.get('jexdate') || '<any>'),
|
||||
t = this.get('jextime') || '<any>',
|
||||
id = this.get('jexid') || this.cid;
|
||||
|
||||
self.errorModel.unset('jscdate');
|
||||
if (d == t && d == '<any>') {
|
||||
errMsg = gettext('Please specify date/time.');
|
||||
self.errorModel.set('jscdate', errMsg);
|
||||
return errMsg ;
|
||||
}
|
||||
|
||||
exceptions.each(function(ex) {
|
||||
if (hasExceptionErr || id == (ex.get('jexid') || ex.cid))
|
||||
return;
|
||||
|
||||
if (
|
||||
d == (ex.get('jexdate') || '<any>') &&
|
||||
t == (ex.get('jextime') || '<any>')
|
||||
) {
|
||||
errMsg = gettext('Please specify unique set of exceptions.');
|
||||
if (ex.errorModel.get('jscdate') != errMsg)
|
||||
self.errorModel.set('jscdate', errMsg);
|
||||
hasExceptionErr = true;
|
||||
}
|
||||
});
|
||||
|
||||
return errMsg;
|
||||
},
|
||||
});
|
||||
|
||||
var CustomInfoControl = Backform.Control.extend({
|
||||
template: _.template([
|
||||
'<div>',
|
||||
' <%=infotext%>',
|
||||
'</div>',
|
||||
].join('\n')),
|
||||
className: 'pgadmin-control-group',
|
||||
});
|
||||
|
||||
pgBrowser.Nodes['pga_schedule'] = pgBrowser.Node.extend({
|
||||
parent_type: 'pga_job',
|
||||
type: 'pga_schedule',
|
||||
@ -240,41 +63,13 @@ define('pgadmin.node.pga_schedule', [
|
||||
icon: 'wcTabIcon icon-pga_schedule', data: {action: 'create'},
|
||||
}]);
|
||||
},
|
||||
model: pgBrowser.Node.Model.extend({
|
||||
defaults: {
|
||||
jscid: null,
|
||||
jscjobid: null,
|
||||
jscname: '',
|
||||
jscdesc: '',
|
||||
jscenabled: true,
|
||||
jscstart: null,
|
||||
jscend: null,
|
||||
jscweekdays: _.map(weekdays, function() { return false; }),
|
||||
jscmonthdays: _.map(monthdays, function() { return false; }),
|
||||
jscmonths: _.map(months, function() { return false; }),
|
||||
jschours: _.map(hours, function() { return false; }),
|
||||
jscminutes: _.map(minutes, function() { return false; }),
|
||||
jscexceptions: [],
|
||||
},
|
||||
idAttribute: 'jscid',
|
||||
parse: function(d) {
|
||||
d.jscexceptions = [];
|
||||
if (d.jexid && d.jexid.length) {
|
||||
var idx = 0;
|
||||
for (; idx < d.jexid.length; idx++) {
|
||||
d.jscexceptions.push({
|
||||
'jexid': d.jexid[idx],
|
||||
'jexdate': d.jexdate[idx],
|
||||
'jextime': d.jextime[idx],
|
||||
});
|
||||
}
|
||||
}
|
||||
delete d.jexid;
|
||||
delete d.jexdate;
|
||||
delete d.jextime;
|
||||
|
||||
return pgBrowser.Node.Model.prototype.parse.apply(this, arguments);
|
||||
},
|
||||
getSchema: function() {
|
||||
return new PgaJobScheduleSchema();
|
||||
},
|
||||
|
||||
model: pgBrowser.Node.Model.extend({
|
||||
idAttribute: 'jscid',
|
||||
schema: [{
|
||||
id: 'jscid', label: gettext('ID'), type: 'int',
|
||||
cellHeaderClasses: 'width_percent_5', mode: ['properties'],
|
||||
@ -286,236 +81,7 @@ define('pgadmin.node.pga_schedule', [
|
||||
id: 'jscenabled', label: gettext('Enabled?'), type: 'switch',
|
||||
disabled: function() { return false; },
|
||||
cellHeaderClasses: 'width_percent_5',
|
||||
},{
|
||||
id: 'jscstart', label: gettext('Start'), type: 'text',
|
||||
control: 'datetimepicker', cell: DatetimeCell,
|
||||
disabled: function() { return false; }, displayInUTC: false,
|
||||
displayFormat: 'YYYY-MM-DD HH:mm:ss Z',
|
||||
modelFormat: 'YYYY-MM-DD HH:mm:ss Z', options: {
|
||||
format: 'YYYY-MM-DD HH:mm:ss Z',
|
||||
minDate: moment().add(0, 'm'),
|
||||
}, cellHeaderClasses: 'width_percent_25',
|
||||
},{
|
||||
id: 'jscend', label: gettext('End'), type: 'text',
|
||||
control: 'datetimepicker', cell: DatetimeCell,
|
||||
disabled: function() { return false; }, displayInUTC: false,
|
||||
displayFormat: 'YYYY-MM-DD HH:mm:ss Z', options: {
|
||||
format: 'YYYY-MM-DD HH:mm:ss Z',
|
||||
minDate: moment().add(0, 'm'),
|
||||
}, cellHeaderClasses: 'width_percent_25',
|
||||
modelFormat: 'YYYY-MM-DD HH:mm:ss Z',
|
||||
},{
|
||||
id: 'jscweekdays', label: gettext('Week days'), type: 'text',
|
||||
control: Backform.Control.extend({
|
||||
formatter: new BooleanArrayFormatter(weekdays, false),
|
||||
}), mode: ['properties'],
|
||||
},{
|
||||
id: 'jscmonthdays', label: gettext('Month days'), type: 'text',
|
||||
control: Backform.Control.extend({
|
||||
formatter: new BooleanArrayFormatter(monthdays, false),
|
||||
}), mode: ['properties'],
|
||||
},{
|
||||
id: 'jscmonths', label: gettext('Months'), type: 'text',
|
||||
control: Backform.Control.extend({
|
||||
formatter: new BooleanArrayFormatter(months, false),
|
||||
}), mode: ['properties'],
|
||||
},{
|
||||
id: 'jschours', label: gettext('Hours'), type: 'text',
|
||||
control: Backform.Control.extend({
|
||||
formatter: new BooleanArrayFormatter(hours, false),
|
||||
}), mode: ['properties'],
|
||||
},{
|
||||
id: 'jscminutes', label: gettext('Minutes'), type: 'text',
|
||||
control: Backform.Control.extend({
|
||||
formatter: new BooleanArrayFormatter(minutes, false),
|
||||
}), mode: ['properties'],
|
||||
},{
|
||||
id: 'jscexceptions', label: gettext('Exceptions'), type: 'text',
|
||||
control: Backform.Control.extend({
|
||||
formatter: new function() {
|
||||
this.fromRaw = function(rawData) {
|
||||
var res = '', idx = 0, d;
|
||||
|
||||
if (!rawData) {
|
||||
return res;
|
||||
}
|
||||
|
||||
for (; idx < rawData.length; idx++) {
|
||||
d = rawData[idx];
|
||||
if (idx)
|
||||
res += ', ';
|
||||
res += '[' + String((d.jexdate || '') + ' ' + (d.jextime || '')).replace(/^\s+|\s+$/g, '') + ']';
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
this.toRaw = function(data) { return data; };
|
||||
|
||||
return this;
|
||||
},
|
||||
}), mode: ['properties'],
|
||||
},{
|
||||
type: 'control', mode: ['create', 'edit'], group: gettext('Repeat'),
|
||||
infotext: gettext('Schedules are specified using a <strong>cron-style</strong> format.<br/><ul><li>For each selected time or date element, the schedule will execute.<br/>e.g. To execute at 5 minutes past every hour, simply select ‘05’ in the Minutes list box.<br/></li><li>Values from more than one field may be specified in order to further control the schedule.<br/>e.g. To execute at 12:05 and 14:05 every Monday and Thursday, you would click minute 05, hours 12 and 14, and weekdays Monday and Thursday.</li><li>For additional flexibility, the Month Days check list includes an extra Last Day option. This matches the last day of the month, whether it happens to be the 28th, 29th, 30th or 31st.</li></ul>'),
|
||||
control: CustomInfoControl,
|
||||
},{
|
||||
type: 'nested', label: gettext('Days'), group: gettext('Repeat'),
|
||||
mode: ['create', 'edit'],
|
||||
control: Backform.FieldsetControl.extend({
|
||||
render: function() {
|
||||
var res = Backform.FieldsetControl.prototype.render.apply(
|
||||
this, arguments
|
||||
);
|
||||
return res;
|
||||
},
|
||||
}),
|
||||
schema:[{
|
||||
id: 'jscweekdays', label: gettext('Week Days'), cell: 'select2',
|
||||
group: gettext('Days'), control: 'select2', type: 'array',
|
||||
select2: {
|
||||
first_empty: false,
|
||||
multiple: true,
|
||||
allowClear: true,
|
||||
placeholder: gettext('Select the weekdays...'),
|
||||
width: 'style',
|
||||
dropdownAdapter: $.fn.select2.amd.require(
|
||||
'select2/selectAllAdapter'
|
||||
),
|
||||
},
|
||||
formatter: new BooleanArrayFormatter(weekdays, true),
|
||||
selector: weekdays, options: BooleanArrayOptions,
|
||||
},{
|
||||
id: 'jscmonthdays', label: gettext('Month Days'), cell: 'select2',
|
||||
group: gettext('Days'), control: 'select2', type: 'array',
|
||||
select2: {
|
||||
first_empty: false,
|
||||
multiple: true,
|
||||
allowClear: true,
|
||||
placeholder: gettext('Select the month days...'),
|
||||
width: 'style',
|
||||
showOnScroll: false,
|
||||
dropdownAdapter: $.fn.select2.amd.require(
|
||||
'select2/selectAllAdapter'
|
||||
),
|
||||
},
|
||||
formatter: new BooleanArrayFormatter(monthdays, true),
|
||||
selector: monthdays, options: BooleanArrayOptions,
|
||||
},{
|
||||
id: 'jscmonths', label: gettext('Months'), cell: 'select2',
|
||||
group: gettext('Days'), control: 'select2', type: 'array',
|
||||
select2: {
|
||||
first_empty: false,
|
||||
multiple: true,
|
||||
allowClear: true,
|
||||
placeholder: gettext('Select the months...'),
|
||||
width: 'style',
|
||||
dropdownAdapter: $.fn.select2.amd.require(
|
||||
'select2/selectAllAdapter'
|
||||
),
|
||||
},
|
||||
formatter: new BooleanArrayFormatter(months, true),
|
||||
selector: months, options: BooleanArrayOptions,
|
||||
}],
|
||||
},{
|
||||
type: 'nested', control: 'fieldset', label: gettext('Times'),
|
||||
group: gettext('Repeat'), mode: ['create', 'edit'],
|
||||
schema:[{
|
||||
id: 'jschours', label: gettext('Hours'), cell: 'select2',
|
||||
group: gettext('Times'), control: 'select2', type: 'array',
|
||||
select2: {
|
||||
first_empty: false,
|
||||
multiple: true,
|
||||
allowClear: true,
|
||||
placeholder: gettext('Select the hours...'),
|
||||
width: 'style',
|
||||
showOnScroll: false,
|
||||
dropdownAdapter: $.fn.select2.amd.require(
|
||||
'select2/selectAllAdapter'
|
||||
),
|
||||
},
|
||||
formatter: new BooleanArrayFormatter(hours, true),
|
||||
selector: hours, options: BooleanArrayOptions,
|
||||
},{
|
||||
id: 'jscminutes', label: gettext('Minutes'), cell: 'select2',
|
||||
group: gettext('Times'), control: 'select2', type: 'array',
|
||||
select2: {
|
||||
first_empty: false,
|
||||
multiple: true,
|
||||
allowClear: true,
|
||||
placeholder: gettext('Select the minutes...'),
|
||||
width: 'style',
|
||||
showOnScroll: false,
|
||||
dropdownAdapter: $.fn.select2.amd.require(
|
||||
'select2/selectAllAdapter'
|
||||
),
|
||||
},
|
||||
formatter: new BooleanArrayFormatter(minutes, true),
|
||||
selector: minutes, options: BooleanArrayOptions,
|
||||
}],
|
||||
},{
|
||||
id: 'jscexceptions', type: 'collection', mode: ['edit', 'create'],
|
||||
label: '', canEdit: false, model: ExceptionModel, canAdd: true,
|
||||
group: gettext('Exceptions'), canDelete: true,
|
||||
cols: ['jexdate', 'jextime'], control: 'sub-node-collection',
|
||||
},{
|
||||
id: 'jscdesc', label: gettext('Comment'), type: 'multiline',
|
||||
}],
|
||||
validate: function() {
|
||||
var val = this.get('jscname'),
|
||||
errMsg = null, msg;
|
||||
|
||||
if (_.isUndefined(val) || _.isNull(val) ||
|
||||
String(val).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Name cannot be empty.');
|
||||
this.errorModel.set('jscname', msg);
|
||||
errMsg = msg;
|
||||
} else {
|
||||
this.errorModel.unset('jscname');
|
||||
}
|
||||
|
||||
val = this.get('jscstart');
|
||||
if (_.isUndefined(val) || _.isNull(val) ||
|
||||
String(val).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Please enter the start time.');
|
||||
if (val == '') {
|
||||
this.set('jscstart', undefined);
|
||||
}
|
||||
this.errorModel.set('jscstart', msg);
|
||||
errMsg = errMsg || msg;
|
||||
} else {
|
||||
this.errorModel.unset('jscstart');
|
||||
}
|
||||
|
||||
// End time must be greater than Start time
|
||||
if(!errMsg) {
|
||||
val = this.get('jscend');
|
||||
// No further validation required if end date is not provided by
|
||||
// the user
|
||||
if (_.isUndefined(val) || _.isNull(val) ||
|
||||
String(val).replace(/^\s+|\s+$/g, '') == '') {
|
||||
if (val == '') {
|
||||
/* Set the default value used in model initialization */
|
||||
this.set('jscend', null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var start_time = this.get('jscstart'),
|
||||
end_time = this.get('jscend'),
|
||||
start_time_js = start_time.split(' '),
|
||||
end_time_js = end_time.split(' ');
|
||||
|
||||
start_time_js = moment(start_time_js[0] + ' ' + start_time_js[1]);
|
||||
end_time_js = moment(end_time_js[0] + ' ' + end_time_js[1]);
|
||||
|
||||
if(end_time_js.isBefore(start_time_js)) {
|
||||
errMsg = gettext('Start time must be less than end time');
|
||||
this.errorModel.set('jscstart', errMsg);
|
||||
}
|
||||
}
|
||||
|
||||
return errMsg;
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
@ -0,0 +1,435 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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 moment from 'moment';
|
||||
|
||||
const weekdays = [
|
||||
{label: gettext('Sunday'), value: 'Sunday'},
|
||||
{label: gettext('Monday'), value: 'Monday'},
|
||||
{label: gettext('Tuesday'), value: 'Tuesday'},
|
||||
{label: gettext('Wednesday'), value: 'Wednesday'},
|
||||
{label: gettext('Thursday'), value: 'Thursday'},
|
||||
{label: gettext('Friday'), value: 'Friday'},
|
||||
{label: gettext('Saturday'), value: 'Saturday'},
|
||||
],
|
||||
monthdays = [
|
||||
{label: gettext('1st'), value: '1st'}, {label: gettext('2nd'), value: '2nd'},
|
||||
{label: gettext('3rd'), value: '3rd'}, {label: gettext('4th'), value: '4th'},
|
||||
{label: gettext('5th'), value: '5th'}, {label: gettext('6th'), value: '6th'},
|
||||
{label: gettext('7th'), value: '7th'}, {label: gettext('8th'), value: '8th'},
|
||||
{label: gettext('9th'), value: '9th'}, {label: gettext('10th'), value: '10th'},
|
||||
{label: gettext('11th'), value: '11th'}, {label: gettext('12th'), value: '12th'},
|
||||
{label: gettext('13th'), value: '13th'}, {label: gettext('14th'), value: '14th'},
|
||||
{label: gettext('15th'), value: '15th'}, {label: gettext('16th'), value: '16th'},
|
||||
{label: gettext('17th'), value: '17th'}, {label: gettext('18th'), value: '18th'},
|
||||
{label: gettext('19th'), value: '19th'}, {label: gettext('20th'), value: '20th'},
|
||||
{label: gettext('21st'), value: '21st'}, {label: gettext('22nd'), value: '22nd'},
|
||||
{label: gettext('23rd'), value: '23rd'}, {label: gettext('24th'), value: '24th'},
|
||||
{label: gettext('25th'), value: '25th'}, {label: gettext('26th'), value: '26th'},
|
||||
{label: gettext('27th'), value: '27th'}, {label: gettext('28th'), value: '28th'},
|
||||
{label: gettext('29th'), value: '29th'}, {label: gettext('30th'), value: '30th'},
|
||||
{label: gettext('31st'), value: '31st'}, {label: gettext('Last day'), value: 'Last Day'},
|
||||
],
|
||||
months = [
|
||||
{label: gettext('January'),value: 'January'}, {label: gettext('February'),value: 'February'},
|
||||
{label: gettext('March'), value: 'March'}, {label: gettext('April'), value: 'April'},
|
||||
{label: gettext('May'), value: 'May'}, {label: gettext('June'), value: 'June'},
|
||||
{label: gettext('July'), value: 'July'}, {label: gettext('August'), value: 'August'},
|
||||
{label: gettext('September'), value: 'September'}, {label: gettext('October'), value: 'October'},
|
||||
{label: gettext('November'), value: 'November'}, {label: gettext('December'), value: 'December'},
|
||||
],
|
||||
hours = [
|
||||
{label: gettext('00'), value: '00'}, {label: gettext('01'), value: '01'}, {label: gettext('02'), value: '02'}, {label: gettext('03'), value: '03'},
|
||||
{label: gettext('04'), value: '04'}, {label: gettext('05'), value: '05'}, {label: gettext('06'), value: '06'}, {label: gettext('07'), value: '07'},
|
||||
{label: gettext('08'), value: '08'}, {label: gettext('09'), value: '09'}, {label: gettext('10'), value: '10'}, {label: gettext('11'), value: '11'},
|
||||
{label: gettext('12'), value: '12'}, {label: gettext('13'), value: '13'}, {label: gettext('14'), value: '14'}, {label: gettext('15'), value: '15'},
|
||||
{label: gettext('16'), value: '16'}, {label: gettext('17'), value: '17'}, {label: gettext('18'), value: '18'}, {label: gettext('19'), value: '19'},
|
||||
{label: gettext('20'), value: '20'}, {label: gettext('21'), value: '21'}, {label: gettext('22'), value: '22'}, {label: gettext('23'), value: '23'},
|
||||
],
|
||||
minutes = [
|
||||
{label: gettext('00'), value: '00'}, {label: gettext('01'), value: '01'}, {label: gettext('02'), value: '02'}, {label: gettext('03'), value: '03'},
|
||||
{label: gettext('04'), value: '04'}, {label: gettext('05'), value: '05'}, {label: gettext('06'), value: '06'}, {label: gettext('07'), value: '07'},
|
||||
{label: gettext('08'), value: '08'}, {label: gettext('09'), value: '09'}, {label: gettext('10'), value: '10'}, {label: gettext('11'), value: '11'},
|
||||
{label: gettext('12'), value: '12'}, {label: gettext('13'), value: '13'}, {label: gettext('14'), value: '14'}, {label: gettext('15'), value: '15'},
|
||||
{label: gettext('16'), value: '16'}, {label: gettext('17'), value: '17'}, {label: gettext('18'), value: '18'}, {label: gettext('19'), value: '19'},
|
||||
{label: gettext('20'), value: '20'}, {label: gettext('21'), value: '21'}, {label: gettext('22'), value: '22'}, {label: gettext('23'), value: '23'},
|
||||
{label: gettext('24'), value: '24'}, {label: gettext('25'), value: '25'}, {label: gettext('26'), value: '26'}, {label: gettext('27'), value: '27'},
|
||||
{label: gettext('28'), value: '28'}, {label: gettext('29'), value: '29'}, {label: gettext('30'), value: '30'}, {label: gettext('31'), value: '31'},
|
||||
{label: gettext('32'), value: '32'}, {label: gettext('33'), value: '33'}, {label: gettext('34'), value: '34'}, {label: gettext('35'), value: '35'},
|
||||
{label: gettext('36'), value: '36'}, {label: gettext('37'), value: '37'}, {label: gettext('38'), value: '38'}, {label: gettext('39'), value: '39'},
|
||||
{label: gettext('40'), value: '40'}, {label: gettext('41'), value: '41'}, {label: gettext('42'), value: '42'}, {label: gettext('43'), value: '43'},
|
||||
{label: gettext('44'), value: '44'}, {label: gettext('45'), value: '45'}, {label: gettext('46'), value: '46'}, {label: gettext('47'), value: '47'},
|
||||
{label: gettext('48'), value: '48'}, {label: gettext('49'), value: '49'}, {label: gettext('50'), value: '50'}, {label: gettext('51'), value: '51'},
|
||||
{label: gettext('52'), value: '52'}, {label: gettext('53'), value: '53'}, {label: gettext('54'), value: '54'}, {label: gettext('55'), value: '55'},
|
||||
{label: gettext('56'), value: '56'}, {label: gettext('57'), value: '57'}, {label: gettext('58'), value: '58'}, {label: gettext('59'), value: '59'},
|
||||
];
|
||||
|
||||
export class ExceptionsSchema extends BaseUISchema {
|
||||
constructor(fieldOptions={}, initValues) {
|
||||
super({
|
||||
jexdate: null,
|
||||
jextime: null,
|
||||
...initValues,
|
||||
});
|
||||
|
||||
this.fieldOptions = {
|
||||
...fieldOptions,
|
||||
};
|
||||
}
|
||||
|
||||
get idAttribute() {
|
||||
return 'jexid';
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
return [
|
||||
{
|
||||
id: 'jexdate', cell: 'datetimepicker', label: gettext('Date'),
|
||||
type: 'datetimepicker',
|
||||
controlProps: { format: 'YYYY-MM-DD', placeholder: gettext('<any>'),
|
||||
autoOk: true, pickerType: 'Date',
|
||||
},
|
||||
}, {
|
||||
id: 'jextime', cell: 'datetimepicker', label: gettext('Time'),
|
||||
type: 'datetimepicker',
|
||||
controlProps: { format: 'HH:mm', placeholder: gettext('<any>'),
|
||||
autoOk: true, pickerType: 'Time', ampm: false,
|
||||
},
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
validate(state, setError) {
|
||||
let d = (state.jexdate || '<any>'),
|
||||
t = (state.jextime || '<any>');
|
||||
|
||||
if (d == t && d == '<any>') {
|
||||
setError('jscdate', gettext('Please specify date/time.'));
|
||||
return true;
|
||||
} else {
|
||||
setError('jscdate', null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const BooleanArrayFormatter = {
|
||||
fromRaw: (originalValue, options) => {
|
||||
if (!_.isNull(originalValue) && !_.isUndefined(originalValue) && Array.isArray(originalValue)) {
|
||||
let retValue = [],
|
||||
index = 0;
|
||||
originalValue.forEach( function (value) {
|
||||
if (value) {
|
||||
retValue.push(options[index]);
|
||||
}
|
||||
index = index + 1;
|
||||
});
|
||||
|
||||
return retValue;
|
||||
}
|
||||
|
||||
return originalValue;
|
||||
},
|
||||
|
||||
toRaw: (selectedVal, options)=> {
|
||||
if (!_.isNull(options) && !_.isUndefined(options) && Array.isArray(options)) {
|
||||
let retValue = [];
|
||||
options.forEach( function (option) {
|
||||
let elementFound = _.find(selectedVal, (selVal)=>_.isEqual(selVal.label, option.label));
|
||||
if(_.isUndefined(elementFound)) {
|
||||
retValue.push(false);
|
||||
} else {
|
||||
retValue.push(true);
|
||||
}
|
||||
});
|
||||
|
||||
return retValue;
|
||||
}
|
||||
|
||||
return selectedVal;
|
||||
}
|
||||
};
|
||||
|
||||
export class DaysSchema extends BaseUISchema {
|
||||
constructor(fieldOptions={}, initValues) {
|
||||
super({
|
||||
...initValues,
|
||||
});
|
||||
|
||||
this.fieldOptions = {
|
||||
...fieldOptions,
|
||||
};
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
return [
|
||||
{
|
||||
id: 'jscweekdays', label: gettext('Week Days'), type: 'select',
|
||||
group: gettext('Days'),
|
||||
controlProps: { allowClear: true, multiple: true, allowSelectAll: true,
|
||||
placeholder: gettext('Select the weekdays...'),
|
||||
formatter: BooleanArrayFormatter,
|
||||
},
|
||||
options: weekdays,
|
||||
}, {
|
||||
id: 'jscmonthdays', label: gettext('Month Days'), type: 'select',
|
||||
group: gettext('Days'),
|
||||
controlProps: { allowClear: true, multiple: true, allowSelectAll: true,
|
||||
placeholder: gettext('Select the month days...'),
|
||||
formatter: BooleanArrayFormatter,
|
||||
},
|
||||
options: monthdays,
|
||||
}, {
|
||||
id: 'jscmonths', label: gettext('Months'), type: 'select',
|
||||
group: gettext('Days'),
|
||||
controlProps: { allowClear: true, multiple: true, allowSelectAll: true,
|
||||
placeholder: gettext('Select the months...'),
|
||||
formatter: BooleanArrayFormatter,
|
||||
},
|
||||
options: months,
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export class TimesSchema extends BaseUISchema {
|
||||
constructor(fieldOptions={}, initValues) {
|
||||
super({
|
||||
...initValues,
|
||||
});
|
||||
|
||||
this.fieldOptions = {
|
||||
...fieldOptions,
|
||||
};
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
return [
|
||||
{
|
||||
id: 'jschours', label: gettext('Hours'), type: 'select',
|
||||
group: gettext('Times'),
|
||||
controlProps: { allowClear: true, multiple: true, allowSelectAll: true,
|
||||
placeholder: gettext('Select the hours...'),
|
||||
formatter: BooleanArrayFormatter,
|
||||
},
|
||||
options: hours,
|
||||
}, {
|
||||
id: 'jscminutes', label: gettext('Minutes'), type: 'select',
|
||||
group: gettext('Times'),
|
||||
controlProps: { allowClear: true, multiple: true, allowSelectAll: true,
|
||||
placeholder: gettext('Select the minutes...'),
|
||||
formatter: BooleanArrayFormatter,
|
||||
},
|
||||
options: minutes,
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export default class PgaJobScheduleSchema extends BaseUISchema {
|
||||
constructor(fieldOptions={}, initValues) {
|
||||
super({
|
||||
jscid: null,
|
||||
jscjobid: null,
|
||||
jscname: '',
|
||||
jscdesc: '',
|
||||
jscenabled: true,
|
||||
jscstart: null,
|
||||
jscend: null,
|
||||
jscweekdays: _.map(weekdays, function() { return false; }),
|
||||
jscmonthdays: _.map(monthdays, function() { return false; }),
|
||||
jscmonths: _.map(months, function() { return false; }),
|
||||
jschours: _.map(hours, function() { return false; }),
|
||||
jscminutes: _.map(minutes, function() { return false; }),
|
||||
jscexceptions: [],
|
||||
...initValues,
|
||||
});
|
||||
|
||||
this.fieldOptions = {
|
||||
...fieldOptions,
|
||||
};
|
||||
}
|
||||
|
||||
get idAttribute() {
|
||||
return 'jscid';
|
||||
}
|
||||
|
||||
customFromRaw(originalValue, options) {
|
||||
if (!_.isNull(originalValue) && !_.isUndefined(originalValue) && Array.isArray(originalValue)) {
|
||||
let retValue = '';
|
||||
originalValue.forEach( function (value, index) {
|
||||
if (value) {
|
||||
retValue = retValue + options[index].label + ', ';
|
||||
}
|
||||
});
|
||||
|
||||
retValue = retValue.replace(/,\s*$/, '');
|
||||
return retValue;
|
||||
}
|
||||
|
||||
return originalValue;
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
let obj = this;
|
||||
return [
|
||||
{
|
||||
id: 'jscid', label: gettext('ID'), type: 'int', mode: ['properties'],
|
||||
}, {
|
||||
id: 'jscname', label: gettext('Name'), type: 'text', noEmpty: true, cell: 'text',
|
||||
}, {
|
||||
id: 'jscenabled', label: gettext('Enabled?'), type: 'switch', cell: 'switch',
|
||||
}, {
|
||||
id: 'jscstart', label: gettext('Start'), type: 'datetimepicker', cell: 'datetimepicker',
|
||||
controlProps: { format: 'YYYY-MM-DD HH:mm:ss Z', ampm: false,
|
||||
placeholder: gettext('YYYY-MM-DD HH:mm:ss Z'), autoOk: true,
|
||||
disablePast: true,
|
||||
},
|
||||
}, {
|
||||
id: 'jscend', label: gettext('End'), type: 'datetimepicker', cell: 'datetimepicker',
|
||||
controlProps: { format: 'YYYY-MM-DD HH:mm:ss Z', ampm: false,
|
||||
placeholder: gettext('YYYY-MM-DD HH:mm:ss Z'), autoOk: true,
|
||||
disablePast: true,
|
||||
},
|
||||
}, {
|
||||
type: 'note', mode: ['create', 'edit'], group: gettext('Repeat'),
|
||||
text: [
|
||||
'<ul><li>',
|
||||
gettext('Schedules are specified using a '),
|
||||
'<strong>', gettext('cron-style'), '</strong>',
|
||||
'format.',
|
||||
'</li><li>',
|
||||
gettext('For each selected time or date element, the schedule will execute.'),
|
||||
'</li><li>',
|
||||
gettext('e.g. To execute at 5 minutes past every hour, simply select ‘05’ in the Minutes list box.'),
|
||||
'</li><li>',
|
||||
gettext('Values from more than one field may be specified in order to further control the schedule.'),
|
||||
'</li><li>',
|
||||
gettext('e.g. To execute at 12:05 and 14:05 every Monday and Thursday, you would click minute 05, hours 12 and 14, and weekdays Monday and Thursday.'),
|
||||
'</li><li>',
|
||||
gettext('For additional flexibility, the Month Days check list includes an extra Last Day option. This matches the last day of the month, whether it happens to be the 28th, 29th, 30th or 31st.'),
|
||||
'</li></ul>',
|
||||
].join(''),
|
||||
}, {
|
||||
id: 'jscweekdays', label: gettext('Week days'), type: 'text',
|
||||
mode: ['properties'],
|
||||
controlProps: {
|
||||
formatter: {
|
||||
fromRaw: (backendVal)=> {
|
||||
return obj.customFromRaw(backendVal, weekdays);
|
||||
}
|
||||
},
|
||||
}
|
||||
}, {
|
||||
id: 'jscmonthdays', label: gettext('Month days'), type: 'text',
|
||||
mode: ['properties'],
|
||||
controlProps: {
|
||||
formatter: {
|
||||
fromRaw: (backendVal)=> {
|
||||
return obj.customFromRaw(backendVal, monthdays);
|
||||
}
|
||||
},
|
||||
}
|
||||
}, {
|
||||
id: 'jscmonths', label: gettext('Months'), type: 'text',
|
||||
mode: ['properties'],
|
||||
controlProps: {
|
||||
formatter: {
|
||||
fromRaw: (backendVal)=> {
|
||||
return obj.customFromRaw(backendVal, months);
|
||||
}
|
||||
},
|
||||
}
|
||||
}, {
|
||||
id: 'jschours', label: gettext('Hours'), type: 'text',
|
||||
mode: ['properties'],
|
||||
controlProps: {
|
||||
formatter: {
|
||||
fromRaw: (backendVal)=> {
|
||||
return obj.customFromRaw(backendVal, hours);
|
||||
}
|
||||
},
|
||||
}
|
||||
}, {
|
||||
id: 'jscminutes', label: gettext('Minutes'), type: 'text',
|
||||
mode: ['properties'],
|
||||
controlProps: {
|
||||
formatter: {
|
||||
fromRaw: (backendVal)=> {
|
||||
return obj.customFromRaw(backendVal, minutes);
|
||||
}
|
||||
},
|
||||
}
|
||||
}, {
|
||||
type: 'nested-fieldset', mode: ['create','edit'],
|
||||
label: gettext('Days'), group: gettext('Repeat'),
|
||||
schema : new DaysSchema(),
|
||||
}, {
|
||||
type: 'nested-fieldset', mode: ['create','edit'],
|
||||
label: gettext('Times'), group: gettext('Repeat'),
|
||||
schema : new TimesSchema(),
|
||||
}, {
|
||||
id: 'jscexceptions', label: gettext('Exceptions'), type: 'text',
|
||||
mode: ['properties'],
|
||||
controlProps: {
|
||||
formatter: {
|
||||
fromRaw: (backendVal)=>{
|
||||
let exceptions = '';
|
||||
if (!_.isNull(backendVal) && !_.isUndefined(backendVal) && Array.isArray(backendVal)) {
|
||||
backendVal.forEach( function (ex, index, array) {
|
||||
exceptions = exceptions + '[' + ex.jexdate + (ex.jextime ? ' ' + ex.jextime + ']': ']');
|
||||
if (index !== (array.length -1)) {
|
||||
exceptions = exceptions + ', ';
|
||||
}
|
||||
});
|
||||
}
|
||||
return exceptions;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
id: 'jscexceptions', type: 'collection', mode: ['edit', 'create'],
|
||||
schema: new ExceptionsSchema(), group: gettext('Exceptions'),
|
||||
canEdit: false, canAdd: true, canDelete: true,
|
||||
uniqueCol : ['jexdate', 'jextime'],
|
||||
}, {
|
||||
id: 'jscdesc', label: gettext('Comment'), type: 'multiline',
|
||||
}
|
||||
];
|
||||
}
|
||||
validate(state, setError) {
|
||||
if (isEmptyString(state.jscstart)) {
|
||||
setError('jscstart', gettext('Please enter the start time.'));
|
||||
return true;
|
||||
} else {
|
||||
setError('jscstart', null);
|
||||
}
|
||||
|
||||
if (!isEmptyString(state.jscend)) {
|
||||
let start_time = state.jscstart,
|
||||
end_time = state.jscend,
|
||||
start_time_js = start_time.split(' '),
|
||||
end_time_js = end_time.split(' ');
|
||||
|
||||
start_time_js = moment(start_time_js[0] + ' ' + start_time_js[1]);
|
||||
end_time_js = moment(end_time_js[0] + ' ' + end_time_js[1]);
|
||||
|
||||
if(end_time_js.isBefore(start_time_js)) {
|
||||
setError('jscend', gettext('Start time must be less than end time'));
|
||||
return true;
|
||||
} else {
|
||||
setError('jscend', null);
|
||||
}
|
||||
} else {
|
||||
state.jscend = null;
|
||||
}
|
||||
}
|
||||
}
|
@ -7,6 +7,10 @@
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import { getNodeAjaxOptions } from '../../../../../static/js/node_ajax';
|
||||
import PgaJobSchema from './pga_job.ui';
|
||||
import { getNodePgaJobStepSchema } from '../../steps/static/js/pga_jobstep.ui';
|
||||
|
||||
define('pgadmin.node.pga_job', [
|
||||
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
|
||||
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.alertifyjs',
|
||||
@ -66,33 +70,21 @@ define('pgadmin.node.pga_job', [
|
||||
icon: 'fa fa-play-circle',
|
||||
}]);
|
||||
},
|
||||
model: pgBrowser.Node.Model.extend({
|
||||
defaults: {
|
||||
jobname: '',
|
||||
jobid: undefined,
|
||||
jobenabled: true,
|
||||
jobhostagent: '',
|
||||
jobjclid: 1,
|
||||
jobcreated: undefined,
|
||||
jobchanged: undefined,
|
||||
jobnextrun: undefined,
|
||||
joblastrun: undefined,
|
||||
jlgstatus: undefined,
|
||||
jobrunningat: undefined,
|
||||
jobdesc: '',
|
||||
jsteps: [],
|
||||
jschedules: [],
|
||||
},
|
||||
idAttribute: 'jobid',
|
||||
parse: function() {
|
||||
var d = pgBrowser.Node.Model.prototype.parse.apply(this, arguments);
|
||||
|
||||
if (d) {
|
||||
d.jobrunningat = d.jagagent || gettext('Not running currently.');
|
||||
d.jlgstatus = d.jlgstatus || gettext('Unknown');
|
||||
}
|
||||
return d;
|
||||
},
|
||||
getSchema: function(treeNodeInfo, itemNodeData) {
|
||||
return new PgaJobSchema(
|
||||
{
|
||||
jobjclid: ()=>getNodeAjaxOptions('classes', this, treeNodeInfo, itemNodeData, {
|
||||
cacheLevel: 'server',
|
||||
cacheNode: 'server'
|
||||
})
|
||||
},
|
||||
() => getNodePgaJobStepSchema(treeNodeInfo, itemNodeData),
|
||||
);
|
||||
},
|
||||
|
||||
model: pgBrowser.Node.Model.extend({
|
||||
idAttribute: 'jobid',
|
||||
schema: [{
|
||||
id: 'jobname', label: gettext('Name'), type: 'text',
|
||||
cellHeaderClasses: 'width_percent_30',
|
||||
@ -102,28 +94,6 @@ define('pgadmin.node.pga_job', [
|
||||
},{
|
||||
id: 'jobenabled', label: gettext('Enabled?'), type: 'switch',
|
||||
cellHeaderClasses: 'width_percent_5',
|
||||
},{
|
||||
id: 'jobclass', label: gettext('Job class'), type: 'text',
|
||||
mode: ['properties'],
|
||||
},{
|
||||
id: 'jobjclid', label: gettext('Job class'), type: 'int',
|
||||
control: 'node-ajax-options', url: 'classes', url_with_id: false,
|
||||
cache_node: 'server', mode: ['create', 'edit'],
|
||||
select2: {allowClear: false},
|
||||
helpMessage: gettext('Please select a class to categorize the job. This option will not affect the way the job runs.'),
|
||||
},{
|
||||
id: 'jobhostagent', label: gettext('Host agent'), type: 'text',
|
||||
mode: ['edit', 'create'],
|
||||
helpMessage: gettext('Enter the hostname of a machine running pgAgent if you wish to ensure only that machine will run this job. Leave blank if any host may run the job.'),
|
||||
},{
|
||||
id: 'jobhostagent', label: gettext('Host agent'), type: 'text',
|
||||
mode: ['properties'],
|
||||
},{
|
||||
id: 'jobcreated', type: 'text', mode: ['properties'],
|
||||
label: gettext('Created'),
|
||||
},{
|
||||
id: 'jobchanged', type: 'text', mode: ['properties'],
|
||||
label: gettext('Changed'),
|
||||
},{
|
||||
id: 'jobnextrun', type: 'text', mode: ['properties'],
|
||||
label: gettext('Next run'), cellHeaderClasses: 'width_percent_20',
|
||||
@ -133,41 +103,10 @@ define('pgadmin.node.pga_job', [
|
||||
},{
|
||||
id: 'jlgstatus', type: 'text', label: gettext('Last result'),
|
||||
cellHeaderClasses: 'width_percent_5', mode: ['properties'],
|
||||
},{
|
||||
id: 'jobrunningat', type: 'text', mode: ['properties'],
|
||||
label: gettext('Running at'),
|
||||
},{
|
||||
id: 'jobdesc', label: gettext('Comment'), type: 'multiline',
|
||||
cellHeaderClasses: 'width_percent_15',
|
||||
},{
|
||||
id: 'jsteps', label: '', group: gettext('Steps'),
|
||||
type: 'collection', mode: ['edit', 'create'],
|
||||
model: pgBrowser.Nodes['pga_jobstep'].model, canEdit: true,
|
||||
control: 'sub-node-collection', canAdd: true, canDelete: true,
|
||||
showError: false,
|
||||
columns: [
|
||||
'jstname', 'jstenabled', 'jstkind', 'jstconntype', 'jstonerror',
|
||||
],
|
||||
},{
|
||||
id: 'jschedules', label: '', group: gettext('Schedules'),
|
||||
type: 'collection', mode: ['edit', 'create'],
|
||||
control: 'sub-node-collection', canAdd: true, canDelete: true,
|
||||
canEdit: true, model: pgBrowser.Nodes['pga_schedule'].model,
|
||||
showError: false,
|
||||
columns: ['jscname', 'jscenabled', 'jscstart', 'jscend'],
|
||||
}],
|
||||
validate: function() {
|
||||
var name = this.get('jobname');
|
||||
if (_.isUndefined(name) || _.isNull(name) ||
|
||||
String(name).replace(/^\s+|\s+$/g, '') == '') {
|
||||
var msg = gettext('Name cannot be empty.');
|
||||
this.errorModel.set('jobname', msg);
|
||||
return msg;
|
||||
} else {
|
||||
this.errorModel.unset('jobname');
|
||||
}
|
||||
return null;
|
||||
},
|
||||
}),
|
||||
/* Run pgagent job now */
|
||||
run_pga_job_now: function(args) {
|
||||
|
@ -0,0 +1,121 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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 PgaJobScheduleSchema from '../../schedules/static/js/pga_schedule.ui';
|
||||
|
||||
export default class PgaJobSchema extends BaseUISchema {
|
||||
constructor(fieldOptions={}, getPgaJobStepSchema, initValues) {
|
||||
super({
|
||||
jobname: '',
|
||||
jobid: undefined,
|
||||
jobenabled: true,
|
||||
jobhostagent: '',
|
||||
jobjclid: 1,
|
||||
jobcreated: undefined,
|
||||
jobchanged: undefined,
|
||||
jobnextrun: undefined,
|
||||
joblastrun: undefined,
|
||||
jlgstatus: undefined,
|
||||
jobrunningat: undefined,
|
||||
jobdesc: '',
|
||||
jsteps: [],
|
||||
jschedules: [],
|
||||
...initValues,
|
||||
});
|
||||
|
||||
this.fieldOptions = {
|
||||
jobjclid: [],
|
||||
...fieldOptions,
|
||||
};
|
||||
this.getPgaJobStepSchema = getPgaJobStepSchema;
|
||||
}
|
||||
|
||||
get idAttribute() {
|
||||
return 'jobid';
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
return [
|
||||
{
|
||||
id: 'jobname', label: gettext('Name'), type: 'text', noEmpty: true,
|
||||
},{
|
||||
id: 'jobid', label: gettext('ID'), mode: ['properties'],
|
||||
type: 'int',
|
||||
},{
|
||||
id: 'jobenabled', label: gettext('Enabled?'), type: 'switch',
|
||||
},{
|
||||
id: 'jobjclid', label: gettext('Job class'), type: 'select',
|
||||
options: this.fieldOptions.jobjclid,
|
||||
controlProps: {allowClear: false},
|
||||
mode: ['properties'],
|
||||
},{
|
||||
id: 'jobjclid', label: gettext('Job class'), type: 'select',
|
||||
options: this.fieldOptions.jobjclid,
|
||||
mode: ['create', 'edit'],
|
||||
controlProps: {allowClear: false},
|
||||
helpMessage: gettext('Please select a class to categorize the job. This option will not affect the way the job runs.'),
|
||||
},{
|
||||
id: 'jobhostagent', label: gettext('Host agent'), type: 'text',
|
||||
mode: ['properties'],
|
||||
},{
|
||||
id: 'jobhostagent', label: gettext('Host agent'), type: 'text',
|
||||
mode: ['edit', 'create'],
|
||||
helpMessage: gettext('Enter the hostname of a machine running pgAgent if you wish to ensure only that machine will run this job. Leave blank if any host may run the job.'),
|
||||
},{
|
||||
id: 'jobcreated', type: 'text', mode: ['properties'],
|
||||
label: gettext('Created'),
|
||||
},{
|
||||
id: 'jobchanged', type: 'text', mode: ['properties'],
|
||||
label: gettext('Changed'),
|
||||
},{
|
||||
id: 'jobnextrun', type: 'text', mode: ['properties'],
|
||||
label: gettext('Next run'),
|
||||
},{
|
||||
id: 'joblastrun', type: 'text', mode: ['properties'],
|
||||
label: gettext('Last run'),
|
||||
},{
|
||||
id: 'jlgstatus', type: 'text', label: gettext('Last result'), mode: ['properties'],
|
||||
controlProps: {
|
||||
formatter: {
|
||||
fromRaw: (originalValue)=>{
|
||||
return originalValue || gettext('Unknown');
|
||||
},
|
||||
}
|
||||
}
|
||||
},{
|
||||
id: 'jobrunningat', type: 'text', mode: ['properties'], label: gettext('Running at'),
|
||||
controlProps: {
|
||||
formatter: {
|
||||
fromRaw: (originalValue)=>{
|
||||
return originalValue || gettext('Not running currently.');
|
||||
},
|
||||
}
|
||||
}
|
||||
},{
|
||||
id: 'jobdesc', label: gettext('Comment'), type: 'multiline',
|
||||
},{
|
||||
id: 'jsteps', label: '', group: gettext('Steps'),
|
||||
type: 'collection', mode: ['edit', 'create'],
|
||||
schema: this.getPgaJobStepSchema(),
|
||||
canEdit: true, canAdd: true, canDelete: true,
|
||||
columns: [
|
||||
'jstname', 'jstenabled', 'jstkind', 'jstconntype', 'jstonerror',
|
||||
],
|
||||
},{
|
||||
id: 'jschedules', label: '', group: gettext('Schedules'),
|
||||
type: 'collection', mode: ['edit', 'create'],
|
||||
schema: new PgaJobScheduleSchema(),
|
||||
canAdd: true, canDelete: true, canEdit: true,
|
||||
columns: ['jscname', 'jscenabled', 'jscstart', 'jscend'],
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
@ -7,11 +7,13 @@
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import { getNodePgaJobStepSchema } from './pga_jobstep.ui';
|
||||
|
||||
define('pgadmin.node.pga_jobstep', [
|
||||
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
|
||||
'sources/pgadmin', 'pgadmin.browser', 'alertify', 'backform',
|
||||
'backgrid', 'pgadmin.backform', 'pgadmin.backgrid',
|
||||
], function(gettext, url_for, $, _, pgAdmin, pgBrowser, Alertify, Backform, Backgrid) {
|
||||
'backgrid', 'pgadmin.backform',
|
||||
], function(gettext, url_for, $, _, pgAdmin, pgBrowser, Alertify, Backform) {
|
||||
|
||||
if (!pgBrowser.Nodes['coll-pga_jobstep']) {
|
||||
pgBrowser.Nodes['coll-pga_jobstep'] =
|
||||
@ -28,40 +30,6 @@ define('pgadmin.node.pga_jobstep', [
|
||||
});
|
||||
}
|
||||
|
||||
// Switch Cell with Deps, Needed for SubNode control
|
||||
var SwitchDepsCell = Backgrid.Extension.SwitchCell.extend({
|
||||
initialize: function initialize() {
|
||||
Backgrid.Extension.SwitchCell.prototype.initialize.apply(this, arguments);
|
||||
Backgrid.Extension.DependentCell.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
dependentChanged: function dependentChanged() {
|
||||
var model = this.model,
|
||||
column = this.column,
|
||||
editable = this.column.get('editable'),
|
||||
input = this.$el.find('input[type=checkbox]').first(),
|
||||
self_name = column.get('name'),
|
||||
is_editable;
|
||||
|
||||
is_editable = _.isFunction(editable) ? !!editable.apply(column, [model]) : !!editable;
|
||||
if (is_editable) {
|
||||
this.$el.addClass('editable');
|
||||
input.bootstrapToggle('disabled', false);
|
||||
} else {
|
||||
this.$el.removeClass('editable');
|
||||
input.bootstrapToggle('disabled', true);
|
||||
// Set self value into model
|
||||
setTimeout(function () {
|
||||
model.set(self_name, true);
|
||||
}, 10);
|
||||
|
||||
}
|
||||
|
||||
this.delegateEvents();
|
||||
return this;
|
||||
},
|
||||
remove: Backgrid.Extension.DependentCell.prototype.remove,
|
||||
});
|
||||
|
||||
if (!pgBrowser.Nodes['pga_jobstep']) {
|
||||
pgBrowser.Nodes['pga_jobstep'] = pgBrowser.Node.extend({
|
||||
parent_type: 'pga_job',
|
||||
@ -94,7 +62,7 @@ define('pgadmin.node.pga_jobstep', [
|
||||
name: 'create_pga_jobstep_on_coll', node: 'coll-pga_jobstep', module: this,
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 4, label: gettext('Job Step...'),
|
||||
data: {'action': 'create'}, icon: 'wcTabIcon icon-pga_jobstep',
|
||||
data: {'action': 'create'}, icon: 'wcTabIcon icon-pga_jobsFtep',
|
||||
},{
|
||||
name: 'create_pga_jobstep', node: 'pga_jobstep', module: this,
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
@ -102,21 +70,12 @@ define('pgadmin.node.pga_jobstep', [
|
||||
data: {'action': 'create'}, icon: 'wcTabIcon icon-pga_jobstep',
|
||||
}]);
|
||||
},
|
||||
|
||||
getSchema: function(treeNodeInfo, itemNodeData) {
|
||||
return getNodePgaJobStepSchema(treeNodeInfo, itemNodeData);
|
||||
},
|
||||
|
||||
model: pgBrowser.Node.Model.extend({
|
||||
defaults: {
|
||||
jstid: null,
|
||||
jstjobid: null,
|
||||
jstname: '',
|
||||
jstdesc: '',
|
||||
jstenabled: true,
|
||||
jstkind: true,
|
||||
jstconntype: true,
|
||||
jstcode: '',
|
||||
jstconnstr: null,
|
||||
jstdbname: null,
|
||||
jstonerror: 'f',
|
||||
jstnextrun: '',
|
||||
},
|
||||
initialize: function() {
|
||||
pgBrowser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||
if (this.isNew() && this.get('jstconntype')) {
|
||||
@ -139,18 +98,16 @@ define('pgadmin.node.pga_jobstep', [
|
||||
cellHeaderClasses: 'width_percent_5', mode: ['properties'],
|
||||
},{
|
||||
id: 'jstname', label: gettext('Name'), type: 'text',
|
||||
disabled: false, cellHeaderClasses: 'width_percent_60',
|
||||
cellHeaderClasses: 'width_percent_60',
|
||||
},{
|
||||
id: 'jstenabled', label: gettext('Enabled?'),
|
||||
type: 'switch',
|
||||
disabled: function() { return false; },
|
||||
},{
|
||||
id: 'jstkind', label: gettext('Kind'), type: 'switch',
|
||||
options: {
|
||||
'onText': gettext('SQL'), 'offText': gettext('Batch'),
|
||||
'onColor': 'primary', 'offColor': 'primary',
|
||||
}, control: Backform.SwitchControl,
|
||||
disabled: function() { return false; },
|
||||
},{
|
||||
id: 'jstconntype', label: gettext('Connection type'),
|
||||
type: 'switch', deps: ['jstkind'], mode: ['properties'],
|
||||
@ -159,159 +116,14 @@ define('pgadmin.node.pga_jobstep', [
|
||||
'onText': gettext('Local'), 'offText': gettext('Remote'),
|
||||
'onColor': 'primary', 'offColor': 'primary', width: '65',
|
||||
},
|
||||
},{
|
||||
id: 'jstconntype', label: gettext('Connection type'),
|
||||
type: 'switch', deps: ['jstkind'], mode: ['create', 'edit'],
|
||||
disabled: function(m) { return !m.get('jstkind'); },
|
||||
cell: SwitchDepsCell,
|
||||
editable: function(m) {
|
||||
// If jstkind is Batch then disable it
|
||||
return m.get('jstkind');
|
||||
},
|
||||
options: {
|
||||
'onText': gettext('Local'), 'offText': gettext('Remote'),
|
||||
'onColor': 'primary', 'offColor': 'primary', width: '65',
|
||||
}, helpMessage: gettext('Select <strong>Local</strong> if the job step will execute on the local database server, or <strong>Remote</strong> to specify a remote database server.'),
|
||||
},{
|
||||
id: 'jstdbname', label: gettext('Database'), type: 'text',
|
||||
mode: ['properties'], disabled: function() { return false; },
|
||||
},{
|
||||
id: 'jstconnstr', type: 'text', mode: ['properties'],
|
||||
label: gettext('Connection string'),
|
||||
},{
|
||||
id: 'jstdbname', label: gettext('Database'), type: 'text',
|
||||
control: 'node-list-by-name', node: 'database',
|
||||
cache_node: 'database', select2: {allowClear: true, placeholder: ''},
|
||||
disabled: function(m) {
|
||||
return !m.get('jstkind') || !m.get('jstconntype');
|
||||
}, deps: ['jstkind', 'jstconntype'], mode: ['create', 'edit'],
|
||||
helpMessage: gettext('Please select the database on which the job step will run.'),
|
||||
},{
|
||||
id: 'jstconnstr', label: gettext('Connection string'), type: 'text',
|
||||
deps: ['jstkind', 'jstconntype'], disabled: function(m) {
|
||||
return !m.get('jstkind') || m.get('jstconntype');
|
||||
}, helpMessage: gettext('Please specify the connection string for the remote database server. Each parameter setting is in the form keyword = value. Spaces around the equal sign are optional. To write an empty value, or a value containing spaces, surround it with single quotes, e.g., keyword = \'a value\'. Single quotes and backslashes within the value must be escaped with a backslash, i.e., \' and \\.<br>For more information, please see the documentation on <a href="https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING" target="_blank">libpq connection strings</a>.'
|
||||
), mode: ['create', 'edit'],
|
||||
},{
|
||||
id: 'jstonerror', label: gettext('On error'), cell: 'select2',
|
||||
control: 'select2', options: [
|
||||
{'label': gettext('Fail'), 'value': 'f'},
|
||||
{'label': gettext('Success'), 'value': 's'},
|
||||
{'label': gettext('Ignore'), 'value': 'i'},
|
||||
], select2: {allowClear: false}, disabled: function() {
|
||||
return false;
|
||||
},
|
||||
},{
|
||||
id: 'jstdesc', label: gettext('Comment'), type: 'multiline',
|
||||
},{
|
||||
id: 'jstcode', label: '', cell: 'string', deps: ['jstkind'],
|
||||
type: 'text', group: gettext('Code'),
|
||||
tabPanelCodeClass: 'sql-code-control',
|
||||
control: Backform.SqlCodeControl,
|
||||
], select2: {allowClear: false},
|
||||
}],
|
||||
validate: function() {
|
||||
var val = this.get('jstname'),
|
||||
errMsg = null, msg;
|
||||
|
||||
if (
|
||||
_.isUndefined(val) || _.isNull(val) ||
|
||||
String(val).replace(/^\s+|\s+$/g, '') == ''
|
||||
) {
|
||||
errMsg = gettext('Name cannot be empty.');
|
||||
this.errorModel.set('jstname', errMsg);
|
||||
} else {
|
||||
this.errorModel.unset('jstname');
|
||||
}
|
||||
if (this.get('jstkind')) {
|
||||
if (this.get('jstconntype')) {
|
||||
this.errorModel.unset('jstconnstr');
|
||||
val = this.get('jstdbname');
|
||||
if (
|
||||
_.isUndefined(val) || _.isNull(val) ||
|
||||
String(val).replace(/^\s+|\s+$/g, '') == ''
|
||||
) {
|
||||
msg = gettext('Please select a database.');
|
||||
errMsg = errMsg || msg;
|
||||
this.errorModel.set('jstdbname', msg);
|
||||
} else {
|
||||
this.errorModel.unset('jstdbname');
|
||||
}
|
||||
} else {
|
||||
this.errorModel.unset('jstdbname');
|
||||
var r = /\s*\b(\w+)\s*=\s*('([^'\\]*(?:\\.[^'\\]*)*)'|[\w|\.]*)/g;
|
||||
val = this.get('jstconnstr');
|
||||
if (
|
||||
_.isUndefined(val) || _.isNull(val) ||
|
||||
String(val).replace(/^\s+|\s+$/g, '') == ''
|
||||
) {
|
||||
msg = gettext('Please enter a connection string.');
|
||||
} else if (String(val).replace(r, '') != '') {
|
||||
msg = gettext('Please enter a valid connection string.');
|
||||
} else {
|
||||
var m,
|
||||
params = {
|
||||
'host': true, 'hostaddr': true, 'port': true,
|
||||
'dbname': true, 'user': true, 'password': true,
|
||||
'connect_timeout': true, 'client_encoding': true,
|
||||
'application_name': true, 'options': true,
|
||||
'fallback_application_name': true, 'sslmode': true,
|
||||
'sslcert': true, 'sslkey': true, 'sslrootcert': true,
|
||||
'sslcrl': true, 'keepalives': true, 'service': true,
|
||||
'keepalives_idle': true, 'keepalives_interval': true,
|
||||
'keepalives_count': true, 'sslcompression': true,
|
||||
'requirepeer': true, 'krbsrvname': true, 'gsslib': true,
|
||||
};
|
||||
|
||||
while((m = r.exec(val))) {
|
||||
if (params[m[1]]) {
|
||||
if (m[2])
|
||||
continue;
|
||||
msg = gettext('Please enter a valid connection string.');
|
||||
break;
|
||||
}
|
||||
|
||||
msg = gettext('Invalid parameter in the connection string - %s.', m[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (msg) {
|
||||
errMsg = errMsg || msg;
|
||||
this.errorModel.set('jstconnstr', msg);
|
||||
} else {
|
||||
this.errorModel.unset('jstconnstr');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.errorModel.unset('jstconnstr');
|
||||
this.errorModel.unset('jstdbname');
|
||||
}
|
||||
|
||||
val = this.get('jstcode');
|
||||
if (
|
||||
_.isUndefined(val) || _.isNull(val) ||
|
||||
String(val).replace(/^\s+|\s+$/g, '') == ''
|
||||
) {
|
||||
msg = gettext('Please specify code to execute.');
|
||||
errMsg = errMsg || msg;
|
||||
this.errorModel.set('jstcode', msg);
|
||||
} else {
|
||||
this.errorModel.unset('jstcode');
|
||||
}
|
||||
|
||||
val = this.get('jstonerror');
|
||||
if (
|
||||
!_.isUndefined(val) && !_.isNull(val) &&
|
||||
String(val).replace(/^\s+|\s+$/g, '') == ''
|
||||
) {
|
||||
msg = gettext('Please select valid on error option.');
|
||||
this.errorModel.set('jstonerror', msg);
|
||||
} else {
|
||||
this.errorModel.unset('jstonerror');
|
||||
}
|
||||
|
||||
return errMsg;
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
@ -0,0 +1,220 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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 { getNodeListByName } from '../../../../../../static/js/node_ajax';
|
||||
import { isEmptyString } from 'sources/validators';
|
||||
|
||||
export function getNodePgaJobStepSchema(treeNodeInfo, itemNodeData) {
|
||||
return new PgaJobStepSchema(
|
||||
{
|
||||
databases: ()=>getNodeListByName('database', treeNodeInfo, itemNodeData, {
|
||||
cacheLevel: 'database',
|
||||
cacheNode: 'database'
|
||||
})
|
||||
},
|
||||
{
|
||||
jstdbname: treeNodeInfo['server']['db'],
|
||||
}
|
||||
);
|
||||
}
|
||||
export default class PgaJobStepSchema extends BaseUISchema {
|
||||
constructor(fieldOptions={}, initValues) {
|
||||
super({
|
||||
jstid: null,
|
||||
jstjobid: null,
|
||||
jstname: '',
|
||||
jstdesc: '',
|
||||
jstenabled: true,
|
||||
jstkind: true,
|
||||
jstconntype: true,
|
||||
jstcode: '',
|
||||
jstconnstr: null,
|
||||
jstdbname: null,
|
||||
jstonerror: 'f',
|
||||
jstnextrun: '',
|
||||
...initValues,
|
||||
});
|
||||
|
||||
this.fieldOptions = {
|
||||
databases: [],
|
||||
...fieldOptions,
|
||||
};
|
||||
}
|
||||
|
||||
get idAttribute() {
|
||||
return 'jstid';
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
return [
|
||||
{
|
||||
id: 'jstid', label: gettext('ID'), type: 'int',
|
||||
mode: ['properties'],
|
||||
}, {
|
||||
id: 'jstname', label: gettext('Name'), type: 'text', noEmpty: true,
|
||||
cell: 'text',
|
||||
}, {
|
||||
id: 'jstenabled', label: gettext('Enabled?'), cell: 'switch',
|
||||
type: 'switch',
|
||||
}, {
|
||||
id: 'jstkind', label: gettext('Kind'),
|
||||
type: () => {
|
||||
return {
|
||||
type: 'toggle',
|
||||
options: [
|
||||
{'label': gettext('SQL'), value: true},
|
||||
{'label': gettext('Batch'), value: false},
|
||||
],
|
||||
};
|
||||
},
|
||||
cell: () => {
|
||||
return {
|
||||
cell: '',
|
||||
controlProps: {
|
||||
formatter: {
|
||||
fromRaw: (actualVal)=> {
|
||||
return actualVal ? gettext('SQL') : gettext('Batch');
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
},
|
||||
}, {
|
||||
id: 'jstconntype', label: gettext('Connection type'),
|
||||
type: () => {
|
||||
return {
|
||||
type: 'toggle',
|
||||
options: [
|
||||
{'label': gettext('Local'), value: true},
|
||||
{'label': gettext('Remote'), value: false},
|
||||
],
|
||||
};
|
||||
},
|
||||
cell: () => {
|
||||
return {
|
||||
cell: '',
|
||||
controlProps: {
|
||||
formatter: {
|
||||
fromRaw: (actualVal)=> {
|
||||
return actualVal ? gettext('Local') : gettext('Remote');
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
},
|
||||
deps: ['jstkind'],
|
||||
disabled: function(state) { return !state.jstkind; },
|
||||
helpMessage: gettext('Select <strong>Local</strong> if the job step will execute on the local database server, or <strong>Remote</strong> to specify a remote database server.'),
|
||||
}, {
|
||||
id: 'jstdbname', label: gettext('Database'), type: 'select',
|
||||
options: this.fieldOptions.databases,
|
||||
controlProps: {allowClear: true, placeholder: ''},
|
||||
disabled: function(state) { return !state.jstkind || !state.jstconntype; },
|
||||
deps: ['jstkind', 'jstconntype'],
|
||||
helpMessage: gettext('Please select the database on which the job step will run.'),
|
||||
}, {
|
||||
id: 'jstconnstr', label: gettext('Connection string'), type: 'text',
|
||||
deps: ['jstkind', 'jstconntype'],
|
||||
disabled: function(state) { return !state.jstkind || state.jstconntype; },
|
||||
helpMessage: gettext('Please specify the connection string for the remote database server. Each parameter setting is in the form keyword = value. Spaces around the equal sign are optional. To write an empty value, or a value containing spaces, surround it with single quotes, e.g., keyword = \'a value\'. Single quotes and backslashes within the value must be escaped with a backslash, i.e., \' and \\.<br>For more information, please see the documentation on <a href="https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING" target="_blank">libpq connection strings</a>.'),
|
||||
}, {
|
||||
id: 'jstonerror', label: gettext('On error'), type: 'select',
|
||||
cell: 'select',
|
||||
options: [
|
||||
{'label': gettext('Fail'), 'value': 'f'},
|
||||
{'label': gettext('Success'), 'value': 's'},
|
||||
{'label': gettext('Ignore'), 'value': 'i'},
|
||||
],
|
||||
controlProps: {allowClear: false},
|
||||
}, {
|
||||
id: 'jstdesc', label: gettext('Comment'), type: 'multiline',
|
||||
}, {
|
||||
id: 'jstcode', label: '', deps: ['jstkind'],
|
||||
type: 'sql', group: gettext('Code'), isFullTab: true,
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
validate(state, setError) {
|
||||
let errMsg = null;
|
||||
|
||||
if (state.jstkind) {
|
||||
if (state.jstconntype) {
|
||||
if (isEmptyString(state.jstdbname)) {
|
||||
setError('jstdbname', gettext('Please select a database.'));
|
||||
return true;
|
||||
} else {
|
||||
setError('jstdbname', null);
|
||||
}
|
||||
} else {
|
||||
let r = /\s*\b(\w+)\s*=\s*('([^'\\]*(?:\\.[^'\\]*)*)'|[\w|\.]*)/g;
|
||||
if (isEmptyString(state.jstconnstr)) {
|
||||
setError('jstconnstr', gettext('Please enter a connection string.'));
|
||||
return true;
|
||||
} else if (String(state.jstconnstr).replace(r, '') != '') {
|
||||
setError('jstconnstr', gettext('Please enter a valid connection string.'));
|
||||
return true;
|
||||
} else {
|
||||
let m,
|
||||
params = {
|
||||
'host': true, 'hostaddr': true, 'port': true,
|
||||
'dbname': true, 'user': true, 'password': true,
|
||||
'connect_timeout': true, 'client_encoding': true,
|
||||
'application_name': true, 'options': true,
|
||||
'fallback_application_name': true, 'sslmode': true,
|
||||
'sslcert': true, 'sslkey': true, 'sslrootcert': true,
|
||||
'sslcrl': true, 'keepalives': true, 'service': true,
|
||||
'keepalives_idle': true, 'keepalives_interval': true,
|
||||
'keepalives_count': true, 'sslcompression': true,
|
||||
'requirepeer': true, 'krbsrvname': true, 'gsslib': true,
|
||||
};
|
||||
|
||||
while((m = r.exec(state.jstconnstr))) {
|
||||
if (params[m[1]]) {
|
||||
if (m[2])
|
||||
continue;
|
||||
errMsg = gettext('Please enter a valid connection string.');
|
||||
break;
|
||||
}
|
||||
|
||||
errMsg = gettext('Invalid parameter in the connection string - %s.', m[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (errMsg) {
|
||||
setError('jstconnstr', errMsg);
|
||||
return true;
|
||||
} else {
|
||||
errMsg = null;
|
||||
setError('jstconnstr', errMsg);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setError('jstconnstr', null);
|
||||
setError('jstdbname', null);
|
||||
}
|
||||
|
||||
if (isEmptyString(state.jstcode)) {
|
||||
setError('jstcode', gettext('Please specify code to execute.'));
|
||||
return true;
|
||||
} else {
|
||||
setError('jstcode', null);
|
||||
}
|
||||
|
||||
if (isEmptyString(state.jstonerror)) {
|
||||
setError('jstonerror', gettext('Please select valid on error option.'));
|
||||
return true;
|
||||
} else {
|
||||
setError('jstonerror', null);
|
||||
}
|
||||
}
|
||||
}
|
@ -35,272 +35,6 @@ define('pgadmin.node.role', [
|
||||
});
|
||||
}
|
||||
|
||||
var SecurityModel = pgAdmin.Browser.SecurityModel =
|
||||
pgAdmin.Browser.SecurityModel || pgAdmin.Browser.Node.Model.extend({
|
||||
defaults: {
|
||||
provider: null,
|
||||
label: null,
|
||||
},
|
||||
schema: [{
|
||||
id: 'provider', label: gettext('Provider'),
|
||||
type: 'text', disabled: false,
|
||||
cellHeaderClasses:'width_percent_50',
|
||||
},{
|
||||
id: 'label', label: gettext('Security label'),
|
||||
type: 'text', disabled: false,
|
||||
}],
|
||||
validate: function() {
|
||||
var data = this.toJSON(), msg;
|
||||
|
||||
if (_.isUndefined(data.provider) ||
|
||||
_.isNull(data.provider) ||
|
||||
String(data.provider).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Please specify the value for all the security providers.');
|
||||
this.errorModel.set('provider', msg);
|
||||
return msg;
|
||||
} else {
|
||||
this.errorModel.unset('provider');
|
||||
}
|
||||
|
||||
if (_.isUndefined(data.label) ||
|
||||
_.isNull(data.label) ||
|
||||
String(data.label).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Please specify the value for all the security providers.') ;
|
||||
this.errorModel.set('label', msg);
|
||||
return msg;
|
||||
} else {
|
||||
this.errorModel.unset('label');
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
var RoleMembersControl = Backform.Control.extend({
|
||||
defaults: _.defaults(
|
||||
{extraClasses: ['col-12 col-sm-12 col-md-12']},
|
||||
Backform.NodeListByNameControl.prototype.defaults
|
||||
),
|
||||
initialize: function() {
|
||||
Backform.NodeListByNameControl.prototype.initialize.apply(this, arguments);
|
||||
},
|
||||
formatter: {
|
||||
fromRaw: function (rawData) {
|
||||
var res = _.isObject(rawData) ? rawData : JSON.parse(rawData);
|
||||
|
||||
return _.pluck(res, 'role');
|
||||
},
|
||||
toRaw: function (formattedData) { return formattedData; },
|
||||
},
|
||||
template: _.template([
|
||||
'<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>',
|
||||
'<div class="<%=Backform.controlsClassName%>">',
|
||||
' <select title = <%=label%> multiple="multiple" style="width:100%;" class="pgadmin-controls <%=extraClasses.join(\' \')%>" name="<%=name%>" value="<%-JSON.stringify(value)%>" <%=disabled ? "disabled" : ""%> <%=readonly ? "readonly" : ""%> <%=required ? "required" : ""%>>',
|
||||
' <% for (var i=0; i < options.length; i++) { %>',
|
||||
' <% var option = options[i]; %>',
|
||||
' <option value=<%-option.value%> data-icon=<%-option.image%> <%=value != null && _.indexOf(value, option.value) != -1 ? "selected" : ""%> <%=option.disabled ? "disabled=\'disabled\'" : ""%>><%-option.label%></option>',
|
||||
' <% } %>',
|
||||
' </select>',
|
||||
' <% if (helpMessage && helpMessage.length) { %>',
|
||||
' <span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>',
|
||||
' <% } %>',
|
||||
'</div>',
|
||||
].join('\n')),
|
||||
selectionTemplate: _.template([
|
||||
'<span>',
|
||||
' <span class="wcTabIcon <%= optimage %>"></span>',
|
||||
' <span><%= opttext %><span>',
|
||||
' <% if (checkbox) { %>',
|
||||
' <div class="custom-control custom-checkbox custom-checkbox-no-label d-inline">',
|
||||
' <input tabindex="-1" type="checkbox" class="custom-control-input" id="check_<%= opttext %>" <%=disabled ? "disabled" : ""%> />',
|
||||
' <label class="custom-control-label" for="check_<%= opttext %>">',
|
||||
' <span class="sr-only">WITH ADMIN<span>',
|
||||
' </label>',
|
||||
' </div>',
|
||||
' <% } %>',
|
||||
'</span>',
|
||||
].join('\n')),
|
||||
events: {'change select': 'onChange'},
|
||||
getValueFromDOM: function() {
|
||||
var res = [];
|
||||
|
||||
this.$el.find('select').find(':selected').each(function() {
|
||||
res.push($(this).attr('value'));
|
||||
});
|
||||
|
||||
return res;
|
||||
},
|
||||
render: function() {
|
||||
var field = _.defaults(this.field.toJSON(), this.defaults),
|
||||
attributes = this.model.toJSON(),
|
||||
attrArr = field.name.split('.'),
|
||||
name = attrArr.shift(),
|
||||
path = attrArr.join('.'),
|
||||
rawValue = this.keyPathAccessor(attributes[name], path),
|
||||
data = _.extend(field, {
|
||||
rawValue: rawValue,
|
||||
value: this.formatter.fromRaw(rawValue, this.model),
|
||||
attributes: attributes,
|
||||
formatter: this.formatter,
|
||||
}),
|
||||
evalF = function(f, d, m) {
|
||||
return (_.isFunction(f) ? !!f.apply(d, [m]) : !!f);
|
||||
},
|
||||
evalASFunc = function(f, d, m) {
|
||||
return (_.isFunction(f) ? f.apply(d, [m]) : f);
|
||||
};
|
||||
|
||||
// Evaluate the disabled, visible, and required option
|
||||
_.extend(data, {
|
||||
disabled: evalF(data.disabled, data, this.model),
|
||||
visible: evalF(data.visible, data, this.model),
|
||||
readonly: evalF(data.readonly, data, this.model),
|
||||
required: evalF(data.required, data, this.model),
|
||||
helpMessage: evalASFunc(data.helpMessage, data, this.model),
|
||||
});
|
||||
// Evaluation the options
|
||||
if (_.isFunction(data.options)) {
|
||||
try {
|
||||
data.options = data.options.apply(this);
|
||||
} catch(e) {
|
||||
// Do nothing
|
||||
data.options = [];
|
||||
this.model.trigger(
|
||||
'pgadmin-view:transform:error', this.model, this.field, e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up first
|
||||
this.$el.removeClass(Backform.hiddenClassName);
|
||||
|
||||
if (!data.visible)
|
||||
this.$el.addClass(Backform.hiddenClassName);
|
||||
|
||||
this.$el.html(this.template(data)).addClass(field.name);
|
||||
this.updateInvalid();
|
||||
|
||||
var self = this,
|
||||
collection = this.model.get(this.field.get('name')),
|
||||
formatState = function(opt) {
|
||||
if (!opt.id) {
|
||||
return opt.text;
|
||||
}
|
||||
|
||||
var optimage = $(opt.element).data('icon');
|
||||
|
||||
if(!optimage){
|
||||
return opt.text;
|
||||
} else {
|
||||
var d = _.extend(
|
||||
{}, data, {
|
||||
'opttext': _.escape(opt.text),
|
||||
'optimage': optimage,
|
||||
'checkbox': false,
|
||||
});
|
||||
return $(self.selectionTemplate(d));
|
||||
}
|
||||
},
|
||||
formatSelection = function (opt) {
|
||||
|
||||
if (!opt.id) {
|
||||
return opt.text;
|
||||
}
|
||||
|
||||
var optimage = $(opt.element).data('icon'),
|
||||
grantUpdate = function(ev) {
|
||||
|
||||
_.each(collection.where({role: opt.id}), function(m) {
|
||||
m.set('admin', $(ev.target).is(':checked'));
|
||||
});
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
if(!optimage){
|
||||
return opt.text;
|
||||
} else {
|
||||
var d = _.extend(
|
||||
{}, data, {
|
||||
'opttext': _.escape(opt.text),
|
||||
'optimage': optimage,
|
||||
'checkbox': true,
|
||||
}),
|
||||
j = $(self.selectionTemplate(d));
|
||||
|
||||
// Update the checkbox lazy
|
||||
setTimeout(
|
||||
function() {
|
||||
_.each(collection.where({role: opt.id}), function(m) {
|
||||
j.find('input').prop('checked', m.get('admin'));
|
||||
});
|
||||
}, 200);
|
||||
|
||||
(j.find('input')).on('change', grantUpdate);
|
||||
|
||||
return j;
|
||||
}
|
||||
};
|
||||
|
||||
this.$el.find('select').select2({
|
||||
templateResult: formatState,
|
||||
templateSelection: formatSelection,
|
||||
multiple: true,
|
||||
tags: true,
|
||||
allowClear: data.disabled ? false : true,
|
||||
placeholder: data.disabled ? '' : gettext('Select roles'),
|
||||
width: 'style',
|
||||
disabled: data.readonly ? true : false,
|
||||
}).on('change', function(e) {
|
||||
$(e.target).find(':selected').each(function() {
|
||||
});
|
||||
});
|
||||
|
||||
return this;
|
||||
},
|
||||
onChange: function() {
|
||||
var model = this.model,
|
||||
attrArr = this.field.get('name').split('.'),
|
||||
name = attrArr.shift(),
|
||||
vals = this.getValueFromDOM(),
|
||||
collection = model.get(name),
|
||||
removed = [];
|
||||
|
||||
this.stopListening(this.model, 'change:' + name, this.render);
|
||||
|
||||
/*
|
||||
* Iterate through all the values, and find out how many are already
|
||||
* present in the collection.
|
||||
*/
|
||||
collection.each(function(m) {
|
||||
var role = m.get('role'),
|
||||
idx = _.indexOf(vals, role);
|
||||
|
||||
if (idx > -1) {
|
||||
vals.splice(idx, 1);
|
||||
} else {
|
||||
removed.push(role);
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Adding new values
|
||||
*/
|
||||
_.each(vals, function(v) {
|
||||
collection.add({role: v});
|
||||
});
|
||||
|
||||
/*
|
||||
* Removing unwanted!
|
||||
*/
|
||||
_.each(removed, function(v) {
|
||||
collection.remove(collection.where({role: v}));
|
||||
});
|
||||
|
||||
this.listenTo(this.model, 'change:' + name, this.render);
|
||||
},
|
||||
});
|
||||
|
||||
if (!pgBrowser.Nodes['role']) {
|
||||
pgAdmin.Browser.Nodes['role'] = pgAdmin.Browser.Node.extend({
|
||||
parent_type: 'server',
|
||||
|
@ -259,6 +259,9 @@ export default function FormView({
|
||||
tabsClassname[group] = classes.fullSpace;
|
||||
fullTabs.push(group);
|
||||
}
|
||||
|
||||
const id = field.id || `control${tabs[group].length}`;
|
||||
|
||||
tabs[group].push(
|
||||
useMemo(()=><MappedFormControl
|
||||
inputRef={(ele)=>{
|
||||
@ -267,11 +270,12 @@ export default function FormView({
|
||||
}
|
||||
}}
|
||||
state={value}
|
||||
key={field.id}
|
||||
key={id}
|
||||
viewHelperProps={viewHelperProps}
|
||||
name={field.id}
|
||||
value={value[field.id]}
|
||||
name={id}
|
||||
value={value[id]}
|
||||
{...field}
|
||||
id={id}
|
||||
readonly={readonly}
|
||||
disabled={disabled}
|
||||
visible={visible}
|
||||
@ -279,7 +283,7 @@ export default function FormView({
|
||||
/* Get the changes on dependent fields as well */
|
||||
dataDispatch({
|
||||
type: SCHEMA_STATE_ACTIONS.SET_VALUE,
|
||||
path: accessPath.concat(field.id),
|
||||
path: accessPath.concat(id),
|
||||
value: value,
|
||||
});
|
||||
}}
|
||||
@ -287,7 +291,7 @@ export default function FormView({
|
||||
className={classes.controlRow}
|
||||
noLabel={field.isFullTab}
|
||||
/>, [
|
||||
value[field.id],
|
||||
value[id],
|
||||
readonly,
|
||||
disabled,
|
||||
visible,
|
||||
|
@ -225,7 +225,11 @@ function validateSchema(schema, sessData, setError) {
|
||||
if(dupInd > 0) {
|
||||
let uniqueColNames = _.filter(field.schema.fields, (uf)=>field.uniqueCol.indexOf(uf.id) > -1)
|
||||
.map((uf)=>uf.label).join(', ');
|
||||
setError(field.uniqueCol[0], gettext('%s in %s must be unique.', uniqueColNames, field.label));
|
||||
if (_.isUndefined(field.label) || _.isNull(field.label)) {
|
||||
setError(field.uniqueCol[0], gettext('%s must be unique.', uniqueColNames));
|
||||
} else {
|
||||
setError(field.uniqueCol[0], gettext('%s in %s must be unique.', uniqueColNames, field.label));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/* Loop through data */
|
||||
@ -587,6 +591,8 @@ function SchemaDialogView({
|
||||
if(viewHelperProps.mode !== 'edit') {
|
||||
/* If new then merge the changed data with origData */
|
||||
changeData = _.assign({}, schema.origData, changeData);
|
||||
} else {
|
||||
changeData[schema.idAttribute] = schema.origData[schema.idAttribute];
|
||||
}
|
||||
/* Call the passed incoming getSQLValue func to get the SQL
|
||||
return of getSQLValue should be a promise.
|
||||
|
@ -157,6 +157,10 @@ basicSettings = createMuiTheme(basicSettings, {
|
||||
MuiFormHelperText: {
|
||||
root: {
|
||||
fontSize: '1em',
|
||||
},
|
||||
contained: {
|
||||
marginLeft: 0,
|
||||
marginRight: 0,
|
||||
}
|
||||
},
|
||||
MuiTypography: {
|
||||
|
@ -28,6 +28,7 @@ import PropTypes from 'prop-types';
|
||||
import HTMLReactParse from 'html-react-parser';
|
||||
import { KeyboardDateTimePicker, KeyboardDatePicker, KeyboardTimePicker, MuiPickersUtilsProvider} from '@material-ui/pickers';
|
||||
import MomentUtils from '@date-io/moment';
|
||||
import moment from 'moment';
|
||||
|
||||
import CodeMirror from './CodeMirror';
|
||||
import gettext from 'sources/gettext';
|
||||
@ -121,7 +122,7 @@ export function FormInput({children, error, className, label, helpMessage, requi
|
||||
<FormControl error={Boolean(error)} fullWidth>
|
||||
{React.cloneElement(children, {cid, helpid})}
|
||||
</FormControl>
|
||||
<FormHelperText id={helpid} variant="outlined">{helpMessage}</FormHelperText>
|
||||
<FormHelperText id={helpid} variant="outlined">{HTMLReactParse(helpMessage || '')}</FormHelperText>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
@ -202,22 +203,30 @@ export function InputDateTimePicker({value, onChange, readonly, controlProps, ..
|
||||
onChange(momentVal ? momentVal.format(controlProps.format || 'HH:mm') : null);
|
||||
};
|
||||
|
||||
if (readonly) {
|
||||
return (<InputText value={value} {...props}/>);
|
||||
}
|
||||
|
||||
if (controlProps && controlProps.pickerType === 'Date') {
|
||||
return (
|
||||
<MuiPickersUtilsProvider utils={MomentUtils}>
|
||||
<KeyboardDatePicker value={value} onChange={onDateChange} readOnly={Boolean(readonly)}
|
||||
format={controlProps.format || 'YYYY-MM-DD'}
|
||||
placeholder={controlProps.placeholder || 'YYYY-MM-DD'}
|
||||
{...props}/>
|
||||
autoOk={controlProps.autoOk || false}
|
||||
{...props} label={''}/>
|
||||
</MuiPickersUtilsProvider>
|
||||
);
|
||||
} else if (controlProps && controlProps.pickerType === 'Time') {
|
||||
let newValue = (!_.isNull(value) && !_.isUndefined(value)) ? moment(value, 'HH:mm').toDate() : value;
|
||||
return (
|
||||
<MuiPickersUtilsProvider utils={MomentUtils}>
|
||||
<KeyboardTimePicker value={value} onChange={onTimeChange} readOnly={Boolean(readonly)}
|
||||
<KeyboardTimePicker value={newValue} onChange={onTimeChange} readOnly={Boolean(readonly)}
|
||||
format={controlProps.format || 'HH:mm'}
|
||||
placeholder={controlProps.placeholder || 'HH:mm'}
|
||||
{...props}/>
|
||||
autoOk={controlProps.autoOk || false}
|
||||
ampm={controlProps.ampm || false}
|
||||
{...props} label={''}/>
|
||||
</MuiPickersUtilsProvider>
|
||||
);
|
||||
}
|
||||
@ -229,13 +238,15 @@ export function InputDateTimePicker({value, onChange, readonly, controlProps, ..
|
||||
ampm={controlProps.ampm || false}
|
||||
format={controlProps.format || 'YYYY-MM-DD HH:mm:ss Z'}
|
||||
placeholder={controlProps.placeholder || 'YYYY-MM-DD HH:mm:ss Z'}
|
||||
autoOk={controlProps.autoOk || true}
|
||||
autoOk={controlProps.autoOk || false}
|
||||
disablePast={controlProps.disablePast || false}
|
||||
value={value}
|
||||
invalidDateMessage=""
|
||||
maxDateMessage=""
|
||||
minDateMessage=""
|
||||
onChange={onDateTimeChange}
|
||||
readOnly={Boolean(readonly)}
|
||||
{...props}
|
||||
{...props} label={''}
|
||||
/>
|
||||
</MuiPickersUtilsProvider>
|
||||
);
|
||||
@ -636,12 +647,13 @@ function getRealValue(options, value, creatable, formatter) {
|
||||
realValue = [...value];
|
||||
/* If multi select options need to be in some format by UI, use formatter */
|
||||
if(formatter) {
|
||||
realValue = formatter.fromRaw(realValue);
|
||||
}
|
||||
if(creatable) {
|
||||
realValue = realValue.map((val)=>({label:val, value: val}));
|
||||
realValue = formatter.fromRaw(realValue, options);
|
||||
} else {
|
||||
realValue = realValue.map((val)=>(_.find(options, (option)=>option.value==val)));
|
||||
if(creatable) {
|
||||
realValue = realValue.map((val)=>({label:val, value: val}));
|
||||
} else {
|
||||
realValue = realValue.map((val)=>(_.find(options, (option)=>_.isEqual(option.value, val))));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
realValue = _.find(options, (option)=>option.value==value) ||
|
||||
@ -676,18 +688,6 @@ export function InputSelect({
|
||||
return ()=>umounted=true;
|
||||
}, [optionsReloadBasis]);
|
||||
|
||||
const onChangeOption = useCallback((selectVal, action)=>{
|
||||
if(_.isArray(selectVal)) {
|
||||
/* If multi select options need to be in some format by UI, use formatter */
|
||||
selectVal = selectVal.map((option)=>option.value);
|
||||
if(controlProps.formatter) {
|
||||
selectVal = controlProps.formatter.toRaw(selectVal);
|
||||
}
|
||||
onChange && onChange(selectVal, action.name);
|
||||
} else {
|
||||
onChange && onChange(selectVal ? selectVal.value : null, action.name);
|
||||
}
|
||||
}, [onChange]);
|
||||
|
||||
/* Apply filter if any */
|
||||
const filteredOptions = (controlProps.filter && controlProps.filter(finalOptions)) || finalOptions;
|
||||
@ -700,6 +700,24 @@ export function InputSelect({
|
||||
|
||||
const styles = customReactSelectStyles(theme, readonly || disabled);
|
||||
|
||||
const onChangeOption = useCallback((selectVal, action)=>{
|
||||
if(_.isArray(selectVal)) {
|
||||
// Check if select all option is selected
|
||||
if (!_.isUndefined(selectVal.find(x => x.label === 'Select All'))) {
|
||||
selectVal = filteredOptions;
|
||||
}
|
||||
/* If multi select options need to be in some format by UI, use formatter */
|
||||
if(controlProps.formatter) {
|
||||
selectVal = controlProps.formatter.toRaw(selectVal, filteredOptions);
|
||||
} else {
|
||||
selectVal = selectVal.map((option)=>option.value);
|
||||
}
|
||||
onChange && onChange(selectVal, action.name);
|
||||
} else {
|
||||
onChange && onChange(selectVal ? selectVal.value : null, action.name);
|
||||
}
|
||||
}, [onChange, filteredOptions]);
|
||||
|
||||
const commonProps = {
|
||||
components: {
|
||||
Option: CustomSelectOption,
|
||||
@ -709,7 +727,7 @@ export function InputSelect({
|
||||
openMenuOnClick: !readonly,
|
||||
onChange: onChangeOption,
|
||||
isLoading: isLoading,
|
||||
options: filteredOptions,
|
||||
options: controlProps.allowSelectAll ? [{ label: 'Select All', value: '*' }, ...filteredOptions] : filteredOptions,
|
||||
value: realValue,
|
||||
menuPortalTarget: document.body,
|
||||
styles: styles,
|
||||
|
92
web/regression/javascript/schema_ui_files/pga_job.ui.spec.js
Normal file
92
web/regression/javascript/schema_ui_files/pga_job.ui.spec.js
Normal file
@ -0,0 +1,92 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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 PgaJobSchema from '../../../pgadmin/browser/server_groups/servers/pgagent/static/js/pga_job.ui';
|
||||
|
||||
describe('PgaJobSchema', ()=>{
|
||||
let mount;
|
||||
let schemaObj = new PgaJobSchema();
|
||||
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: 'edit',
|
||||
}}
|
||||
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={()=>{}}
|
||||
/>);
|
||||
});
|
||||
});
|
||||
|
156
web/regression/javascript/schema_ui_files/pga_jobstep.ui.spec.js
Normal file
156
web/regression/javascript/schema_ui_files/pga_jobstep.ui.spec.js
Normal file
@ -0,0 +1,156 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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 PgaJobStepSchema from '../../../pgadmin/browser/server_groups/servers/pgagent/steps/static/js/pga_jobstep.ui';
|
||||
|
||||
describe('PgaJobStepSchema', ()=>{
|
||||
let mount;
|
||||
let schemaObj = new PgaJobStepSchema(
|
||||
{
|
||||
databases: ()=>[],
|
||||
},
|
||||
[],
|
||||
{
|
||||
jstdbname: '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: 'edit',
|
||||
}}
|
||||
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('validate', ()=>{
|
||||
let state = {};
|
||||
let setError = jasmine.createSpy('setError');
|
||||
|
||||
state.name = 'my_step';
|
||||
state.jstkind = true;
|
||||
state.jstconntype = true;
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('jstdbname', 'Please select a database.');
|
||||
|
||||
state.jstdbname = 'postgres';
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('jstdbname', null);
|
||||
|
||||
state.jstconntype = false;
|
||||
state.jstconnstr = null;
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('jstconnstr', 'Please enter a connection string.');
|
||||
|
||||
state.jstconnstr = '**!!';
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('jstconnstr', 'Please enter a connection string.');
|
||||
|
||||
state.jstconnstr = 'host:\'192.168.1.7\'';
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('jstconnstr', 'Please enter a valid connection string.');
|
||||
|
||||
state.jstconnstr = 'host:\'192.168.1.7\'';
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('jstconnstr', 'Please enter a valid connection string.');
|
||||
|
||||
state.jstconnstr = 'hostaddrtest=\'192.168.1.7\'';
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('jstconnstr', 'Invalid parameter in the connection string - hostaddrtest.');
|
||||
|
||||
state.jstconnstr = 'host=\'192.168.1.7\' port=5432';
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('jstconnstr', null);
|
||||
|
||||
state.jstcode = null;
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('jstcode', 'Please specify code to execute.');
|
||||
|
||||
state.jstcode = 'PERFORM 1;';
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('jstcode', null);
|
||||
|
||||
state.jstonerror = null;
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('jstonerror', 'Please select valid on error option.');
|
||||
|
||||
state.jstonerror = 'f';
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('jstonerror', null);
|
||||
});
|
||||
});
|
||||
|
@ -0,0 +1,206 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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 PgaJobScheduleSchema from '../../../pgadmin/browser/server_groups/servers/pgagent/schedules/static/js/pga_schedule.ui';
|
||||
import { ExceptionsSchema } from '../../../pgadmin/browser/server_groups/servers/pgagent/schedules/static/js/pga_schedule.ui';
|
||||
|
||||
describe('PgaJobScheduleSchema', ()=>{
|
||||
let mount;
|
||||
let schemaObj = new PgaJobScheduleSchema([], {
|
||||
jscweekdays:[true,true,true,true,false,false,true],
|
||||
jscexceptions:[{'jexid':81,'jexdate':'2021-08-05','jextime':'12:55:00'},{'jexid':83,'jexdate':'2021-08-17','jextime':'20:00:00'}],
|
||||
});
|
||||
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: 'edit',
|
||||
}}
|
||||
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('validate', ()=>{
|
||||
let state = {};
|
||||
let setError = jasmine.createSpy('setError');
|
||||
|
||||
state.jscstart = null;
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('jscstart', 'Please enter the start time.');
|
||||
|
||||
state.jscstart = '2021-08-04 12:35:00+05:30';
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('jscstart', null);
|
||||
|
||||
state.jscend = '2021-08-04 11:35:00+05:30';
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('jscend', 'Start time must be less than end time');
|
||||
|
||||
state.jscend = '2021-08-04 15:35:00+05:30';
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('jscend', null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ExceptionsSchema', ()=>{
|
||||
let mount;
|
||||
let schemaObj = new ExceptionsSchema();
|
||||
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: 'edit',
|
||||
}}
|
||||
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('validate', ()=>{
|
||||
let state = {};
|
||||
let setError = jasmine.createSpy('setError');
|
||||
|
||||
state.jexdate = '<any>';
|
||||
state.jextime = '<any>';
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('jscdate', 'Please specify date/time.');
|
||||
|
||||
state.jexdate = '2021-08-04';
|
||||
state.jextime = '12:35';
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('jscdate', null);
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user