mirror of
https://github.com/ansible/awx.git
synced 2026-05-23 16:47:45 -02:30
Inventory group edit dialog is now draggable and resizable. Added tab for schedules fixed issue with Variables.js helper- when json variables object empty, return --- rather than {}
This commit is contained in:
@@ -86,8 +86,18 @@ angular.module('ansible', [
|
|||||||
'HomeGroupListDefinition',
|
'HomeGroupListDefinition',
|
||||||
'HomeHostListDefinition',
|
'HomeHostListDefinition',
|
||||||
'ActivityDetailDefinition',
|
'ActivityDetailDefinition',
|
||||||
'VariablesHelper'
|
'VariablesHelper',
|
||||||
|
'SchedulesListDefinition',
|
||||||
|
'AngularScheduler',
|
||||||
|
'Timezones',
|
||||||
|
'SchedulesHelper'
|
||||||
])
|
])
|
||||||
|
|
||||||
|
.constant('AngularScheduler.partial', $basePath + 'lib/angular-scheduler/lib/angular-scheduler.html')
|
||||||
|
.constant('AngularScheduler.useTimezone', false)
|
||||||
|
.constant('AngularScheduler.showUTCField', false)
|
||||||
|
.constant('$timezones.definitions.location', $basePath + 'lib/angular-tz-extensions/tz/data')
|
||||||
|
|
||||||
.config(['$routeProvider',
|
.config(['$routeProvider',
|
||||||
function ($routeProvider) {
|
function ($routeProvider) {
|
||||||
$routeProvider.
|
$routeProvider.
|
||||||
@@ -131,6 +141,11 @@ angular.module('ansible', [
|
|||||||
controller: 'JobTemplatesEdit'
|
controller: 'JobTemplatesEdit'
|
||||||
}).
|
}).
|
||||||
|
|
||||||
|
when('/job_templates/:id/schedules', {
|
||||||
|
templateUrl: urlPrefix + 'partials/schedule_detail.html',
|
||||||
|
controller: 'ScheduleEdit'
|
||||||
|
}).
|
||||||
|
|
||||||
when('/projects', {
|
when('/projects', {
|
||||||
templateUrl: urlPrefix + 'partials/projects.html',
|
templateUrl: urlPrefix + 'partials/projects.html',
|
||||||
controller: 'ProjectsList'
|
controller: 'ProjectsList'
|
||||||
@@ -146,6 +161,11 @@ angular.module('ansible', [
|
|||||||
controller: 'ProjectsEdit'
|
controller: 'ProjectsEdit'
|
||||||
}).
|
}).
|
||||||
|
|
||||||
|
when('/projects/:id/schedules', {
|
||||||
|
templateUrl: urlPrefix + 'partials/schedule_detail.html',
|
||||||
|
controller: 'ScheduleEdit'
|
||||||
|
}).
|
||||||
|
|
||||||
when('/projects/:project_id/organizations', {
|
when('/projects/:project_id/organizations', {
|
||||||
templateUrl: urlPrefix + 'partials/projects.html',
|
templateUrl: urlPrefix + 'partials/projects.html',
|
||||||
controller: 'OrganizationsList'
|
controller: 'OrganizationsList'
|
||||||
|
|||||||
135
awx/ui/static/js/controllers/Schedules.js
Normal file
135
awx/ui/static/js/controllers/Schedules.js
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
|
||||||
|
/************************************
|
||||||
|
* Copyright (c) 2014 AnsibleWorks, Inc.
|
||||||
|
*
|
||||||
|
* Schedules.js
|
||||||
|
*
|
||||||
|
* Controller functions for the Schedule model.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function ScheduleEdit($scope, $compile, $location, $routeParams, SchedulesList, GenerateList, Rest, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope,
|
||||||
|
GetBasePath, LookUpInit, Wait, SchedulerInit, Breadcrumbs, SearchInit, PaginateInit, PageRangeSetup, EditSchedule, AddSchedule, Find) {
|
||||||
|
|
||||||
|
ClearScope();
|
||||||
|
|
||||||
|
var base, e, id, job_template, url;
|
||||||
|
|
||||||
|
base = $location.path().replace(/^\//, '').split('/')[0];
|
||||||
|
|
||||||
|
$scope.$on('job_template_ready', function() {
|
||||||
|
// Add breadcrumbs
|
||||||
|
LoadBreadCrumbs({
|
||||||
|
path: $location.path().replace(/\/schedules$/,''),
|
||||||
|
title: job_template.name
|
||||||
|
});
|
||||||
|
e = angular.element(document.getElementById('breadcrumbs'));
|
||||||
|
e.html(Breadcrumbs({ list: SchedulesList, mode: 'edit' }));
|
||||||
|
$compile(e)($scope);
|
||||||
|
|
||||||
|
// Add schedules list
|
||||||
|
GenerateList.inject(SchedulesList, {
|
||||||
|
mode: 'edit',
|
||||||
|
id: 'schedule-list-target',
|
||||||
|
breadCrumbs: false,
|
||||||
|
searchSize: 'col-lg-4 col-md-4 col-sm-3'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Change later to use GetBasePath(base)
|
||||||
|
switch(base) {
|
||||||
|
case 'job_templates':
|
||||||
|
url = '/static/sample/data/schedules/data.json';
|
||||||
|
break;
|
||||||
|
case 'projects':
|
||||||
|
url = '/static/sample/data/schedules/projects/data.json';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
SearchInit({
|
||||||
|
scope: $scope,
|
||||||
|
set: 'schedules',
|
||||||
|
list: SchedulesList,
|
||||||
|
url: url
|
||||||
|
});
|
||||||
|
PaginateInit({
|
||||||
|
scope: $scope,
|
||||||
|
list: SchedulesList,
|
||||||
|
url: url
|
||||||
|
});
|
||||||
|
|
||||||
|
Rest.setUrl(url);
|
||||||
|
Rest.get()
|
||||||
|
.success(function(data) {
|
||||||
|
var i, modifier;
|
||||||
|
PageRangeSetup({
|
||||||
|
scope: $scope,
|
||||||
|
count: data.count,
|
||||||
|
next: data.next,
|
||||||
|
previous: data.previous,
|
||||||
|
iterator: SchedulesList.iterator
|
||||||
|
});
|
||||||
|
$scope[SchedulesList.iterator + 'Loading'] = false;
|
||||||
|
for (i = 1; i <= 3; i++) {
|
||||||
|
modifier = (i === 1) ? '' : i;
|
||||||
|
$scope[SchedulesList.iterator + 'HoldInput' + modifier] = false;
|
||||||
|
}
|
||||||
|
$scope.schedules = data.results;
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
Wait('stop');
|
||||||
|
$scope.$emit('PostRefresh');
|
||||||
|
$scope.schedules = data.results;
|
||||||
|
})
|
||||||
|
.error(function(data, status) {
|
||||||
|
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
|
||||||
|
msg: 'Call to ' + url + ' failed. GET returned: ' + status });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.editSchedule = function(id) {
|
||||||
|
var schedule = Find({ list: $scope.schedules, key: 'id', val: id });
|
||||||
|
EditSchedule({ scope: $scope, schedule: schedule, url: url });
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.addSchedule = function() {
|
||||||
|
var schedule = { };
|
||||||
|
switch(base) {
|
||||||
|
case 'job_templates':
|
||||||
|
schedule.job_template = $routeParams.id;
|
||||||
|
schedule.job_type = 'playbook_run';
|
||||||
|
schedule.job_class = "ansible:playbook";
|
||||||
|
break;
|
||||||
|
case 'inventories':
|
||||||
|
schedule.inventory = $routeParams.id;
|
||||||
|
schedule.job_type = 'inventory_sync';
|
||||||
|
schedule.job_class = "inventory:sync";
|
||||||
|
break;
|
||||||
|
case 'projects':
|
||||||
|
schedule.project = $routeParams.id;
|
||||||
|
schedule.job_type = 'project_sync';
|
||||||
|
schedule.job_class = "project:sync";
|
||||||
|
}
|
||||||
|
AddSchedule({ scope: $scope, schedule: schedule, url: url });
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//scheduler = SchedulerInit({ scope: $scope });
|
||||||
|
//scheduler.inject('scheduler-target', true);
|
||||||
|
|
||||||
|
id = $routeParams.id;
|
||||||
|
Rest.setUrl(GetBasePath(base) + id);
|
||||||
|
Rest.get()
|
||||||
|
.success(function(data) {
|
||||||
|
job_template = data;
|
||||||
|
$scope.$emit('job_template_ready');
|
||||||
|
})
|
||||||
|
.error(function(status) {
|
||||||
|
ProcessErrors($scope, null, status, null, { hdr: 'Error!',
|
||||||
|
msg: 'Call to ' + url + ' failed. GET returned: ' + status });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ScheduleEdit.$inject = ['$scope', '$compile', '$location', '$routeParams', 'SchedulesList', 'GenerateList', 'Rest', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller',
|
||||||
|
'ClearScope', 'GetBasePath', 'LookUpInit', 'Wait', 'SchedulerInit', 'Breadcrumbs', 'SearchInit', 'PaginateInit', 'PageRangeSetup', 'EditSchedule', 'AddSchedule',
|
||||||
|
'Find'
|
||||||
|
];
|
||||||
@@ -14,7 +14,7 @@ angular.module('GroupFormDefinition', [])
|
|||||||
showTitle: true,
|
showTitle: true,
|
||||||
cancelButton: false,
|
cancelButton: false,
|
||||||
name: 'group',
|
name: 'group',
|
||||||
well: true,
|
well: false,
|
||||||
formLabelSize: 'col-lg-3',
|
formLabelSize: 'col-lg-3',
|
||||||
formFieldSize: 'col-lg-9',
|
formFieldSize: 'col-lg-9',
|
||||||
|
|
||||||
@@ -24,6 +24,9 @@ angular.module('GroupFormDefinition', [])
|
|||||||
}, {
|
}, {
|
||||||
name: 'source',
|
name: 'source',
|
||||||
label: 'Source'
|
label: 'Source'
|
||||||
|
},{
|
||||||
|
name: 'schedules',
|
||||||
|
label: 'Schedules'
|
||||||
}],
|
}],
|
||||||
|
|
||||||
fields: {
|
fields: {
|
||||||
@@ -46,7 +49,7 @@ angular.module('GroupFormDefinition', [])
|
|||||||
type: 'textarea',
|
type: 'textarea',
|
||||||
addRequired: false,
|
addRequired: false,
|
||||||
editRequird: false,
|
editRequird: false,
|
||||||
rows: 6,
|
rows: 12,
|
||||||
'default': '---',
|
'default': '---',
|
||||||
dataTitle: 'Group Variables',
|
dataTitle: 'Group Variables',
|
||||||
dataPlacement: 'right',
|
dataPlacement: 'right',
|
||||||
@@ -184,7 +187,7 @@ angular.module('GroupFormDefinition', [])
|
|||||||
},
|
},
|
||||||
|
|
||||||
buttons: {
|
buttons: {
|
||||||
|
/*
|
||||||
labelClass: 'col-lg-3',
|
labelClass: 'col-lg-3',
|
||||||
controlClass: 'col-lg-5',
|
controlClass: 'col-lg-5',
|
||||||
|
|
||||||
@@ -196,6 +199,7 @@ angular.module('GroupFormDefinition', [])
|
|||||||
ngClick: 'formReset()',
|
ngClick: 'formReset()',
|
||||||
ngDisabled: true //Disabled when $pristine
|
ngDisabled: true //Disabled when $pristine
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
},
|
},
|
||||||
|
|
||||||
related: { }
|
related: { }
|
||||||
|
|||||||
@@ -346,6 +346,54 @@ angular.module('JobTemplateFormDefinition', [])
|
|||||||
icon: 'icon-zoom-in'
|
icon: 'icon-zoom-in'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
schedules: {
|
||||||
|
type: 'collection',
|
||||||
|
title: 'Schedules',
|
||||||
|
iterator: 'schedule',
|
||||||
|
index: true,
|
||||||
|
open: false,
|
||||||
|
|
||||||
|
fields: {
|
||||||
|
name: {
|
||||||
|
key: true,
|
||||||
|
label: 'Name'
|
||||||
|
},
|
||||||
|
dtstart: {
|
||||||
|
label: 'Start'
|
||||||
|
},
|
||||||
|
dtend: {
|
||||||
|
label: 'End'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
add: {
|
||||||
|
mode: 'all',
|
||||||
|
ngClick: 'addSchedule()',
|
||||||
|
awToolTip: 'Add a new schedule'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
fieldActions: {
|
||||||
|
edit: {
|
||||||
|
label: 'Edit',
|
||||||
|
ngClick: "editSchedule(schedule.id)",
|
||||||
|
icon: 'icon-edit',
|
||||||
|
awToolTip: 'Edit schedule',
|
||||||
|
dataPlacement: 'top'
|
||||||
|
},
|
||||||
|
|
||||||
|
"delete": {
|
||||||
|
label: 'Delete',
|
||||||
|
ngClick: "deleteSchedule(schedule.id)",
|
||||||
|
icon: 'icon-trash',
|
||||||
|
awToolTip: 'Delete schedule',
|
||||||
|
dataPlacement: 'top'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -129,9 +129,9 @@ angular.module('ProjectFormDefinition', [])
|
|||||||
hdr: 'GIT URLs',
|
hdr: 'GIT URLs',
|
||||||
content: '<p>Example URLs for GIT SCM include:</p><ul class=\"no-bullets\"><li>https://github.com/ansible/ansible.git</li>' +
|
content: '<p>Example URLs for GIT SCM include:</p><ul class=\"no-bullets\"><li>https://github.com/ansible/ansible.git</li>' +
|
||||||
'<li>git@github.com:ansible/ansible.git</li><li>git://servername.example.com/ansible.git</li></ul>' +
|
'<li>git@github.com:ansible/ansible.git</li><li>git://servername.example.com/ansible.git</li></ul>' +
|
||||||
'<p><strong>Note:</strong> If using SSH protocol for GitHub or Bitbucket, enter in the SSH key only, ' +
|
'<p><strong>Note:</strong> When using SSH protocol for GitHub or Bitbucket, enter an SSH key only, ' +
|
||||||
'do not enter a username (other than git). Additionally, GitHub and Bitbucket do not support password authentication when using ' +
|
'do not enter a username (other than git). Additionally, GitHub and Bitbucket do not support password authentication when using ' +
|
||||||
'SSH protocol. GIT read only protocol (git://) does not use username or password information.',
|
'SSH. GIT read only protocol (git://) does not use username or password information.',
|
||||||
show: "scm_type.value == 'git'"
|
show: "scm_type.value == 'git'"
|
||||||
}, {
|
}, {
|
||||||
hdr: 'SVN URLs',
|
hdr: 'SVN URLs',
|
||||||
@@ -262,6 +262,54 @@ angular.module('ProjectFormDefinition', [])
|
|||||||
awToolTip: 'Delete the organization'
|
awToolTip: 'Delete the organization'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
schedules: {
|
||||||
|
type: 'collection',
|
||||||
|
title: 'Schedules',
|
||||||
|
iterator: 'schedule',
|
||||||
|
index: true,
|
||||||
|
open: false,
|
||||||
|
|
||||||
|
fields: {
|
||||||
|
name: {
|
||||||
|
key: true,
|
||||||
|
label: 'Name'
|
||||||
|
},
|
||||||
|
dtstart: {
|
||||||
|
label: 'Start'
|
||||||
|
},
|
||||||
|
dtend: {
|
||||||
|
label: 'End'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
add: {
|
||||||
|
mode: 'all',
|
||||||
|
ngClick: 'addSchedule()',
|
||||||
|
awToolTip: 'Add a new schedule'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
fieldActions: {
|
||||||
|
edit: {
|
||||||
|
label: 'Edit',
|
||||||
|
ngClick: "editSchedule(schedule.id)",
|
||||||
|
icon: 'icon-edit',
|
||||||
|
awToolTip: 'Edit schedule',
|
||||||
|
dataPlacement: 'top'
|
||||||
|
},
|
||||||
|
|
||||||
|
"delete": {
|
||||||
|
label: 'Delete',
|
||||||
|
ngClick: "deleteSchedule(schedule.id)",
|
||||||
|
icon: 'icon-trash',
|
||||||
|
awToolTip: 'Delete schedule',
|
||||||
|
dataPlacement: 'top'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -551,25 +551,138 @@ angular.module('GroupsHelper', ['RestServices', 'Utilities', 'ListGenerator', 'G
|
|||||||
defaultUrl = GetBasePath('groups') + group_id + '/',
|
defaultUrl = GetBasePath('groups') + group_id + '/',
|
||||||
master = {},
|
master = {},
|
||||||
choicesReady,
|
choicesReady,
|
||||||
scope = generator.inject(form, { mode: 'edit', modal: true, related: false, show_modal: false });
|
scope, html, x, y, ww, wh, maxrows;
|
||||||
|
|
||||||
generator.reset();
|
|
||||||
|
|
||||||
|
html = "<div id=\"group-modal-dialog\" title=\"Group Edit\">\n" +
|
||||||
|
"<div id=\"form-container\" style=\"width: 100%;\"></div></div>\n";
|
||||||
|
$('#inventory-modal-container').empty().append(html);
|
||||||
|
scope = generator.inject(form, { mode: 'edit', id: 'form-container', breadCrumbs: false, related: false });
|
||||||
|
//generator.reset();
|
||||||
GetSourceTypeOptions({ scope: scope, variable: 'source_type_options' });
|
GetSourceTypeOptions({ scope: scope, variable: 'source_type_options' });
|
||||||
|
|
||||||
scope.formModalActionLabel = 'Save';
|
|
||||||
scope.formModalHeader = 'Group';
|
|
||||||
scope.formModalCancelShow = true;
|
|
||||||
scope.source = form.fields.source['default'];
|
scope.source = form.fields.source['default'];
|
||||||
scope.sourcePathRequired = false;
|
scope.sourcePathRequired = false;
|
||||||
|
scope[form.fields.source_vars.parseTypeName] = 'yaml';
|
||||||
|
scope.parseType = 'yaml';
|
||||||
|
|
||||||
|
function waitStop() { Wait('stop'); }
|
||||||
|
|
||||||
|
function textareaResize(textareaID) {
|
||||||
|
var formHeight = $('#group_form').height(),
|
||||||
|
windowHeight = $('#group-modal-dialog').height(),
|
||||||
|
current_height, height, rows, row_height, model;
|
||||||
|
Wait('start');
|
||||||
|
current_height = $('#' + textareaID).height();
|
||||||
|
row_height = Math.floor( current_height / $('#' + textareaID).attr('rows'));
|
||||||
|
height = current_height + windowHeight - formHeight;
|
||||||
|
rows = Math.floor(height / row_height) - 3;
|
||||||
|
rows = (rows < 6) ? 6 : rows;
|
||||||
|
$('#' + textareaID).attr('rows', rows);
|
||||||
|
if (scope.codeMirror) {
|
||||||
|
model = $('#' + textareaID).attr('ng-model');
|
||||||
|
scope[model] = scope.codeMirror.getValue();
|
||||||
|
scope.codeMirror.destroy();
|
||||||
|
}
|
||||||
|
ParseTypeChange({ scope: scope, field_id: textareaID, onReady: waitStop });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set modal dimensions based on viewport width
|
||||||
|
ww = $(document).width();
|
||||||
|
wh = $('body').height();
|
||||||
|
if (ww > 1199) {
|
||||||
|
// desktop
|
||||||
|
x = 675;
|
||||||
|
y = (750 > wh) ? wh - 20 : 750;
|
||||||
|
maxrows = 18;
|
||||||
|
} else if (ww <= 1199 && ww >= 768) {
|
||||||
|
x = 550;
|
||||||
|
y = (620 > wh) ? wh - 15 : 620;
|
||||||
|
maxrows = 12;
|
||||||
|
} else {
|
||||||
|
x = (ww - 20);
|
||||||
|
y = (500 > wh) ? wh : 500;
|
||||||
|
maxrows = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the modal
|
||||||
|
$('#group-modal-dialog').dialog({
|
||||||
|
buttons: {
|
||||||
|
'Cancel': function() {
|
||||||
|
scope.cancelModal();
|
||||||
|
},
|
||||||
|
'Save': function () {
|
||||||
|
//setTimeout(function(){
|
||||||
|
// scope.$apply(function(){
|
||||||
|
scope.saveGroup();
|
||||||
|
// });
|
||||||
|
//});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modal: true,
|
||||||
|
width: x,
|
||||||
|
height: y,
|
||||||
|
autoOpen: false,
|
||||||
|
create: function () {
|
||||||
|
$('.ui-dialog[aria-describedby="group-modal-dialog"]').find('.ui-dialog-titlebar button').empty().attr({'class': 'close'}).text('x');
|
||||||
|
$('.ui-dialog[aria-describedby="group-modal-dialog"]').find('.ui-dialog-buttonset button').each(function () {
|
||||||
|
var c, h, i, l;
|
||||||
|
l = $(this).text();
|
||||||
|
if (l === 'Cancel') {
|
||||||
|
h = "fa-times";
|
||||||
|
c = "btn btn-default";
|
||||||
|
i = "group-close-button";
|
||||||
|
$(this).attr({
|
||||||
|
'class': c,
|
||||||
|
'id': i
|
||||||
|
}).html("<i class=\"fa " + h + "\"></i> Cancel");
|
||||||
|
} else if (l === 'Save') {
|
||||||
|
h = "fa-check";
|
||||||
|
c = "btn btn-primary";
|
||||||
|
i = "group-save-button";
|
||||||
|
$(this).attr({
|
||||||
|
'class': c,
|
||||||
|
'id': i
|
||||||
|
}).html("<i class=\"fa " + h + "\"></i> Save");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
resizeStop: function () {
|
||||||
|
// for some reason, after resizing dialog the form and fields (the content) doesn't expand to 100%
|
||||||
|
var dialog = $('.ui-dialog[aria-describedby="group-modal-dialog"]'),
|
||||||
|
content = dialog.find('#group-modal-dialog');
|
||||||
|
content.width(dialog.width() - 28);
|
||||||
|
if ($('#group_tabs .active a').text() === 'Properties') {
|
||||||
|
textareaResize('group_variables');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
close: function () {
|
||||||
|
// Destroy on close
|
||||||
|
$('.tooltip').each(function () {
|
||||||
|
// Remove any lingering tooltip <div> elements
|
||||||
|
$(this).remove();
|
||||||
|
});
|
||||||
|
$('.popover').each(function () {
|
||||||
|
// remove lingering popover <div> elements
|
||||||
|
$(this).remove();
|
||||||
|
});
|
||||||
|
$('#group-modal-dialog').dialog('destroy');
|
||||||
|
$('#inventory-modal-container').empty();
|
||||||
|
scope.cancelModal();
|
||||||
|
},
|
||||||
|
open: function () {
|
||||||
|
Wait('stop');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$('#group_tabs a[data-toggle="tab"]').on('show.bs.tab', function (e) {
|
$('#group_tabs a[data-toggle="tab"]').on('show.bs.tab', function (e) {
|
||||||
var callback = function(){ Wait('stop'); };
|
var callback = function(){
|
||||||
|
Wait('stop');
|
||||||
|
};
|
||||||
if ($(e.target).text() === 'Properties') {
|
if ($(e.target).text() === 'Properties') {
|
||||||
Wait('start');
|
Wait('start');
|
||||||
ParseTypeChange({ scope: scope, field_id: 'group_variables', onReady: callback });
|
setTimeout(function(){ textareaResize('group_variables'); }, 300);
|
||||||
|
//ParseTypeChange({ scope: scope, field_id: 'group_variables', onReady: callback });
|
||||||
}
|
}
|
||||||
else {
|
else if ($(e.target).text() === 'Scope') {
|
||||||
if (scope.source && scope.source.value === 'ec2') {
|
if (scope.source && scope.source.value === 'ec2') {
|
||||||
Wait('start');
|
Wait('start');
|
||||||
ParseTypeChange({ scope: scope, variable: 'source_vars', parse_variable: form.fields.source_vars.parseTypeName,
|
ParseTypeChange({ scope: scope, variable: 'source_vars', parse_variable: form.fields.source_vars.parseTypeName,
|
||||||
@@ -578,15 +691,16 @@ angular.module('GroupsHelper', ['RestServices', 'Utilities', 'ListGenerator', 'G
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
scope[form.fields.source_vars.parseTypeName] = 'yaml';
|
|
||||||
scope.parseType = 'yaml';
|
|
||||||
|
|
||||||
if (scope.groupVariablesLoadedRemove) {
|
if (scope.groupVariablesLoadedRemove) {
|
||||||
scope.groupVariablesLoadedRemove();
|
scope.groupVariablesLoadedRemove();
|
||||||
}
|
}
|
||||||
scope.groupVariablesLoadedRemove = scope.$on('groupVariablesLoaded', function () {
|
scope.groupVariablesLoadedRemove = scope.$on('groupVariablesLoaded', function () {
|
||||||
var callback = function() { Wait('stop'); };
|
//$('#group_tabs a:first').tab('show');
|
||||||
ParseTypeChange({ scope: scope, field_id: 'group_variables', onReady: callback });
|
|
||||||
|
//ParseTypeChange({ scope: scope, field_id: 'group_variables', onReady: callback });
|
||||||
|
Wait('start');
|
||||||
|
$('#group-modal-dialog').dialog('open');
|
||||||
|
setTimeout(function() { textareaResize('group_variables'); }, 300);
|
||||||
});
|
});
|
||||||
|
|
||||||
// After the group record is loaded, retrieve related data
|
// After the group record is loaded, retrieve related data
|
||||||
@@ -600,6 +714,7 @@ angular.module('GroupsHelper', ['RestServices', 'Utilities', 'ListGenerator', 'G
|
|||||||
Rest.get()
|
Rest.get()
|
||||||
.success(function (data) {
|
.success(function (data) {
|
||||||
scope.variables = ParseVariableString(data);
|
scope.variables = ParseVariableString(data);
|
||||||
|
master.variables = scope.variables;
|
||||||
scope.$emit('groupVariablesLoaded');
|
scope.$emit('groupVariablesLoaded');
|
||||||
})
|
})
|
||||||
.error(function (data, status) {
|
.error(function (data, status) {
|
||||||
@@ -609,9 +724,9 @@ angular.module('GroupsHelper', ['RestServices', 'Utilities', 'ListGenerator', 'G
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
scope.variables = "---";
|
scope.variables = "---";
|
||||||
|
master.variables = scope.variables;
|
||||||
scope.$emit('groupVariablesLoaded');
|
scope.$emit('groupVariablesLoaded');
|
||||||
}
|
}
|
||||||
master.variables = scope.variables;
|
|
||||||
|
|
||||||
if (scope.source_url) {
|
if (scope.source_url) {
|
||||||
// get source data
|
// get source data
|
||||||
@@ -724,7 +839,7 @@ angular.module('GroupsHelper', ['RestServices', 'Utilities', 'ListGenerator', 'G
|
|||||||
}
|
}
|
||||||
scope.variable_url = data.related.variable_data;
|
scope.variable_url = data.related.variable_data;
|
||||||
scope.source_url = data.related.inventory_source;
|
scope.source_url = data.related.inventory_source;
|
||||||
$('#form-modal').modal('show');
|
//$('#form-modal').modal('show');
|
||||||
scope.$emit('groupLoaded');
|
scope.$emit('groupLoaded');
|
||||||
})
|
})
|
||||||
.error(function (data, status) {
|
.error(function (data, status) {
|
||||||
@@ -803,7 +918,7 @@ angular.module('GroupsHelper', ['RestServices', 'Utilities', 'ListGenerator', 'G
|
|||||||
|
|
||||||
scope.formModalActionDisabled = false;
|
scope.formModalActionDisabled = false;
|
||||||
|
|
||||||
$('#form-modal').modal('hide');
|
$('#group-modal-dialog').dialog('close');
|
||||||
|
|
||||||
// Change the selected group
|
// Change the selected group
|
||||||
if (groups_reload && parent_scope.selected_tree_id !== tree_id) {
|
if (groups_reload && parent_scope.selected_tree_id !== tree_id) {
|
||||||
@@ -864,6 +979,12 @@ angular.module('GroupsHelper', ['RestServices', 'Utilities', 'ListGenerator', 'G
|
|||||||
|
|
||||||
// Cancel
|
// Cancel
|
||||||
scope.cancelModal = function () {
|
scope.cancelModal = function () {
|
||||||
|
try {
|
||||||
|
$('#group-modal-dialog').dialog('close');
|
||||||
|
}
|
||||||
|
catch(e) {
|
||||||
|
//ignore
|
||||||
|
}
|
||||||
if (scope.searchCleanup) {
|
if (scope.searchCleanup) {
|
||||||
scope.searchCleanup();
|
scope.searchCleanup();
|
||||||
}
|
}
|
||||||
@@ -871,7 +992,7 @@ angular.module('GroupsHelper', ['RestServices', 'Utilities', 'ListGenerator', 'G
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Save
|
// Save
|
||||||
scope.formModalAction = function () {
|
scope.saveGroup = function () {
|
||||||
Wait('start');
|
Wait('start');
|
||||||
var fld, data, json_data;
|
var fld, data, json_data;
|
||||||
|
|
||||||
|
|||||||
@@ -19,23 +19,21 @@ angular.module('ParseHelper', ['Utilities', 'AngularCodeMirrorModule'])
|
|||||||
fld = (params.variable) ? params.variable : 'variables',
|
fld = (params.variable) ? params.variable : 'variables',
|
||||||
pfld = (params.parse_variable) ? params.parse_variable : 'parseType',
|
pfld = (params.parse_variable) ? params.parse_variable : 'parseType',
|
||||||
onReady = params.onReady,
|
onReady = params.onReady,
|
||||||
onChange = params.onChange,
|
onChange = params.onChange;
|
||||||
codeMirror;
|
|
||||||
|
|
||||||
function removeField() {
|
function removeField() {
|
||||||
//set our model to the last change in CodeMirror and then destroy CodeMirror
|
//set our model to the last change in CodeMirror and then destroy CodeMirror
|
||||||
scope[fld] = codeMirror.getValue();
|
scope[fld] = scope.codeMirror.getValue();
|
||||||
codeMirror.destroy();
|
scope.codeMirror.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
function createField(onChange, onReady) {
|
function createField(onChange, onReady) {
|
||||||
//hide the textarea and show a fresh CodeMirror with the current mode (json or yaml)
|
//hide the textarea and show a fresh CodeMirror with the current mode (json or yaml)
|
||||||
codeMirror = AngularCodeMirror();
|
scope.codeMirror = AngularCodeMirror();
|
||||||
codeMirror.addModes($AnsibleConfig.variable_edit_modes);
|
scope.codeMirror.addModes($AnsibleConfig.variable_edit_modes);
|
||||||
codeMirror.showTextArea({ scope: scope, model: fld, element: field_id, mode: scope[pfld], onReady: onReady, onChange: onChange });
|
scope.codeMirror.showTextArea({ scope: scope, model: fld, element: field_id, mode: scope[pfld], onReady: onReady, onChange: onChange });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Hide the textarea and show a CodeMirror editor
|
// Hide the textarea and show a CodeMirror editor
|
||||||
createField(onChange, onReady);
|
createField(onChange, onReady);
|
||||||
|
|
||||||
|
|||||||
245
awx/ui/static/js/helpers/Schedules.js
Normal file
245
awx/ui/static/js/helpers/Schedules.js
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
/*********************************************
|
||||||
|
* Copyright (c) 2014 AnsibleWorks, Inc.
|
||||||
|
*
|
||||||
|
* Schedules Helper
|
||||||
|
*
|
||||||
|
* Display the scheduler widget in a dialog
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
angular.module('SchedulesHelper', ['Utilities', 'SchedulesHelper'])
|
||||||
|
|
||||||
|
.factory('ShowSchedulerModal', ['Wait', function(Wait) {
|
||||||
|
return function(params) {
|
||||||
|
// Set modal dimensions based on viewport width
|
||||||
|
|
||||||
|
var ww, wh, x, y, maxrows, scope = params.scope;
|
||||||
|
|
||||||
|
ww = $(document).width();
|
||||||
|
wh = $('body').height();
|
||||||
|
if (ww > 1199) {
|
||||||
|
// desktop
|
||||||
|
x = 675;
|
||||||
|
y = (625 > wh) ? wh - 20 : 625;
|
||||||
|
maxrows = 20;
|
||||||
|
} else if (ww <= 1199 && ww >= 768) {
|
||||||
|
x = 550;
|
||||||
|
y = (625 > wh) ? wh - 15 : 625;
|
||||||
|
maxrows = 15;
|
||||||
|
} else {
|
||||||
|
x = (ww - 20);
|
||||||
|
y = (625 > wh) ? wh : 625;
|
||||||
|
maxrows = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the modal
|
||||||
|
$('#scheduler-modal-dialog').dialog({
|
||||||
|
buttons: {
|
||||||
|
'Cancel': function() {
|
||||||
|
$(this).dialog('close');
|
||||||
|
},
|
||||||
|
'Save': function () {
|
||||||
|
setTimeout(function(){
|
||||||
|
scope.$apply(function(){
|
||||||
|
scope.saveSchedule();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modal: true,
|
||||||
|
width: x,
|
||||||
|
height: y,
|
||||||
|
autoOpen: false,
|
||||||
|
create: function () {
|
||||||
|
$('.ui-dialog[aria-describedby="scheduler-modal-dialog"]').find('.ui-dialog-titlebar button').empty().attr({'class': 'close'}).text('x');
|
||||||
|
$('.ui-dialog[aria-describedby="scheduler-modal-dialog"]').find('.ui-dialog-buttonset button').each(function () {
|
||||||
|
var c, h, i, l;
|
||||||
|
l = $(this).text();
|
||||||
|
if (l === 'Cancel') {
|
||||||
|
h = "fa-times";
|
||||||
|
c = "btn btn-default";
|
||||||
|
i = "schedule-close-button";
|
||||||
|
$(this).attr({
|
||||||
|
'class': c,
|
||||||
|
'id': i
|
||||||
|
}).html("<i class=\"fa " + h + "\"></i> Cancel");
|
||||||
|
} else if (l === 'Save') {
|
||||||
|
h = "fa-check";
|
||||||
|
c = "btn btn-primary";
|
||||||
|
i = "schedule-save-button";
|
||||||
|
$(this).attr({
|
||||||
|
'class': c,
|
||||||
|
'id': i
|
||||||
|
}).html("<i class=\"fa " + h + "\"></i> Save");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$('#scheduler-tabs a:first').tab('show');
|
||||||
|
$('#rrule_nlp_description').dblclick(function() {
|
||||||
|
setTimeout(function() { scope.$apply(function() { scope.showRRuleDetail = (scope.showRRuleDetail) ? false : true; }); }, 100);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
resizeStop: function () {
|
||||||
|
// for some reason, after resizing dialog the form and fields (the content) doesn't expand to 100%
|
||||||
|
var dialog = $('.ui-dialog[aria-describedby="status-modal-dialog"]'),
|
||||||
|
content = dialog.find('#scheduler-modal-dialog');
|
||||||
|
content.width(dialog.width() - 28);
|
||||||
|
},
|
||||||
|
close: function () {
|
||||||
|
// Destroy on close
|
||||||
|
$('.tooltip').each(function () {
|
||||||
|
// Remove any lingering tooltip <div> elements
|
||||||
|
$(this).remove();
|
||||||
|
});
|
||||||
|
$('.popover').each(function () {
|
||||||
|
// remove lingering popover <div> elements
|
||||||
|
$(this).remove();
|
||||||
|
});
|
||||||
|
$('#scheduler-modal-dialog').dialog('destroy');
|
||||||
|
$('#scheduler-modal-dialog #form-container').empty();
|
||||||
|
},
|
||||||
|
open: function () {
|
||||||
|
Wait('stop');
|
||||||
|
$('#schedulerName').focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}])
|
||||||
|
|
||||||
|
.factory('ShowDetails', [ function() {
|
||||||
|
return function(params) {
|
||||||
|
|
||||||
|
var scope = params.scope,
|
||||||
|
scheduler = params.scheduler,
|
||||||
|
e = params.e,
|
||||||
|
rrule;
|
||||||
|
|
||||||
|
if ($(e.target).text() === 'Details') {
|
||||||
|
if (scheduler.isValid()) {
|
||||||
|
scope.schedulerIsValid = true;
|
||||||
|
rrule = scheduler.getRRule();
|
||||||
|
scope.occurrence_list = [];
|
||||||
|
scope.dateChoice = 'utc';
|
||||||
|
rrule.all(function(date, i){
|
||||||
|
if (i < 10) {
|
||||||
|
scope.occurrence_list.push({ utc: date.toUTCString(), local: date.toString() });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
scope.rrule_nlp_description = rrule.toText().replace(/^RRule error.*$/,'Natural language description not available');
|
||||||
|
scope.rrule = scheduler.getValue().rrule;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
scope.schedulerIsValid = false;
|
||||||
|
$('#scheduler-tabs a:first').tab('show');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}])
|
||||||
|
|
||||||
|
.factory('EditSchedule', ['SchedulerInit', 'ShowSchedulerModal', 'ShowDetails', 'Wait', 'Rest',
|
||||||
|
function(SchedulerInit, ShowSchedulerModal, ShowDetails, Wait, Rest) {
|
||||||
|
return function(params) {
|
||||||
|
var scope = params.scope,
|
||||||
|
schedule = params.schedule,
|
||||||
|
url = params.url,
|
||||||
|
scheduler;
|
||||||
|
|
||||||
|
Wait('start');
|
||||||
|
$('#form-container').empty();
|
||||||
|
scheduler = SchedulerInit({ scope: scope });
|
||||||
|
scheduler.inject('form-container', false);
|
||||||
|
ShowSchedulerModal({ scope: scope });
|
||||||
|
scope.showRRuleDetail = false;
|
||||||
|
|
||||||
|
setTimeout(function(){
|
||||||
|
$('#scheduler-modal-dialog').dialog('open');
|
||||||
|
scope.$apply(function() {
|
||||||
|
scheduler.setRRule(schedule.rrule);
|
||||||
|
scheduler.setName(schedule.name);
|
||||||
|
});
|
||||||
|
$('#schedulerName').focus();
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
scope.saveSchedule = function() {
|
||||||
|
var newSchedule;
|
||||||
|
$('#scheduler-tabs a:first').tab('show');
|
||||||
|
if (scheduler.isValid()) {
|
||||||
|
scope.schedulerIsValid = true;
|
||||||
|
Wait('start');
|
||||||
|
newSchedule = scheduler.getValue();
|
||||||
|
schedule.name = newSchedule.name;
|
||||||
|
schedule.rrule = newSchedule.rrule;
|
||||||
|
Rest.setUrl(url);
|
||||||
|
Rest.post(schedule)
|
||||||
|
.success(function(){
|
||||||
|
Wait('stop');
|
||||||
|
$('#scheduler-modal-dialog').dialog('close');
|
||||||
|
})
|
||||||
|
.error(function(){
|
||||||
|
Wait('stop');
|
||||||
|
$('#scheduler-modal-dialog').dialog('close');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
scope.schedulerIsValid = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$('#scheduler-tabs li a').on('shown.bs.tab', function(e) {
|
||||||
|
ShowDetails({ e: e, scope: scope, scheduler: scheduler });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}])
|
||||||
|
|
||||||
|
.factory('AddSchedule', ['SchedulerInit', 'ShowSchedulerModal', 'ShowDetails', 'Wait', 'Rest',
|
||||||
|
function(SchedulerInit, ShowSchedulerModal, ShowDetails, Wait, Rest) {
|
||||||
|
return function(params) {
|
||||||
|
var scope = params.scope,
|
||||||
|
url = params.url,
|
||||||
|
schedule = params.schedule,
|
||||||
|
scheduler;
|
||||||
|
|
||||||
|
Wait('start');
|
||||||
|
$('#form-container').empty();
|
||||||
|
scheduler = SchedulerInit({ scope: scope });
|
||||||
|
scheduler.inject('form-container', false);
|
||||||
|
ShowSchedulerModal({ scope: scope });
|
||||||
|
scope.showRRuleDetail = false;
|
||||||
|
|
||||||
|
setTimeout(function(){
|
||||||
|
$('#scheduler-modal-dialog').dialog('open');
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
scope.saveSchedule = function() {
|
||||||
|
var newSchedule;
|
||||||
|
$('#scheduler-tabs a:first').tab('show');
|
||||||
|
if (scheduler.isValid()) {
|
||||||
|
scope.schedulerIsValid = true;
|
||||||
|
Wait('start');
|
||||||
|
newSchedule = scheduler.getValue();
|
||||||
|
schedule.name = newSchedule.name;
|
||||||
|
schedule.rrule = newSchedule.rrule;
|
||||||
|
Rest.setUrl(url);
|
||||||
|
Rest.post(schedule)
|
||||||
|
.success(function(){
|
||||||
|
Wait('stop');
|
||||||
|
$('#scheduler-modal-dialog').dialog('close');
|
||||||
|
})
|
||||||
|
.error(function(){
|
||||||
|
Wait('stop');
|
||||||
|
$('#scheduler-modal-dialog').dialog('close');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
scope.schedulerIsValid = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$('#scheduler-tabs li a').on('shown.bs.tab', function(e) {
|
||||||
|
ShowDetails({ e: e, scope: scope, scheduler: scheduler });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}]);
|
||||||
@@ -23,8 +23,7 @@ angular.module('VariablesHelper', ['Utilities'])
|
|||||||
return function (variables) {
|
return function (variables) {
|
||||||
var result = "---", json_obj;
|
var result = "---", json_obj;
|
||||||
if (typeof variables === 'string') {
|
if (typeof variables === 'string') {
|
||||||
if ($.isEmptyObject(variables) || variables === "{}" || variables === "null" ||
|
if (variables === "{}" || variables === "null" || variables === "") {
|
||||||
variables === "" || variables === null) {
|
|
||||||
// String is empty, return ---
|
// String is empty, return ---
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
@@ -45,13 +44,18 @@ angular.module('VariablesHelper', ['Utilities'])
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// an object was passed in. just convert to yaml
|
if ($.isEmptyObject(variables) || variables === null) {
|
||||||
try {
|
// Empty object, return ---
|
||||||
result = jsyaml.safeDump(variables);
|
|
||||||
}
|
}
|
||||||
catch(e3) {
|
else {
|
||||||
ProcessErrors(null, variables, e3.message, null, { hdr: 'Error!',
|
// convert object to yaml
|
||||||
msg: 'Attempt to convert JSON object to YAML document failed: ' + e3.message });
|
try {
|
||||||
|
result = jsyaml.safeDump(variables);
|
||||||
|
}
|
||||||
|
catch(e3) {
|
||||||
|
ProcessErrors(null, variables, e3.message, null, { hdr: 'Error!',
|
||||||
|
msg: 'Attempt to convert JSON object to YAML document failed: ' + e3.message });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -65,7 +65,8 @@ angular.module('JobTemplatesListDefinition', [])
|
|||||||
schedule: {
|
schedule: {
|
||||||
label: 'Schedule',
|
label: 'Schedule',
|
||||||
mode: 'all',
|
mode: 'all',
|
||||||
awToolTip: 'Schedule a future job using this template',
|
ngHref: '#/job_templates/{{ job_template.id }}/schedules',
|
||||||
|
awToolTip: 'Schedule future job template runs',
|
||||||
dataPlacement: 'top'
|
dataPlacement: 'top'
|
||||||
},
|
},
|
||||||
"delete": {
|
"delete": {
|
||||||
|
|||||||
@@ -112,6 +112,13 @@ angular.module('ProjectsListDefinition', [])
|
|||||||
ngShow: "project.status == 'updating'",
|
ngShow: "project.status == 'updating'",
|
||||||
dataPlacement: 'top'
|
dataPlacement: 'top'
|
||||||
},
|
},
|
||||||
|
schedule: {
|
||||||
|
label: 'Schedule',
|
||||||
|
mode: 'all',
|
||||||
|
ngHref: '#/projects/{{ project.id }}/schedules',
|
||||||
|
awToolTip: 'Schedule future project sync runs',
|
||||||
|
dataPlacement: 'top'
|
||||||
|
},
|
||||||
"delete": {
|
"delete": {
|
||||||
label: 'Delete',
|
label: 'Delete',
|
||||||
ngClick: "deleteProject(project.id, project.name)",
|
ngClick: "deleteProject(project.id, project.name)",
|
||||||
|
|||||||
64
awx/ui/static/js/lists/Schedules.js
Normal file
64
awx/ui/static/js/lists/Schedules.js
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
/*********************************************
|
||||||
|
* Copyright (c) 2014 AnsibleWorks, Inc.
|
||||||
|
*
|
||||||
|
* Schedules.js
|
||||||
|
* List object for Schedule data model.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
angular.module('SchedulesListDefinition', [])
|
||||||
|
.value('SchedulesList', {
|
||||||
|
|
||||||
|
name: 'schedules',
|
||||||
|
iterator: 'schedule',
|
||||||
|
selectTitle: '',
|
||||||
|
editTitle: 'Schedules',
|
||||||
|
index: true,
|
||||||
|
hover: true,
|
||||||
|
|
||||||
|
fields: {
|
||||||
|
name: {
|
||||||
|
key: true,
|
||||||
|
label: 'Name'
|
||||||
|
},
|
||||||
|
dtstart: {
|
||||||
|
label: 'Start'
|
||||||
|
},
|
||||||
|
dtend: {
|
||||||
|
label: 'End'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
add: {
|
||||||
|
mode: 'all',
|
||||||
|
ngClick: 'addSchedule()',
|
||||||
|
awToolTip: 'Add a new schedule'
|
||||||
|
},
|
||||||
|
stream: {
|
||||||
|
ngClick: "showActivity()",
|
||||||
|
awToolTip: "View Activity Stream",
|
||||||
|
mode: 'edit'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
fieldActions: {
|
||||||
|
edit: {
|
||||||
|
label: 'Edit',
|
||||||
|
ngClick: "editSchedule(schedule.id)",
|
||||||
|
icon: 'icon-edit',
|
||||||
|
awToolTip: 'Edit schedule',
|
||||||
|
dataPlacement: 'top'
|
||||||
|
},
|
||||||
|
|
||||||
|
"delete": {
|
||||||
|
label: 'Delete',
|
||||||
|
ngClick: "deleteSchedule(schedule.id)",
|
||||||
|
icon: 'icon-trash',
|
||||||
|
awToolTip: 'Delete schedule',
|
||||||
|
dataPlacement: 'top'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
134
awx/ui/static/less/angular-scheduler.less
Normal file
134
awx/ui/static/less/angular-scheduler.less
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
/***************************************************************************
|
||||||
|
* Copyright (c) 2014 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* Styling for angular-scheduler
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#scheduler-modal-dialog {
|
||||||
|
display: none;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-top: 25px;
|
||||||
|
|
||||||
|
form {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sublabel {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#occurrence-label {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#date-choice {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 15px;
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
.label-inline {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
margin-bottom: 2px;
|
||||||
|
height: 11px;
|
||||||
|
width: 10px;
|
||||||
|
}
|
||||||
|
.label-inline:first-child {
|
||||||
|
padding-bottom: 2px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
.label-inline:nth-child(3) {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-widget input {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: normal;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.ui-spinner.ui-widget-content {
|
||||||
|
border-bottom-color: #ccc;
|
||||||
|
border-top-color: #ccc;
|
||||||
|
border-left-color: #ccc;
|
||||||
|
border-right-color: #ccc;
|
||||||
|
}
|
||||||
|
.ui-spinner-button {
|
||||||
|
border-left-color: #ccc;
|
||||||
|
border-left-style: solid;
|
||||||
|
border-left-width: 1px;
|
||||||
|
}
|
||||||
|
.scheduler-time-spinner {
|
||||||
|
width: 40px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
.scheduler-spinner {
|
||||||
|
width: 50px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
.fmt-help {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: normal;
|
||||||
|
color: #999;
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
color: #dd1b16;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-top: 0;
|
||||||
|
padding-top: 3px;
|
||||||
|
}
|
||||||
|
.red-text {
|
||||||
|
color: #dd1b16;
|
||||||
|
}
|
||||||
|
input.ng-dirty.ng-invalid, select.ng-dirty.ng-invalid, textarea.ng-dirty.ng-invalid {
|
||||||
|
border: 1px solid red;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.help-text {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: normal;
|
||||||
|
color: #999;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
.inline-label {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
#scheduler-buttons {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
.no-label {
|
||||||
|
padding-top: 25px;
|
||||||
|
}
|
||||||
|
.padding-top-slim {
|
||||||
|
padding-top: 5px;
|
||||||
|
}
|
||||||
|
.option-pad-left {
|
||||||
|
padding-left: 15px;
|
||||||
|
}
|
||||||
|
.option-pad-top {
|
||||||
|
padding-top: 15px;
|
||||||
|
}
|
||||||
|
.option-pad-bottom {
|
||||||
|
padding-bottom: 15px;
|
||||||
|
}
|
||||||
|
#monthlyOccurrence, #monthlyWeekDay {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.occurrence-list {
|
||||||
|
border: 1px solid @well-border;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: @well;
|
||||||
|
list-style: none;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,6 +31,7 @@
|
|||||||
@import "animations.less";
|
@import "animations.less";
|
||||||
@import "jquery-ui-overrides.less";
|
@import "jquery-ui-overrides.less";
|
||||||
@import "codemirror.less";
|
@import "codemirror.less";
|
||||||
|
@import "angular-scheduler.less";
|
||||||
|
|
||||||
|
|
||||||
html, body { height: 100%; }
|
html, body { height: 100%; }
|
||||||
@@ -527,6 +528,9 @@ dd {
|
|||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#group_form #group_tabs {
|
||||||
|
margin-top: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Outline required fields in Red when there is an error */
|
/* Outline required fields in Red when there is an error */
|
||||||
.form-control.ng-dirty.ng-invalid, .form-control.ng-dirty.ng-invalid:focus {
|
.form-control.ng-dirty.ng-invalid, .form-control.ng-dirty.ng-invalid:focus {
|
||||||
@@ -794,6 +798,16 @@ input[type="checkbox"].checkbox-no-label {
|
|||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.checkbox-group {
|
||||||
|
.radio-inline + .radio-inline,
|
||||||
|
.checkbox-inline + .checkbox-inline {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
.checkbox-inline, .radio-inline {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.checkbox-options {
|
.checkbox-options {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
padding-right: 20px;
|
padding-right: 20px;
|
||||||
|
|||||||
@@ -9,6 +9,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
table.ui-datepicker-calendar {
|
||||||
|
background-color: @well;
|
||||||
|
}
|
||||||
|
|
||||||
/* Modal dialog */
|
/* Modal dialog */
|
||||||
|
|
||||||
.ui-dialog-title {
|
.ui-dialog-title {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "angular-scheduler",
|
"name": "angular-scheduler",
|
||||||
|
"version": "0.0.2",
|
||||||
"authors": [
|
"authors": [
|
||||||
"Chris Houseknecht <chouse@ansible.com>"
|
"Chris Houseknecht <chouse@ansible.com>"
|
||||||
],
|
],
|
||||||
@@ -35,14 +36,13 @@
|
|||||||
"rrule",
|
"rrule",
|
||||||
"calendar"
|
"calendar"
|
||||||
],
|
],
|
||||||
"_release": "f2488ff1ec",
|
"_release": "0.0.2",
|
||||||
"_resolution": {
|
"_resolution": {
|
||||||
"type": "branch",
|
"type": "version",
|
||||||
"branch": "master",
|
"tag": "v0.0.2",
|
||||||
"commit": "f2488ff1ec1b2aa48206fa97111b6f8d5e88de89"
|
"commit": "f9df5d081112d0ebecfd418bd859c26f0c7711b4"
|
||||||
},
|
},
|
||||||
"_source": "git://github.com/chouseknecht/angular-scheduler.git",
|
"_source": "git://github.com/chouseknecht/angular-scheduler.git",
|
||||||
"_target": "*",
|
"_target": "*",
|
||||||
"_originalSource": "angular-scheduler",
|
"_originalSource": "angular-scheduler"
|
||||||
"_direct": true
|
|
||||||
}
|
}
|
||||||
@@ -60,6 +60,20 @@ textarea {
|
|||||||
resize: none;
|
resize: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-left {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.occurrence-list {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#rrule-description {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: normal;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
a,
|
a,
|
||||||
a:active,
|
a:active,
|
||||||
a:link,
|
a:link,
|
||||||
@@ -143,7 +157,7 @@ a:hover {
|
|||||||
color: #A9A9A9;
|
color: #A9A9A9;
|
||||||
}
|
}
|
||||||
.mono-space {
|
.mono-space {
|
||||||
font-family: Fixed, monospace;
|
font-family: "Courier New", Courier, monospace;
|
||||||
}
|
}
|
||||||
textarea.resizable {
|
textarea.resizable {
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
|
|||||||
@@ -51,6 +51,7 @@
|
|||||||
<!-- rrule -->
|
<!-- rrule -->
|
||||||
<script src="/bower_components/underscore/underscore.js"></script>
|
<script src="/bower_components/underscore/underscore.js"></script>
|
||||||
<script src="/bower_components/rrule/lib/rrule.js"></script>
|
<script src="/bower_components/rrule/lib/rrule.js"></script>
|
||||||
|
<script src="/bower_components/rrule/lib/nlp.js"></script>
|
||||||
|
|
||||||
<script src="/bower_components/angular/angular.min.js"></script>
|
<script src="/bower_components/angular/angular.min.js"></script>
|
||||||
<script src="/bower_components/angular-route/angular-route.min.js"></script>
|
<script src="/bower_components/angular-route/angular-route.min.js"></script>
|
||||||
|
|||||||
@@ -31,9 +31,6 @@ angular.module('sampleApp', ['ngRoute', 'AngularScheduler', 'Timezones'])
|
|||||||
|
|
||||||
scheduler.inject('form-container', true);
|
scheduler.inject('form-container', true);
|
||||||
|
|
||||||
console.log('User timezone: ');
|
|
||||||
console.log(scheduler.getUserTimezone());
|
|
||||||
|
|
||||||
$scope.setRRule = function() {
|
$scope.setRRule = function() {
|
||||||
$scope.inputRRuleMsg = '';
|
$scope.inputRRuleMsg = '';
|
||||||
$scope.inputRRuleMsg = scheduler.setRRule($scope.inputRRule);
|
$scope.inputRRuleMsg = scheduler.setRRule($scope.inputRRule);
|
||||||
@@ -44,19 +41,44 @@ angular.module('sampleApp', ['ngRoute', 'AngularScheduler', 'Timezones'])
|
|||||||
$scope.saveForm = function() {
|
$scope.saveForm = function() {
|
||||||
if (scheduler.isValid()) {
|
if (scheduler.isValid()) {
|
||||||
var schedule = scheduler.getValue(),
|
var schedule = scheduler.getValue(),
|
||||||
html =
|
rrule = scheduler.getRRule(),
|
||||||
"<form>\n" +
|
html,
|
||||||
"<div class=\"form-group\">\n" +
|
|
||||||
"<label>RRule</label>\n" +
|
|
||||||
"<textarea id=\"rrule-result\" readonly class=\"form-control\" rows=\"8\">" + schedule.rrule + "</textarea>\n" +
|
|
||||||
"</div>\n" +
|
|
||||||
"</form>\n",
|
|
||||||
wheight = $(window).height(),
|
wheight = $(window).height(),
|
||||||
wwidth = $(window).width(),
|
wwidth = $(window).width(),
|
||||||
w, h;
|
w, h, occurrences;
|
||||||
|
|
||||||
|
occurrences = [];
|
||||||
|
rrule.all(function(date, i) {
|
||||||
|
if (i < 10) {
|
||||||
|
occurrences.push(date);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
html = "<form>\n" +
|
||||||
|
"<div class=\"form-group\">\n" +
|
||||||
|
"<label>Description</label>\n" +
|
||||||
|
"<textarea id=\"rrule-description\" readonly class=\"form-control\" rows=\"2\">Run " + rrule.toText() + "</textarea>\n" +
|
||||||
|
"</div>" +
|
||||||
|
"<div class=\"form-group\">\n" +
|
||||||
|
"<label>RRule</label>\n" +
|
||||||
|
"<textarea id=\"rrule-result\" readonly class=\"form-control\" rows=\"3\">" + schedule.rrule + "</textarea>\n" +
|
||||||
|
"</div>\n" +
|
||||||
|
"<div class=\"form-group\">\n" +
|
||||||
|
"<label>Occurrences (up to 10)</label>\n" +
|
||||||
|
"<ul class=\"occurrence-list mono-space\">\n";
|
||||||
|
occurrences.forEach(function(itm){
|
||||||
|
html += "<li>" + itm + "</li>\n";
|
||||||
|
});
|
||||||
|
html += "</ul>\n" +
|
||||||
|
"</div>\n" +
|
||||||
|
"</form>\n";
|
||||||
|
|
||||||
w = (600 > wwidth) ? wwidth : 600;
|
w = (600 > wwidth) ? wwidth : 600;
|
||||||
h = (400 > wheight) ? wheight : 400;
|
h = (600 > wheight) ? wheight : 600;
|
||||||
|
|
||||||
$('#message').html(html)
|
$('#message').html(html)
|
||||||
.dialog({
|
.dialog({
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "angular-scheduler",
|
"name": "angular-scheduler",
|
||||||
"version": "0.0.1",
|
"version": "0.0.2",
|
||||||
"authors": [
|
"authors": [
|
||||||
"Chris Houseknecht <chouse@ansible.com>"
|
"Chris Houseknecht <chouse@ansible.com>"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -47,9 +47,10 @@
|
|||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
padding-top: 3px;
|
padding-top: 3px;
|
||||||
}
|
}
|
||||||
.pull-up {
|
.error-pull-up {
|
||||||
margin-top: -15px;
|
position: relative;
|
||||||
margin-bottom: 10px;
|
top: -15px;
|
||||||
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
.red-text {
|
.red-text {
|
||||||
color: #dd1b16;
|
color: #dd1b16;
|
||||||
|
|||||||
@@ -15,14 +15,11 @@
|
|||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
|
|
||||||
<form class="form" role="form" name="scheduler_form" novalidate>
|
<form class="form" role="form" name="scheduler_form" novalidate>
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-5">
|
<div class="form-group">
|
||||||
<div class="form-group">
|
<label><span class="red-text">*</span> Name</label>
|
||||||
<label><span class="red-text">*</span> Name</label>
|
<input type="text" class="form-control input-sm" name="schedulerName" id="schedulerName" ng-model="schedulerName" required placeholder="Schedule name">
|
||||||
<input type="text" class="form-control input-sm" name="schedulerName" id="schedulerName" ng-model="schedulerName" required placeholder="Schedule name">
|
<div class="error" ng-show="scheduler_form.schedulerName.$dirty && scheduler_form.schedulerName.$error.required">Schedule name is required</div>
|
||||||
<div class="error" ng-show="scheduler_form.schedulerName.$dirty && scheduler_form.schedulerName.$error.required">Schedule name is required</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -54,9 +51,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row error-pull-up">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<div class="error pull-up" ng-show="scheduler_form.schedulerStartDt.$dirty && scheduler_form_schedulerStartDt_error" ng-bind="scheduler_form_schedulerStartDt_error"></div>
|
<div class="error" ng-show="scheduler_form_schedulerStartDt_error" ng-bind="scheduler_form_schedulerStartDt_error"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -79,7 +76,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Repeat</label>
|
<label>Repeat frequency</label>
|
||||||
<select name="schedulerFrequency" id="schedulerFrequency" ng-model="schedulerFrequency"
|
<select name="schedulerFrequency" id="schedulerFrequency" ng-model="schedulerFrequency"
|
||||||
ng-options="f.name for f in frequencyOptions" required class="form-control input-sm"
|
ng-options="f.name for f in frequencyOptions" required class="form-control input-sm"
|
||||||
ng-change="scheduleRepeatChange()"></select>
|
ng-change="scheduleRepeatChange()"></select>
|
||||||
|
|||||||
@@ -290,14 +290,14 @@ angular.module('AngularScheduler', ['underscore'])
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
scope.startDateError("Provide a valid start date and time");
|
this.scope.startDateError("Provide a valid start date and time");
|
||||||
validity = false;
|
validity = false;
|
||||||
}
|
}
|
||||||
return validity;
|
return validity;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns an rrule object
|
// Returns an rrule object
|
||||||
this.getRule = function() {
|
this.getRRule = function() {
|
||||||
var options = this.getOptions();
|
var options = this.getOptions();
|
||||||
return GetRule(options);
|
return GetRule(options);
|
||||||
};
|
};
|
||||||
@@ -305,7 +305,7 @@ angular.module('AngularScheduler', ['underscore'])
|
|||||||
// Return object containing schedule name, string representation of rrule per iCalendar RFC,
|
// Return object containing schedule name, string representation of rrule per iCalendar RFC,
|
||||||
// and options used to create rrule
|
// and options used to create rrule
|
||||||
this.getValue = function() {
|
this.getValue = function() {
|
||||||
var rule = this.getRule(),
|
var rule = this.getRRule(),
|
||||||
options = this.getOptions();
|
options = this.getOptions();
|
||||||
return {
|
return {
|
||||||
name: scope.schedulerName,
|
name: scope.schedulerName,
|
||||||
@@ -319,6 +319,10 @@ angular.module('AngularScheduler', ['underscore'])
|
|||||||
return SetRule(rule, this.scope);
|
return SetRule(rule, this.scope);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.setName = function(name) {
|
||||||
|
this.scope.schedulerName = name;
|
||||||
|
};
|
||||||
|
|
||||||
// Read in the HTML partial, compile and inject it into the DOM.
|
// Read in the HTML partial, compile and inject it into the DOM.
|
||||||
// Pass in the target element's id attribute value or an angular.element()
|
// Pass in the target element's id attribute value or an angular.element()
|
||||||
// object.
|
// object.
|
||||||
@@ -336,13 +340,13 @@ angular.module('AngularScheduler', ['underscore'])
|
|||||||
// Get the user's local timezone
|
// Get the user's local timezone
|
||||||
this.getUserTimezone = function() {
|
this.getUserTimezone = function() {
|
||||||
return $timezones.getLocal();
|
return $timezones.getLocal();
|
||||||
}
|
};
|
||||||
};
|
};
|
||||||
return new fn();
|
return new fn();
|
||||||
};
|
};
|
||||||
}])
|
}])
|
||||||
|
|
||||||
.factory('Inject', ['AngularScheduler.partial', '$compile', '$http', '$log', function(scheduler_partial, $compile, $http, $log) {
|
.factory('Inject', ['AngularScheduler.partial', '$compile', '$http', '$log', function(scheduler_partial, $compile, $http) {
|
||||||
return function(params) {
|
return function(params) {
|
||||||
|
|
||||||
var scope = params.scope,
|
var scope = params.scope,
|
||||||
@@ -741,12 +745,12 @@ angular.module('AngularScheduler', ['underscore'])
|
|||||||
|
|
||||||
scope.frequencyOptions = [
|
scope.frequencyOptions = [
|
||||||
{ name: 'None (run once)', value: 'none', intervalLabel: '' },
|
{ name: 'None (run once)', value: 'none', intervalLabel: '' },
|
||||||
{ name: 'Minutely', value: 'minutely', intervalLabel: 'minutes' },
|
{ name: 'Minute', value: 'minutely', intervalLabel: 'minutes' },
|
||||||
{ name: 'Hourly', value: 'hourly', intervalLabel: 'hours' },
|
{ name: 'Hour', value: 'hourly', intervalLabel: 'hours' },
|
||||||
{ name: 'Daily', value: 'daily', intervalLabel: 'days' },
|
{ name: 'Day', value: 'daily', intervalLabel: 'days' },
|
||||||
{ name: 'Weekly', value: 'weekly', intervalLabel: 'weeks' },
|
{ name: 'Week', value: 'weekly', intervalLabel: 'weeks' },
|
||||||
{ name: 'Monthly', value: 'monthly', intervalLabel: 'months' },
|
{ name: 'Month', value: 'monthly', intervalLabel: 'months' },
|
||||||
{ name: 'Yearly', value: 'yearly', intervalLabel: 'years' }
|
{ name: 'Year', value: 'yearly', intervalLabel: 'years' }
|
||||||
];
|
];
|
||||||
|
|
||||||
scope.endOptions = [
|
scope.endOptions = [
|
||||||
@@ -830,6 +834,11 @@ angular.module('AngularScheduler', ['underscore'])
|
|||||||
options.maxDate = (attrs.maxDate) ? new Date(attrs('maxDate')) : null;
|
options.maxDate = (attrs.maxDate) ? new Date(attrs('maxDate')) : null;
|
||||||
options.changeMonth = (attrs.changeMonth === "false") ? false : true;
|
options.changeMonth = (attrs.changeMonth === "false") ? false : true;
|
||||||
options.changeYear = (attrs.changeYear === "false") ? false : true;
|
options.changeYear = (attrs.changeYear === "false") ? false : true;
|
||||||
|
options.beforeShow = function() {
|
||||||
|
setTimeout(function(){
|
||||||
|
$('.ui-datepicker').css('z-index', 9999);
|
||||||
|
}, 100);
|
||||||
|
};
|
||||||
$(element).datepicker(options);
|
$(element).datepicker(options);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
.ui-widget input{font-size:12px;font-weight:400;text-align:center}.ui-spinner.ui-widget-content{border-bottom-color:#ccc;border-top-color:#ccc;border-left-color:#ccc;border-right-color:#ccc}.ui-spinner-button{border-left-color:#ccc;border-left-style:solid;border-left-width:1px}.scheduler-time-spinner{width:40px;height:24px}.scheduler-spinner{width:50px;height:24px}.fmt-help{font-size:12px;font-weight:400;color:#999;padding-left:10px}.error{color:#dd1b16;font-size:12px;margin-bottom:0;margin-top:0;padding-top:3px}.pull-up{margin-top:-15px;margin-bottom:10px}.red-text{color:#dd1b16}input.ng-dirty.ng-invalid,select.ng-dirty.ng-invalid,textarea.ng-dirty.ng-invalid{border:1px solid red;outline:0}.help-text{font-size:12px;font-weight:400;color:#999;margin-top:5px}.inline-label{margin-left:10px}#scheduler-buttons{margin-top:20px}.no-label{padding-top:25px}.padding-top-slim{padding-top:5px}.option-pad-left{padding-left:15px}.option-pad-top{padding-top:15px}.option-pad-bottom{padding-bottom:15px}#monthlyOccurrence,#monthlyWeekDay{margin-top:5px}select{width:100%}
|
.ui-widget input{font-size:12px;font-weight:400;text-align:center}.ui-spinner.ui-widget-content{border-bottom-color:#ccc;border-top-color:#ccc;border-left-color:#ccc;border-right-color:#ccc}.ui-spinner-button{border-left-color:#ccc;border-left-style:solid;border-left-width:1px}.scheduler-time-spinner{width:40px;height:24px}.scheduler-spinner{width:50px;height:24px}.fmt-help{font-size:12px;font-weight:400;color:#999;padding-left:10px}.error{color:#dd1b16;font-size:12px;margin-bottom:0;margin-top:0;padding-top:3px}.error-pull-up{position:relative;top:-15px;margin-bottom:15px}.red-text{color:#dd1b16}input.ng-dirty.ng-invalid,select.ng-dirty.ng-invalid,textarea.ng-dirty.ng-invalid{border:1px solid red;outline:0}.help-text{font-size:12px;font-weight:400;color:#999;margin-top:5px}.inline-label{margin-left:10px}#scheduler-buttons{margin-top:20px}.no-label{padding-top:25px}.padding-top-slim{padding-top:5px}.option-pad-left{padding-left:15px}.option-pad-top{padding-top:15px}.option-pad-bottom{padding-bottom:15px}#monthlyOccurrence,#monthlyWeekDay{margin-top:5px}select{width:100%}
|
||||||
File diff suppressed because one or more lines are too long
@@ -1162,8 +1162,7 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies', 'Utilities'])
|
|||||||
html += " class=\"active\"";
|
html += " class=\"active\"";
|
||||||
}
|
}
|
||||||
html += "><a id=\"" + tab.name + "_link\" ng-click=\"toggleTab($event, '" + tab.name + "_link', '" +
|
html += "><a id=\"" + tab.name + "_link\" ng-click=\"toggleTab($event, '" + tab.name + "_link', '" +
|
||||||
this.form.name + "_tabs')\" href=\"#" + tab.name + "\"" +
|
this.form.name + "_tabs')\" href=\"#" + tab.name + "\" data-toggle=\"tab\">" + tab.label + "</a></li>\n";
|
||||||
tab.name + "\" data-toggle=\"tab\">" + tab.label + "</a></li>\n";
|
|
||||||
}
|
}
|
||||||
html += "</ul>\n";
|
html += "</ul>\n";
|
||||||
html += "<div class=\"tab-content\">\n";
|
html += "<div class=\"tab-content\">\n";
|
||||||
@@ -1396,7 +1395,7 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies', 'Utilities'])
|
|||||||
}
|
}
|
||||||
html += "\"></i></a></th>\n";
|
html += "\"></i></a></th>\n";
|
||||||
}
|
}
|
||||||
html += "<th></th>\n";
|
html += "<th>Actions</th>\n";
|
||||||
html += "</tr>\n";
|
html += "</tr>\n";
|
||||||
html += "</thead>";
|
html += "</thead>";
|
||||||
html += "<tbody>\n";
|
html += "<tbody>\n";
|
||||||
|
|||||||
52
awx/ui/static/partials/schedule_detail.html
Normal file
52
awx/ui/static/partials/schedule_detail.html
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-12" id="breadcrumbs"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div id="schedule-list-target"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="scheduler-modal-dialog" title="Edit Schedule">
|
||||||
|
<ul id="scheduler-tabs" class="nav nav-tabs">
|
||||||
|
<li class="active"><a href="#schedule" id="schedule-link" data-toggle="tab" ng-click="toggleTab($event, 'schedule-link', 'scheduler-tabs')">Options</a></li>
|
||||||
|
<li><a href="#occurrences" id="occurrence-link" data-toggle="tab" ng-click="toggleTab($event, 'occurrence-link', 'scheduler-tabs')">Details</a></li>
|
||||||
|
</ul>
|
||||||
|
<div class="tab-content">
|
||||||
|
<div class="tab-pane active" id="schedule">
|
||||||
|
<div id="form-container"></div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane" id="occurrences">
|
||||||
|
<div class="alert alert-danger" ng-show="!schedulerIsValid">
|
||||||
|
<p>The scheduler options are invalid or incomplete. Make the needed changes on the options tab, then come back here to see details.</p>
|
||||||
|
</div>
|
||||||
|
<div ng-show="schedulerIsValid">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Description</label>
|
||||||
|
<textarea ng-model="rrule_nlp_description" name="rrule_nlp_description" id="rrule_nlp_description" readonly class="form-control" rows="2"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" ng-show="showRRuleDetail">
|
||||||
|
<label>RRule</label>
|
||||||
|
<textarea ng-model="rrule" name="rrule" id="rrule" readonly class="form-control" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label id="occurrences-label">Occurrences <span class="sublabel">(limited to first 10)</label>
|
||||||
|
<div id="date-choice">
|
||||||
|
<div class="label-inline"><strong>Date format</strong></div>
|
||||||
|
<input type="radio" ng-model="dateChoice" id="date-choice-utc" value="utc" >
|
||||||
|
<div class="label-inline"> UTC</div>
|
||||||
|
<input type="radio" ng-model="dateChoice" id="date-choice-local" value="local" >
|
||||||
|
<div class="label-inline"> Local time</div>
|
||||||
|
</div>
|
||||||
|
<ul class="occurrence-list mono-space" ng-show="dateChoice == 'utc'">
|
||||||
|
<li ng-repeat="occurrence in occurrence_list">{{ occurrence.utc }}</li>
|
||||||
|
</ul>
|
||||||
|
<ul class="occurrence-list mono-space" ng-show="dateChoice == 'local'">
|
||||||
|
<li ng-repeat="occurrence in occurrence_list">{{ occurrence.local }}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
40
awx/ui/static/sample/data/schedules/data.json
Normal file
40
awx/ui/static/sample/data/schedules/data.json
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"count": 3,
|
||||||
|
"next": null,
|
||||||
|
"previous": null,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"job_template": 3,
|
||||||
|
"inventory": null,
|
||||||
|
"project": null,
|
||||||
|
"job_class": "ansible:playbook",
|
||||||
|
"name": "Hourly",
|
||||||
|
"dtstart": "2014-03-10T17:00:00.000Z" ,
|
||||||
|
"dtend": null,
|
||||||
|
"rrule": "FREQ=HOURLY;DTSTART=20140310T170000Z;INTERVAL=1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"job_template": 3,
|
||||||
|
"inventory": null,
|
||||||
|
"project": null,
|
||||||
|
"job_class": "ansible:playbook",
|
||||||
|
"name": "Weekly",
|
||||||
|
"dtstart": "2014-03-17T13:00:00.000Z",
|
||||||
|
"dtend": null,
|
||||||
|
"rrule": "FREQ=WEEKLY;DTSTART=20140317T130000Z;INTERVAL=1;COUNT=10;BYDAY=MO"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"job_template": 3,
|
||||||
|
"inventory": null,
|
||||||
|
"project": null,
|
||||||
|
"job_class": "ansible:playbook",
|
||||||
|
"name": "Monthly",
|
||||||
|
"dtstart": "2014-04-06T01:00:00.000Z",
|
||||||
|
"dtend": "2020-03-01T01:00:00.000Z",
|
||||||
|
"rrule": "FREQ=MONTHLY;DTSTART=20140406T010000Z;INTERVAL=1;UNTIL=20200301T010000Z;BYMONTHDAY=1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
43
awx/ui/static/sample/data/schedules/projects/data.json
Normal file
43
awx/ui/static/sample/data/schedules/projects/data.json
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"count": 3,
|
||||||
|
"next": null,
|
||||||
|
"previous": null,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"job_template": null,
|
||||||
|
"inventory": null,
|
||||||
|
"project": 1,
|
||||||
|
"job_class": "project:sync",
|
||||||
|
"job_type": "project_sync",
|
||||||
|
"name": "Hourly",
|
||||||
|
"dtstart": "2014-03-10T17:00:00.000Z" ,
|
||||||
|
"dtend": null,
|
||||||
|
"rrule": "FREQ=HOURLY;DTSTART=20140310T170000Z;INTERVAL=1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"job_template": null,
|
||||||
|
"inventory": null,
|
||||||
|
"project": 1,
|
||||||
|
"job_class": "project:sync",
|
||||||
|
"job_type": "project_sync",
|
||||||
|
"name": "Weekly",
|
||||||
|
"dtstart": "2014-03-17T13:00:00.000Z",
|
||||||
|
"dtend": null,
|
||||||
|
"rrule": "FREQ=WEEKLY;DTSTART=20140317T130000Z;INTERVAL=1;COUNT=10;BYDAY=MO"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"job_template": null,
|
||||||
|
"inventory": null,
|
||||||
|
"project": 1,
|
||||||
|
"job_class": "project:sync",
|
||||||
|
"job_type": "project_sync",
|
||||||
|
"name": "Monthly",
|
||||||
|
"dtstart": "2014-04-06T01:00:00.000Z",
|
||||||
|
"dtend": "2020-03-01T01:00:00.000Z",
|
||||||
|
"rrule": "FREQ=MONTHLY;DTSTART=20140406T010000Z;INTERVAL=1;UNTIL=20200301T010000Z;BYMONTHDAY=1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
244
awx/ui/static/scripts/web-server.js
Executable file
244
awx/ui/static/scripts/web-server.js
Executable file
@@ -0,0 +1,244 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
var util = require('util'),
|
||||||
|
http = require('http'),
|
||||||
|
fs = require('fs'),
|
||||||
|
url = require('url'),
|
||||||
|
events = require('events');
|
||||||
|
|
||||||
|
var DEFAULT_PORT = 8000;
|
||||||
|
|
||||||
|
function main(argv) {
|
||||||
|
new HttpServer({
|
||||||
|
'GET': createServlet(StaticServlet),
|
||||||
|
'HEAD': createServlet(StaticServlet)
|
||||||
|
}).start(Number(argv[2]) || DEFAULT_PORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHtml(value) {
|
||||||
|
return value.toString().
|
||||||
|
replace('<', '<').
|
||||||
|
replace('>', '>').
|
||||||
|
replace('"', '"');
|
||||||
|
}
|
||||||
|
|
||||||
|
function createServlet(Class) {
|
||||||
|
var servlet = new Class();
|
||||||
|
return servlet.handleRequest.bind(servlet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An Http server implementation that uses a map of methods to decide
|
||||||
|
* action routing.
|
||||||
|
*
|
||||||
|
* @param {Object} Map of method => Handler function
|
||||||
|
*/
|
||||||
|
function HttpServer(handlers) {
|
||||||
|
this.handlers = handlers;
|
||||||
|
this.server = http.createServer(this.handleRequest_.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpServer.prototype.start = function(port) {
|
||||||
|
this.port = port;
|
||||||
|
this.server.listen(port);
|
||||||
|
util.puts('Http Server running at http://localhost:' + port + '/');
|
||||||
|
};
|
||||||
|
|
||||||
|
HttpServer.prototype.parseUrl_ = function(urlString) {
|
||||||
|
var parsed = url.parse(urlString);
|
||||||
|
parsed.pathname = url.resolve('/', parsed.pathname);
|
||||||
|
return url.parse(url.format(parsed), true);
|
||||||
|
};
|
||||||
|
|
||||||
|
HttpServer.prototype.handleRequest_ = function(req, res) {
|
||||||
|
var logEntry = req.method + ' ' + req.url;
|
||||||
|
if (req.headers['user-agent']) {
|
||||||
|
logEntry += ' ' + req.headers['user-agent'];
|
||||||
|
}
|
||||||
|
util.puts(logEntry);
|
||||||
|
req.url = this.parseUrl_(req.url);
|
||||||
|
var handler = this.handlers[req.method];
|
||||||
|
if (!handler) {
|
||||||
|
res.writeHead(501);
|
||||||
|
res.end();
|
||||||
|
} else {
|
||||||
|
handler.call(this, req, res);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles static content.
|
||||||
|
*/
|
||||||
|
function StaticServlet() {}
|
||||||
|
|
||||||
|
StaticServlet.MimeMap = {
|
||||||
|
'txt': 'text/plain',
|
||||||
|
'html': 'text/html',
|
||||||
|
'css': 'text/css',
|
||||||
|
'xml': 'application/xml',
|
||||||
|
'json': 'application/json',
|
||||||
|
'js': 'application/javascript',
|
||||||
|
'jpg': 'image/jpeg',
|
||||||
|
'jpeg': 'image/jpeg',
|
||||||
|
'gif': 'image/gif',
|
||||||
|
'png': 'image/png',
|
||||||
|
'svg': 'image/svg+xml'
|
||||||
|
};
|
||||||
|
|
||||||
|
StaticServlet.prototype.handleRequest = function(req, res) {
|
||||||
|
var self = this;
|
||||||
|
var path = ('./' + req.url.pathname).replace('//','/').replace(/%(..)/g, function(match, hex){
|
||||||
|
return String.fromCharCode(parseInt(hex, 16));
|
||||||
|
});
|
||||||
|
var parts = path.split('/');
|
||||||
|
if (parts[parts.length-1].charAt(0) === '.')
|
||||||
|
return self.sendForbidden_(req, res, path);
|
||||||
|
fs.stat(path, function(err, stat) {
|
||||||
|
if (err)
|
||||||
|
return self.sendMissing_(req, res, path);
|
||||||
|
if (stat.isDirectory())
|
||||||
|
return self.sendDirectory_(req, res, path);
|
||||||
|
return self.sendFile_(req, res, path);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
StaticServlet.prototype.sendError_ = function(req, res, error) {
|
||||||
|
res.writeHead(500, {
|
||||||
|
'Content-Type': 'text/html'
|
||||||
|
});
|
||||||
|
res.write('<!doctype html>\n');
|
||||||
|
res.write('<title>Internal Server Error</title>\n');
|
||||||
|
res.write('<h1>Internal Server Error</h1>');
|
||||||
|
res.write('<pre>' + escapeHtml(util.inspect(error)) + '</pre>');
|
||||||
|
util.puts('500 Internal Server Error');
|
||||||
|
util.puts(util.inspect(error));
|
||||||
|
};
|
||||||
|
|
||||||
|
StaticServlet.prototype.sendMissing_ = function(req, res, path) {
|
||||||
|
path = path.substring(1);
|
||||||
|
res.writeHead(404, {
|
||||||
|
'Content-Type': 'text/html'
|
||||||
|
});
|
||||||
|
res.write('<!doctype html>\n');
|
||||||
|
res.write('<title>404 Not Found</title>\n');
|
||||||
|
res.write('<h1>Not Found</h1>');
|
||||||
|
res.write(
|
||||||
|
'<p>The requested URL ' +
|
||||||
|
escapeHtml(path) +
|
||||||
|
' was not found on this server.</p>'
|
||||||
|
);
|
||||||
|
res.end();
|
||||||
|
util.puts('404 Not Found: ' + path);
|
||||||
|
};
|
||||||
|
|
||||||
|
StaticServlet.prototype.sendForbidden_ = function(req, res, path) {
|
||||||
|
path = path.substring(1);
|
||||||
|
res.writeHead(403, {
|
||||||
|
'Content-Type': 'text/html'
|
||||||
|
});
|
||||||
|
res.write('<!doctype html>\n');
|
||||||
|
res.write('<title>403 Forbidden</title>\n');
|
||||||
|
res.write('<h1>Forbidden</h1>');
|
||||||
|
res.write(
|
||||||
|
'<p>You do not have permission to access ' +
|
||||||
|
escapeHtml(path) + ' on this server.</p>'
|
||||||
|
);
|
||||||
|
res.end();
|
||||||
|
util.puts('403 Forbidden: ' + path);
|
||||||
|
};
|
||||||
|
|
||||||
|
StaticServlet.prototype.sendRedirect_ = function(req, res, redirectUrl) {
|
||||||
|
res.writeHead(301, {
|
||||||
|
'Content-Type': 'text/html',
|
||||||
|
'Location': redirectUrl
|
||||||
|
});
|
||||||
|
res.write('<!doctype html>\n');
|
||||||
|
res.write('<title>301 Moved Permanently</title>\n');
|
||||||
|
res.write('<h1>Moved Permanently</h1>');
|
||||||
|
res.write(
|
||||||
|
'<p>The document has moved <a href="' +
|
||||||
|
redirectUrl +
|
||||||
|
'">here</a>.</p>'
|
||||||
|
);
|
||||||
|
res.end();
|
||||||
|
util.puts('301 Moved Permanently: ' + redirectUrl);
|
||||||
|
};
|
||||||
|
|
||||||
|
StaticServlet.prototype.sendFile_ = function(req, res, path) {
|
||||||
|
var self = this;
|
||||||
|
var file = fs.createReadStream(path);
|
||||||
|
res.writeHead(200, {
|
||||||
|
'Content-Type': StaticServlet.
|
||||||
|
MimeMap[path.split('.').pop()] || 'text/plain'
|
||||||
|
});
|
||||||
|
if (req.method === 'HEAD') {
|
||||||
|
res.end();
|
||||||
|
} else {
|
||||||
|
file.on('data', res.write.bind(res));
|
||||||
|
file.on('close', function() {
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
file.on('error', function(error) {
|
||||||
|
self.sendError_(req, res, error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
StaticServlet.prototype.sendDirectory_ = function(req, res, path) {
|
||||||
|
var self = this;
|
||||||
|
if (path.match(/[^\/]$/)) {
|
||||||
|
req.url.pathname += '/';
|
||||||
|
var redirectUrl = url.format(url.parse(url.format(req.url)));
|
||||||
|
return self.sendRedirect_(req, res, redirectUrl);
|
||||||
|
}
|
||||||
|
fs.readdir(path, function(err, files) {
|
||||||
|
if (err)
|
||||||
|
return self.sendError_(req, res, error);
|
||||||
|
|
||||||
|
if (!files.length)
|
||||||
|
return self.writeDirectoryIndex_(req, res, path, []);
|
||||||
|
|
||||||
|
var remaining = files.length;
|
||||||
|
files.forEach(function(fileName, index) {
|
||||||
|
fs.stat(path + '/' + fileName, function(err, stat) {
|
||||||
|
if (err)
|
||||||
|
return self.sendError_(req, res, err);
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
files[index] = fileName + '/';
|
||||||
|
}
|
||||||
|
if (!(--remaining))
|
||||||
|
return self.writeDirectoryIndex_(req, res, path, files);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
StaticServlet.prototype.writeDirectoryIndex_ = function(req, res, path, files) {
|
||||||
|
path = path.substring(1);
|
||||||
|
res.writeHead(200, {
|
||||||
|
'Content-Type': 'text/html'
|
||||||
|
});
|
||||||
|
if (req.method === 'HEAD') {
|
||||||
|
res.end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.write('<!doctype html>\n');
|
||||||
|
res.write('<title>' + escapeHtml(path) + '</title>\n');
|
||||||
|
res.write('<style>\n');
|
||||||
|
res.write(' ol { list-style-type: none; font-size: 1.2em; }\n');
|
||||||
|
res.write('</style>\n');
|
||||||
|
res.write('<h1>Directory: ' + escapeHtml(path) + '</h1>');
|
||||||
|
res.write('<ol>');
|
||||||
|
files.forEach(function(fileName) {
|
||||||
|
if (fileName.charAt(0) !== '.') {
|
||||||
|
res.write('<li><a href="' +
|
||||||
|
escapeHtml(fileName) + '">' +
|
||||||
|
escapeHtml(fileName) + '</a></li>');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
res.write('</ol>');
|
||||||
|
res.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Must be last,
|
||||||
|
main(process.argv);
|
||||||
@@ -33,6 +33,16 @@
|
|||||||
<script src="{{ STATIC_URL }}lib/angular-sanitize/angular-sanitize.min.js"></script>
|
<script src="{{ STATIC_URL }}lib/angular-sanitize/angular-sanitize.min.js"></script>
|
||||||
<script src="{{ STATIC_URL }}lib/angular-md5/angular-md5.min.js"></script>
|
<script src="{{ STATIC_URL }}lib/angular-md5/angular-md5.min.js"></script>
|
||||||
<script src="{{ STATIC_URL }}lib/angular-codemirror/lib/AngularCodeMirror.js"></script>
|
<script src="{{ STATIC_URL }}lib/angular-codemirror/lib/AngularCodeMirror.js"></script>
|
||||||
|
|
||||||
|
<!-- scheduler pieces -->
|
||||||
|
<script src="{{ STATIC_URL }}lib/timezone-js/src/date.js"></script>
|
||||||
|
<script src="{{ STATIC_URL }}lib/angular-tz-extensions/packages/jstimezonedetect/jstz.min.js"></script>
|
||||||
|
<script src="{{ STATIC_URL }}lib/underscore/underscore.js"></script>
|
||||||
|
<script src="{{ STATIC_URL }}lib/rrule/lib/rrule.js"></script>
|
||||||
|
<script src="{{ STATIC_URL }}lib/rrule/lib/nlp.js"></script>
|
||||||
|
<script src="{{ STATIC_URL }}lib/angular-tz-extensions/lib/angular-tz-extensions.js"></script>
|
||||||
|
<script src="{{ STATIC_URL }}lib/angular-scheduler/lib/angular-scheduler.js"></script>
|
||||||
|
|
||||||
{% if settings.USE_MINIFIED_JS %}
|
{% if settings.USE_MINIFIED_JS %}
|
||||||
<script src="{{ STATIC_URL }}js/awx.min.js"></script>
|
<script src="{{ STATIC_URL }}js/awx.min.js"></script>
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -66,6 +76,7 @@
|
|||||||
<script src="{{ STATIC_URL }}js/controllers/JobEvents.js"></script>
|
<script src="{{ STATIC_URL }}js/controllers/JobEvents.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/controllers/JobHosts.js"></script>
|
<script src="{{ STATIC_URL }}js/controllers/JobHosts.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/controllers/Permissions.js"></script>
|
<script src="{{ STATIC_URL }}js/controllers/Permissions.js"></script>
|
||||||
|
<script src="{{ STATIC_URL }}js/controllers/Schedules.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/forms/Users.js"></script>
|
<script src="{{ STATIC_URL }}js/forms/Users.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/forms/Organizations.js"></script>
|
<script src="{{ STATIC_URL }}js/forms/Organizations.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/forms/Inventories.js"></script>
|
<script src="{{ STATIC_URL }}js/forms/Inventories.js"></script>
|
||||||
@@ -104,6 +115,7 @@
|
|||||||
<script src="{{ STATIC_URL }}js/lists/HomeHosts.js"></script>
|
<script src="{{ STATIC_URL }}js/lists/HomeHosts.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/lists/Groups.js"></script>
|
<script src="{{ STATIC_URL }}js/lists/Groups.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/lists/Hosts.js"></script>
|
<script src="{{ STATIC_URL }}js/lists/Hosts.js"></script>
|
||||||
|
<script src="{{ STATIC_URL }}js/lists/Schedules.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/helpers/refresh-related.js"></script>
|
<script src="{{ STATIC_URL }}js/helpers/refresh-related.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/helpers/related-search.js"></script>
|
<script src="{{ STATIC_URL }}js/helpers/related-search.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/helpers/refresh.js"></script>
|
<script src="{{ STATIC_URL }}js/helpers/refresh.js"></script>
|
||||||
@@ -128,6 +140,7 @@
|
|||||||
<script src="{{ STATIC_URL }}js/helpers/Groups.js"></script>
|
<script src="{{ STATIC_URL }}js/helpers/Groups.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/helpers/Hosts.js"></script>
|
<script src="{{ STATIC_URL }}js/helpers/Hosts.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/helpers/Variables.js"></script>
|
<script src="{{ STATIC_URL }}js/helpers/Variables.js"></script>
|
||||||
|
<script src="{{ STATIC_URL }}js/helpers/Schedules.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/widgets/JobStatus.js"></script>
|
<script src="{{ STATIC_URL }}js/widgets/JobStatus.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/widgets/InventorySyncStatus.js"></script>
|
<script src="{{ STATIC_URL }}js/widgets/InventorySyncStatus.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/widgets/SCMSyncStatus.js"></script>
|
<script src="{{ STATIC_URL }}js/widgets/SCMSyncStatus.js"></script>
|
||||||
|
|||||||
Reference in New Issue
Block a user