mirror of
https://github.com/ansible/awx.git
synced 2026-03-13 23:17:32 -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',
|
||||
'HomeHostListDefinition',
|
||||
'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',
|
||||
function ($routeProvider) {
|
||||
$routeProvider.
|
||||
@@ -131,6 +141,11 @@ angular.module('ansible', [
|
||||
controller: 'JobTemplatesEdit'
|
||||
}).
|
||||
|
||||
when('/job_templates/:id/schedules', {
|
||||
templateUrl: urlPrefix + 'partials/schedule_detail.html',
|
||||
controller: 'ScheduleEdit'
|
||||
}).
|
||||
|
||||
when('/projects', {
|
||||
templateUrl: urlPrefix + 'partials/projects.html',
|
||||
controller: 'ProjectsList'
|
||||
@@ -145,6 +160,11 @@ angular.module('ansible', [
|
||||
templateUrl: urlPrefix + 'partials/projects.html',
|
||||
controller: 'ProjectsEdit'
|
||||
}).
|
||||
|
||||
when('/projects/:id/schedules', {
|
||||
templateUrl: urlPrefix + 'partials/schedule_detail.html',
|
||||
controller: 'ScheduleEdit'
|
||||
}).
|
||||
|
||||
when('/projects/:project_id/organizations', {
|
||||
templateUrl: urlPrefix + 'partials/projects.html',
|
||||
|
||||
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,
|
||||
cancelButton: false,
|
||||
name: 'group',
|
||||
well: true,
|
||||
well: false,
|
||||
formLabelSize: 'col-lg-3',
|
||||
formFieldSize: 'col-lg-9',
|
||||
|
||||
@@ -24,6 +24,9 @@ angular.module('GroupFormDefinition', [])
|
||||
}, {
|
||||
name: 'source',
|
||||
label: 'Source'
|
||||
},{
|
||||
name: 'schedules',
|
||||
label: 'Schedules'
|
||||
}],
|
||||
|
||||
fields: {
|
||||
@@ -46,7 +49,7 @@ angular.module('GroupFormDefinition', [])
|
||||
type: 'textarea',
|
||||
addRequired: false,
|
||||
editRequird: false,
|
||||
rows: 6,
|
||||
rows: 12,
|
||||
'default': '---',
|
||||
dataTitle: 'Group Variables',
|
||||
dataPlacement: 'right',
|
||||
@@ -184,7 +187,7 @@ angular.module('GroupFormDefinition', [])
|
||||
},
|
||||
|
||||
buttons: {
|
||||
|
||||
/*
|
||||
labelClass: 'col-lg-3',
|
||||
controlClass: 'col-lg-5',
|
||||
|
||||
@@ -196,6 +199,7 @@ angular.module('GroupFormDefinition', [])
|
||||
ngClick: 'formReset()',
|
||||
ngDisabled: true //Disabled when $pristine
|
||||
}
|
||||
*/
|
||||
},
|
||||
|
||||
related: { }
|
||||
|
||||
@@ -346,6 +346,54 @@ angular.module('JobTemplateFormDefinition', [])
|
||||
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',
|
||||
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>' +
|
||||
'<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 ' +
|
||||
'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'"
|
||||
}, {
|
||||
hdr: 'SVN URLs',
|
||||
@@ -262,6 +262,54 @@ angular.module('ProjectFormDefinition', [])
|
||||
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 + '/',
|
||||
master = {},
|
||||
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' });
|
||||
|
||||
scope.formModalActionLabel = 'Save';
|
||||
scope.formModalHeader = 'Group';
|
||||
scope.formModalCancelShow = true;
|
||||
scope.source = form.fields.source['default'];
|
||||
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) {
|
||||
var callback = function(){ Wait('stop'); };
|
||||
var callback = function(){
|
||||
Wait('stop');
|
||||
};
|
||||
if ($(e.target).text() === 'Properties') {
|
||||
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') {
|
||||
Wait('start');
|
||||
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) {
|
||||
scope.groupVariablesLoadedRemove();
|
||||
}
|
||||
scope.groupVariablesLoadedRemove = scope.$on('groupVariablesLoaded', function () {
|
||||
var callback = function() { Wait('stop'); };
|
||||
ParseTypeChange({ scope: scope, field_id: 'group_variables', onReady: callback });
|
||||
//$('#group_tabs a:first').tab('show');
|
||||
|
||||
//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
|
||||
@@ -600,6 +714,7 @@ angular.module('GroupsHelper', ['RestServices', 'Utilities', 'ListGenerator', 'G
|
||||
Rest.get()
|
||||
.success(function (data) {
|
||||
scope.variables = ParseVariableString(data);
|
||||
master.variables = scope.variables;
|
||||
scope.$emit('groupVariablesLoaded');
|
||||
})
|
||||
.error(function (data, status) {
|
||||
@@ -609,10 +724,10 @@ angular.module('GroupsHelper', ['RestServices', 'Utilities', 'ListGenerator', 'G
|
||||
});
|
||||
} else {
|
||||
scope.variables = "---";
|
||||
master.variables = scope.variables;
|
||||
scope.$emit('groupVariablesLoaded');
|
||||
}
|
||||
master.variables = scope.variables;
|
||||
|
||||
|
||||
if (scope.source_url) {
|
||||
// get source data
|
||||
Rest.setUrl(scope.source_url);
|
||||
@@ -724,7 +839,7 @@ angular.module('GroupsHelper', ['RestServices', 'Utilities', 'ListGenerator', 'G
|
||||
}
|
||||
scope.variable_url = data.related.variable_data;
|
||||
scope.source_url = data.related.inventory_source;
|
||||
$('#form-modal').modal('show');
|
||||
//$('#form-modal').modal('show');
|
||||
scope.$emit('groupLoaded');
|
||||
})
|
||||
.error(function (data, status) {
|
||||
@@ -803,7 +918,7 @@ angular.module('GroupsHelper', ['RestServices', 'Utilities', 'ListGenerator', 'G
|
||||
|
||||
scope.formModalActionDisabled = false;
|
||||
|
||||
$('#form-modal').modal('hide');
|
||||
$('#group-modal-dialog').dialog('close');
|
||||
|
||||
// Change the selected group
|
||||
if (groups_reload && parent_scope.selected_tree_id !== tree_id) {
|
||||
@@ -864,6 +979,12 @@ angular.module('GroupsHelper', ['RestServices', 'Utilities', 'ListGenerator', 'G
|
||||
|
||||
// Cancel
|
||||
scope.cancelModal = function () {
|
||||
try {
|
||||
$('#group-modal-dialog').dialog('close');
|
||||
}
|
||||
catch(e) {
|
||||
//ignore
|
||||
}
|
||||
if (scope.searchCleanup) {
|
||||
scope.searchCleanup();
|
||||
}
|
||||
@@ -871,7 +992,7 @@ angular.module('GroupsHelper', ['RestServices', 'Utilities', 'ListGenerator', 'G
|
||||
};
|
||||
|
||||
// Save
|
||||
scope.formModalAction = function () {
|
||||
scope.saveGroup = function () {
|
||||
Wait('start');
|
||||
var fld, data, json_data;
|
||||
|
||||
|
||||
@@ -19,23 +19,21 @@ angular.module('ParseHelper', ['Utilities', 'AngularCodeMirrorModule'])
|
||||
fld = (params.variable) ? params.variable : 'variables',
|
||||
pfld = (params.parse_variable) ? params.parse_variable : 'parseType',
|
||||
onReady = params.onReady,
|
||||
onChange = params.onChange,
|
||||
codeMirror;
|
||||
|
||||
onChange = params.onChange;
|
||||
|
||||
function removeField() {
|
||||
//set our model to the last change in CodeMirror and then destroy CodeMirror
|
||||
scope[fld] = codeMirror.getValue();
|
||||
codeMirror.destroy();
|
||||
scope[fld] = scope.codeMirror.getValue();
|
||||
scope.codeMirror.destroy();
|
||||
}
|
||||
|
||||
function createField(onChange, onReady) {
|
||||
//hide the textarea and show a fresh CodeMirror with the current mode (json or yaml)
|
||||
codeMirror = AngularCodeMirror();
|
||||
codeMirror.addModes($AnsibleConfig.variable_edit_modes);
|
||||
codeMirror.showTextArea({ scope: scope, model: fld, element: field_id, mode: scope[pfld], onReady: onReady, onChange: onChange });
|
||||
scope.codeMirror = AngularCodeMirror();
|
||||
scope.codeMirror.addModes($AnsibleConfig.variable_edit_modes);
|
||||
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
|
||||
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) {
|
||||
var result = "---", json_obj;
|
||||
if (typeof variables === 'string') {
|
||||
if ($.isEmptyObject(variables) || variables === "{}" || variables === "null" ||
|
||||
variables === "" || variables === null) {
|
||||
if (variables === "{}" || variables === "null" || variables === "") {
|
||||
// String is empty, return ---
|
||||
} else {
|
||||
try {
|
||||
@@ -45,13 +44,18 @@ angular.module('VariablesHelper', ['Utilities'])
|
||||
}
|
||||
}
|
||||
else {
|
||||
// an object was passed in. just convert to yaml
|
||||
try {
|
||||
result = jsyaml.safeDump(variables);
|
||||
if ($.isEmptyObject(variables) || variables === null) {
|
||||
// Empty object, return ---
|
||||
}
|
||||
catch(e3) {
|
||||
ProcessErrors(null, variables, e3.message, null, { hdr: 'Error!',
|
||||
msg: 'Attempt to convert JSON object to YAML document failed: ' + e3.message });
|
||||
else {
|
||||
// convert object to yaml
|
||||
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;
|
||||
|
||||
@@ -65,7 +65,8 @@ angular.module('JobTemplatesListDefinition', [])
|
||||
schedule: {
|
||||
label: 'Schedule',
|
||||
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'
|
||||
},
|
||||
"delete": {
|
||||
|
||||
@@ -112,6 +112,13 @@ angular.module('ProjectsListDefinition', [])
|
||||
ngShow: "project.status == 'updating'",
|
||||
dataPlacement: 'top'
|
||||
},
|
||||
schedule: {
|
||||
label: 'Schedule',
|
||||
mode: 'all',
|
||||
ngHref: '#/projects/{{ project.id }}/schedules',
|
||||
awToolTip: 'Schedule future project sync runs',
|
||||
dataPlacement: 'top'
|
||||
},
|
||||
"delete": {
|
||||
label: 'Delete',
|
||||
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 "jquery-ui-overrides.less";
|
||||
@import "codemirror.less";
|
||||
@import "angular-scheduler.less";
|
||||
|
||||
|
||||
html, body { height: 100%; }
|
||||
@@ -527,6 +528,9 @@ dd {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
#group_form #group_tabs {
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
/* Outline required fields in Red when there is an error */
|
||||
.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;
|
||||
}
|
||||
|
||||
.checkbox-group {
|
||||
.radio-inline + .radio-inline,
|
||||
.checkbox-inline + .checkbox-inline {
|
||||
margin-left: 0;
|
||||
}
|
||||
.checkbox-inline, .radio-inline {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox-options {
|
||||
font-weight: normal;
|
||||
padding-right: 20px;
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
*/
|
||||
|
||||
|
||||
table.ui-datepicker-calendar {
|
||||
background-color: @well;
|
||||
}
|
||||
|
||||
/* Modal dialog */
|
||||
|
||||
.ui-dialog-title {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"name": "angular-scheduler",
|
||||
"version": "0.0.2",
|
||||
"authors": [
|
||||
"Chris Houseknecht <chouse@ansible.com>"
|
||||
],
|
||||
@@ -35,14 +36,13 @@
|
||||
"rrule",
|
||||
"calendar"
|
||||
],
|
||||
"_release": "f2488ff1ec",
|
||||
"_release": "0.0.2",
|
||||
"_resolution": {
|
||||
"type": "branch",
|
||||
"branch": "master",
|
||||
"commit": "f2488ff1ec1b2aa48206fa97111b6f8d5e88de89"
|
||||
"type": "version",
|
||||
"tag": "v0.0.2",
|
||||
"commit": "f9df5d081112d0ebecfd418bd859c26f0c7711b4"
|
||||
},
|
||||
"_source": "git://github.com/chouseknecht/angular-scheduler.git",
|
||||
"_target": "*",
|
||||
"_originalSource": "angular-scheduler",
|
||||
"_direct": true
|
||||
"_originalSource": "angular-scheduler"
|
||||
}
|
||||
@@ -60,6 +60,20 @@ textarea {
|
||||
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:active,
|
||||
a:link,
|
||||
@@ -143,7 +157,7 @@ a:hover {
|
||||
color: #A9A9A9;
|
||||
}
|
||||
.mono-space {
|
||||
font-family: Fixed, monospace;
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
}
|
||||
textarea.resizable {
|
||||
resize: vertical;
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
<!-- rrule -->
|
||||
<script src="/bower_components/underscore/underscore.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-route/angular-route.min.js"></script>
|
||||
|
||||
@@ -31,9 +31,6 @@ angular.module('sampleApp', ['ngRoute', 'AngularScheduler', 'Timezones'])
|
||||
|
||||
scheduler.inject('form-container', true);
|
||||
|
||||
console.log('User timezone: ');
|
||||
console.log(scheduler.getUserTimezone());
|
||||
|
||||
$scope.setRRule = function() {
|
||||
$scope.inputRRuleMsg = '';
|
||||
$scope.inputRRuleMsg = scheduler.setRRule($scope.inputRRule);
|
||||
@@ -44,19 +41,44 @@ angular.module('sampleApp', ['ngRoute', 'AngularScheduler', 'Timezones'])
|
||||
$scope.saveForm = function() {
|
||||
if (scheduler.isValid()) {
|
||||
var schedule = scheduler.getValue(),
|
||||
html =
|
||||
"<form>\n" +
|
||||
"<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",
|
||||
rrule = scheduler.getRRule(),
|
||||
html,
|
||||
wheight = $(window).height(),
|
||||
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;
|
||||
h = (400 > wheight) ? wheight : 400;
|
||||
h = (600 > wheight) ? wheight : 600;
|
||||
|
||||
$('#message').html(html)
|
||||
.dialog({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-scheduler",
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.2",
|
||||
"authors": [
|
||||
"Chris Houseknecht <chouse@ansible.com>"
|
||||
],
|
||||
|
||||
@@ -47,9 +47,10 @@
|
||||
margin-top: 0;
|
||||
padding-top: 3px;
|
||||
}
|
||||
.pull-up {
|
||||
margin-top: -15px;
|
||||
margin-bottom: 10px;
|
||||
.error-pull-up {
|
||||
position: relative;
|
||||
top: -15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.red-text {
|
||||
color: #dd1b16;
|
||||
|
||||
@@ -15,16 +15,13 @@
|
||||
<div class="col-md-12">
|
||||
|
||||
<form class="form" role="form" name="scheduler_form" novalidate>
|
||||
<div class="row">
|
||||
<div class="col-md-5">
|
||||
<div class="form-group">
|
||||
<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">
|
||||
<div class="error" ng-show="scheduler_form.schedulerName.$dirty && scheduler_form.schedulerName.$error.required">Schedule name is required</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<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">
|
||||
<div class="error" ng-show="scheduler_form.schedulerName.$dirty && scheduler_form.schedulerName.$error.required">Schedule name is required</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-5">
|
||||
<div class="form-group">
|
||||
@@ -53,10 +50,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="row error-pull-up">
|
||||
<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>
|
||||
|
||||
@@ -79,7 +76,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<label>Repeat</label>
|
||||
<label>Repeat frequency</label>
|
||||
<select name="schedulerFrequency" id="schedulerFrequency" ng-model="schedulerFrequency"
|
||||
ng-options="f.name for f in frequencyOptions" required class="form-control input-sm"
|
||||
ng-change="scheduleRepeatChange()"></select>
|
||||
@@ -226,4 +223,4 @@
|
||||
</div>
|
||||
|
||||
</div><!-- col-md-12 -->
|
||||
</div><!-- row -->
|
||||
</div><!-- row -->
|
||||
|
||||
@@ -290,14 +290,14 @@ angular.module('AngularScheduler', ['underscore'])
|
||||
}
|
||||
}
|
||||
else {
|
||||
scope.startDateError("Provide a valid start date and time");
|
||||
this.scope.startDateError("Provide a valid start date and time");
|
||||
validity = false;
|
||||
}
|
||||
return validity;
|
||||
};
|
||||
|
||||
// Returns an rrule object
|
||||
this.getRule = function() {
|
||||
this.getRRule = function() {
|
||||
var options = this.getOptions();
|
||||
return GetRule(options);
|
||||
};
|
||||
@@ -305,7 +305,7 @@ angular.module('AngularScheduler', ['underscore'])
|
||||
// Return object containing schedule name, string representation of rrule per iCalendar RFC,
|
||||
// and options used to create rrule
|
||||
this.getValue = function() {
|
||||
var rule = this.getRule(),
|
||||
var rule = this.getRRule(),
|
||||
options = this.getOptions();
|
||||
return {
|
||||
name: scope.schedulerName,
|
||||
@@ -319,6 +319,10 @@ angular.module('AngularScheduler', ['underscore'])
|
||||
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.
|
||||
// Pass in the target element's id attribute value or an angular.element()
|
||||
// object.
|
||||
@@ -336,13 +340,13 @@ angular.module('AngularScheduler', ['underscore'])
|
||||
// Get the user's local timezone
|
||||
this.getUserTimezone = function() {
|
||||
return $timezones.getLocal();
|
||||
}
|
||||
};
|
||||
};
|
||||
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) {
|
||||
|
||||
var scope = params.scope,
|
||||
@@ -741,12 +745,12 @@ angular.module('AngularScheduler', ['underscore'])
|
||||
|
||||
scope.frequencyOptions = [
|
||||
{ name: 'None (run once)', value: 'none', intervalLabel: '' },
|
||||
{ name: 'Minutely', value: 'minutely', intervalLabel: 'minutes' },
|
||||
{ name: 'Hourly', value: 'hourly', intervalLabel: 'hours' },
|
||||
{ name: 'Daily', value: 'daily', intervalLabel: 'days' },
|
||||
{ name: 'Weekly', value: 'weekly', intervalLabel: 'weeks' },
|
||||
{ name: 'Monthly', value: 'monthly', intervalLabel: 'months' },
|
||||
{ name: 'Yearly', value: 'yearly', intervalLabel: 'years' }
|
||||
{ name: 'Minute', value: 'minutely', intervalLabel: 'minutes' },
|
||||
{ name: 'Hour', value: 'hourly', intervalLabel: 'hours' },
|
||||
{ name: 'Day', value: 'daily', intervalLabel: 'days' },
|
||||
{ name: 'Week', value: 'weekly', intervalLabel: 'weeks' },
|
||||
{ name: 'Month', value: 'monthly', intervalLabel: 'months' },
|
||||
{ name: 'Year', value: 'yearly', intervalLabel: 'years' }
|
||||
];
|
||||
|
||||
scope.endOptions = [
|
||||
@@ -830,6 +834,11 @@ angular.module('AngularScheduler', ['underscore'])
|
||||
options.maxDate = (attrs.maxDate) ? new Date(attrs('maxDate')) : null;
|
||||
options.changeMonth = (attrs.changeMonth === "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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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 += "><a id=\"" + tab.name + "_link\" ng-click=\"toggleTab($event, '" + tab.name + "_link', '" +
|
||||
this.form.name + "_tabs')\" href=\"#" + tab.name + "\"" +
|
||||
tab.name + "\" data-toggle=\"tab\">" + tab.label + "</a></li>\n";
|
||||
this.form.name + "_tabs')\" href=\"#" + tab.name + "\" data-toggle=\"tab\">" + tab.label + "</a></li>\n";
|
||||
}
|
||||
html += "</ul>\n";
|
||||
html += "<div class=\"tab-content\">\n";
|
||||
@@ -1396,7 +1395,7 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies', 'Utilities'])
|
||||
}
|
||||
html += "\"></i></a></th>\n";
|
||||
}
|
||||
html += "<th></th>\n";
|
||||
html += "<th>Actions</th>\n";
|
||||
html += "</tr>\n";
|
||||
html += "</thead>";
|
||||
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-md5/angular-md5.min.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 %}
|
||||
<script src="{{ STATIC_URL }}js/awx.min.js"></script>
|
||||
{% else %}
|
||||
@@ -66,6 +76,7 @@
|
||||
<script src="{{ STATIC_URL }}js/controllers/JobEvents.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/Schedules.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/Inventories.js"></script>
|
||||
@@ -104,6 +115,7 @@
|
||||
<script src="{{ STATIC_URL }}js/lists/HomeHosts.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/Schedules.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/refresh.js"></script>
|
||||
@@ -128,6 +140,7 @@
|
||||
<script src="{{ STATIC_URL }}js/helpers/Groups.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/Schedules.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/SCMSyncStatus.js"></script>
|
||||
@@ -401,7 +414,7 @@
|
||||
<script src="{{ STATIC_URL }}lib/codemirror/addon/edit/closebrackets.js"></script>
|
||||
<script src="{{ STATIC_URL }}lib/codemirror/addon/edit/matchbrackets.js"></script>
|
||||
<script src="{{ STATIC_URL }}lib/codemirror/addon/selection/active-line.js"></script>
|
||||
|
||||
|
||||
<script>
|
||||
// When user clicks on main tab, fire the matching Angular route
|
||||
$('a[data-toggle="tab"]').on('show.bs.tab', function (e) {
|
||||
|
||||
Reference in New Issue
Block a user