/* HeuristicLab
* Copyright (C) 2002-2015 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
*
* This file is part of HeuristicLab.
*
* HeuristicLab is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* HeuristicLab is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with HeuristicLab. If not, see .
*/
angular.module('wjm', ['ui.bootstrap', 'ui.calendar', 'angularTreeview', 'ngDialog']).
controller('resourceCtrl', function ($rootScope, $scope, uiCalendarConfig, $compile, ngDialog, $timeout) {
var vm = $scope;
var date = new Date();
var d = date.getDate();
var m = date.getMonth();
var y = date.getFullYear();
var hubber = $.connection.calendarHub;
vm.currentcal = [];//Reference to current calendar (treeview.currentNode.calendar)
$scope.permissionview = false;//Show permissions or not
$scope.resaddview = false;//Show add resources or not
vm.selectedEventId = -1;//Selected downtime (-1 for none)
vm.groups = []; //All resource groups
vm.clients = []; //All resources
vm.init = function () {
var v = document.getElementById("userId").innerHTML;
$.connection.hub.qs = { 'userid': v };
//Connection string set to identify the unique session ID for the user
$.connection.hub.start().done(function () {
hubber.server.requestInfo();//initial data request
});
//Process all initial data needed (heavy load)
hubber.client.processData = function (data, users, groups) {
vm.data = JSON.parse(data);
vm.permUsers = JSON.parse(users);
vm.permGroups = JSON.parse(groups);
$scope.buildTree();
$scope.$apply();
};
//Saving from current graph is done -> refresh it
hubber.client.savingCurrentDone = function () {
vm.calendarSaver = false;
$scope.clearCurrentCalendar();
$scope.$apply();
};
//Saving all graphs done -> clear everything
hubber.client.savingAllDone = function () {
vm.calendarSaver = false;
clearAllCalendarsFunc();
$scope.$apply();
};
//Dispose toggle refresh for current
hubber.client.processDispose = function (disp) {
vm.treeview.currentNode.IsDisposable = disp;
$scope.calendarDispose = false;
$scope.$apply();
}
//Permissions refresh for specific resource
hubber.client.processPermissions = function (id, perm) {
var json = JSON.parse(perm);
$scope.treeview.currentNode.permissions = json;
refreshPermissions();
$scope.permissionLoader = false;
$scope.$apply();
}
//Process downtimes for a resource. Data conversion
hubber.client.processDowntime = function (id, down) {
var json = JSON.parse(down);
var arrdown = [];
var str = "";
for (var i = 0; i < json.length; i++) {
if (json[i].DowntimeType === 0) {
str = "Unavailable";
col = "#006080";
}
else {
str = "Shutdown";
col = "#993300";
}
arrdown.push({
id: json[i].Id,
title: str,
start: new Date(json[i].StartDate),
end: new Date(json[i].EndDate),
allDay: json[i].AllDayEvent,
color: col,
rec: {
recurrence: json[i].Recurring,
recid: json[i].RecurringId,
days: [false, false, false, false, false, false, false],
start: new Date(json[i].StartDate),
end: new Date(json[i].EndDate)
},
changed: false
});
}
var dat = { 'id': id, 'down': [arrdown] };
$scope.treeview.currentNode.calendar = dat;
$scope.currentcal = $scope.treeview.currentNode.calendar;
$("#resourcecalendar").fullCalendar('refresh');
$scope.$apply();
}
//Initial function to build the resource tree
$scope.buildTree = function () {
vm.tree = [];
vm.temptree = [];
vm.top = false;
var ungrouped = {
children: [],
Name: 'Ungrouped'
}
for (; vm.data.length > 0;) {
if (vm.data[0].ParentResourceId == null && vm.data[0].IsDisposable !== undefined) {
var curr = $scope.seekChildren(vm.data.splice(0, 1)[0]);
vm.clients.push(curr);
ungrouped.children.push(curr);
}
else if (vm.data[0].ParentResourceId == null) {
var curr = $scope.seekChildren(vm.data.splice(0, 1)[0]);
vm.groups.push(curr);
vm.tree.push(curr);
}
else {
var curr = $scope.seekChildren(vm.data.splice(0, 1)[0]);
if (curr.IsDisposable !== undefined)
vm.clients.push(curr);
else
vm.groups.push(curr);
vm.temptree.push(curr);
}
}
vm.tree.push(ungrouped);
};//End for hubber init
//menu navigation
$scope.menuchange = function (i) {
switch (i) {
case 0:
$scope.permissionview = false;
$scope.resaddview = false;
break;
case 1:
$scope.permissionview = true;
$scope.resaddview = false;
break;
case 2:
$scope.permissionview = false;
$scope.resaddview = true;
break;
}
}
//Seeks for children, used recursively for building the treeview
$scope.seekChildren = function (current) {
current.calendar = [];
current.children = [];
current.todelete = [];
current.changes = false;
current.collapsed = true;
for (var t = 0; t < vm.temptree.length;) {
if (current.Id == vm.temptree[t].ParentResourceId) {
current.children.push(vm.temptree.splice(t, 1)[0]);
}
else {
t++;
}
}
var childc = current.children.length;//Remembers count of children received from temp tree;
for (var t = 0; t < vm.data.length;) {
if (current.Id == vm.data[t].ParentResourceId) {
if (vm.data[t].IsDisposable !== undefined)
vm.clients.push(vm.data[t]);
else
vm.groups.push(vm.data[t]);
current.children.push(vm.data.splice(t, 1)[0]);
}
else {
t++;
}
}
for (var t = childc; t < current.children.length; t++) {
current.children[t] = $scope.seekChildren(current.children[t]);
}
return current;
}
//Change to another resource
$scope.$watch("treeview.currentNode", function (newValue, oldValue) {
$scope.currentcal = [];
$scope.resaddview = false;
$(".selected.ng-binding").addClass('loaded');
$scope.selectedEventId = -1;
//Check if resource already has downtime data loaded
if ($scope.treeview.currentNode != null && $scope.treeview.currentNode.Id != undefined) {
if ($scope.treeview.currentNode.calendar.length === 0) {
//Reach out to server to receive resource data
hubber.server.requestPermissions(vm.treeview.currentNode.Id);
hubber.server.requestDownTime(vm.treeview.currentNode.Id);
vm.permissionLoader = true;
}
else {
$timeout(function () {
refreshPermissions();
//set previously loaded data
$scope.currentcal = $scope.treeview.currentNode.calendar;
}, 0);
}
$scope.refreshAdds();
}
});
}
//Resets the add resources menu
$scope.refreshAdds = function() {
for (var i = 0; i < $scope.clients.length; i++) {
$scope.clients[i].add = false;
for (var j = 0; j < $scope.treeview.currentNode.children.length; j++) {
if ($scope.clients[i].Id === $scope.treeview.currentNode.children[j].Id)
$scope.clients[i].add = true;
}
}
}
//Resets the permissions menu
function refreshPermissions() {
for (var i = 0; i < $scope.permUsers.length; i++){
$scope.permUsers[i].state = false;
for (var j = 0; j < $scope.treeview.currentNode.permissions.length; j++) {
if($scope.permUsers[i].Id === $scope.treeview.currentNode.permissions[j].GrantedUserId)
$scope.permUsers[i].state = true;
}
}
for (var i = 0; i < $scope.permGroups.length; i++) {
$scope.permGroups[i].state = false;
for (var j = 0; j < $scope.treeview.currentNode.permissions.length; j++) {
if ($scope.permGroups[i].Id === $scope.treeview.currentNode.permissions[j].GrantedUserId)
$scope.permGroups[i].state = true;
}
}
}
//Add resource group show by clearing the tree
$scope.clearTreeSelect = function () {
if (vm.treeview.currentNode != undefined) {
vm.treeview.currentNode.selected = undefined;
vm.treeview.currentNode = undefined;
vm.selectedEventId = -1;
}
}
//Reach out to server to toggle disposable for current calendar
$scope.toggleDisposable = function () {
$scope.calendarDispose = true;
hubber.server.toggleDisposable(vm.treeview.currentNode.Id);
}
//Pushes permissions to server
$scope.pushPermissions = function () {
var perms = [];
$scope.permissionLoader = true;
var node = $scope.treeview.currentNode;
for (var i = 0; i < $scope.permGroups.length; i++) {
if ($scope.permGroups[i].state === true)
perms.push($scope.permGroups[i].Id);
}
for (var i = 0; i < $scope.permUsers.length; i++) {
if ($scope.permUsers[i].state === true)
perms.push($scope.permUsers[i].Id);
}
hubber.server.changePermissions(perms, node.Id);
}
//Collects all data from a single resource to save to the server
function collectInfoToSave(node, refresh, last) {
var arr = node.calendar.down[0];
var toadd = [];
var todel = node.todelete;
var toupd = [];
for (var i = 0; i < arr.length; i++) {
if (arr[i].id === '00000000-0000-0000-0000-000000000000') {
//NEW DOWNTIME
var t = arr[i].allDay.toString();
toadd.push([
arr[i].title,//status
"" + Date.parse(arr[i].start),//start
"" + Date.parse(arr[i].end),//end
t,// allday
node.Id, //resource id
arr[i].rec.recurrence.toString(),//true on recur
arr[i].rec.recid,//recur id
"" + Date.parse(arr[i].rec.start),
"" + Date.parse(arr[i].rec.end),
arr[i].rec.days.join(",")
]);
}
else if (arr[i].changed === true) {
//EDIT EXISTING DOWNTIME
var t = arr[i].allDay.toString();
toupd.push([
arr[i].id,//id to update
arr[i].title,//status
"" + Date.parse(arr[i].start),//start
"" + Date.parse(arr[i].end),//end
t,//allday
arr[i].rec.recurrence.toString(),//true on recur
arr[i].rec.recid,//recur id
"" + Date.parse(arr[i].rec.start),
"" + Date.parse(arr[i].rec.end),
arr[i].rec.days.join(",")
]);
}
}
vm.sendtoserv = [todel, toadd, toupd];
hubber.server.saveCalendar(node.Id, todel, toadd, toupd, refresh, last);
}
//Save current calendar
$scope.saveCurrentCalendar = function () {
vm.calendarSaver = true;
collectInfoToSave(vm.treeview.currentNode, true, false);//true for refresh, false for save all
//true for refresh, false for showing it's only one calendar saved.
}
//Save all changed calendars
$scope.saveAllCalendars = function () {
$scope.allSave = [];
for (var i = 0; i < $scope.tree.length; i++)
createSaveRequests($scope.tree[i]);
ngDialog.openConfirm({
template:
'
Are you sure you want to save all changes made to ' + $scope.allSave.length + ' calendars?
' +
'
' +
'
',
plain: true
}).then(function (success) {
vm.calendarSaver = true;
for (var i = 0; i < $scope.allSave.length; i++) {
if (i >= $scope.allSave.length - 1)
collectInfoToSave($scope.allSave[i], false, true);
else
collectInfoToSave($scope.allSave[i], false, false);
}
});
}
//Checks treeview for edits and builds new array containing these (recurse for children)
function createSaveRequests(node) {
if (node.changes === true)
$scope.allSave.push(node);
for (var i = 0; i < node.children.length; i++) {
createSaveRequests(node.children[i]);
}
}
//Clears and refreshes calendar for current resource (delete current changes)
$scope.clearCurrentCalendar = function () {
$scope.selectedEventId = -1;
$scope.treeview.currentNode.calendar = null;
$scope.treeview.currentNode.todelete = [];
$scope.treeview.currentNode.changes = false;
$scope.currentcal = [];
hubber.server.requestDownTime(vm.treeview.currentNode.Id);
$(".selected.ng-scope").removeClass('changed');
$(".selected.ng-binding").removeClass('changed');
}
//Clears all calendars and refreshes current (Deletes all changes)
$scope.clearAllCalendars = function () {
ngDialog.openConfirm({
template:
'
Are you sure you want to delete all calendar changes?
' +
'
' +
'No ' +
'Yes' +
'
',
plain: true
}).then(function (success) {
clearAllCalendarsFunc();
});
}
//Function that clears all calendar (separate so dialog is not called when saveAll is finished)
function clearAllCalendarsFunc() {
$(".ng-binding.changed").removeClass('changed');
$(".ng-scope.changed").removeClass('changed');
$(".ng-binding.loaded").removeClass('loaded');
$(".ng-scope.loaded").removeClass('loaded');
$scope.selectedEventId = -1;
$scope.treeview.currentNode.calendar = null;
$scope.currentcal = [];
for (var i = 0; i < $scope.tree.length; i++)
clearCalendarsRecurse($scope.tree[i]);
hubber.server.requestDownTime(vm.treeview.currentNode.Id);
$(".selected.ng-binding").addClass('loaded');
$(".selected.ng-scope").addClass('loaded');
}
//Recurse trough tree view
function clearCalendarsRecurse(node) {
node.calendar = [];
node.todelete = [];
node.changes = false;
for (var i = 0; i < node.children.length; i++) {
clearCalendarsRecurse(node.children[i]);
}
}
//Delete all downtimes from current resource
$scope.deleteAllEvents = function () {
vm.calendarDeleter = true;
vm.selectedEventId = -1;
var node = vm.treeview.currentNode.calendar.down[0];
for (var i = 0; i < node.length;) {
if (node[i].id != '00000000-0000-0000-0000-000000000000')
vm.treeview.currentNode.todelete.push(node[i].id);
$scope.setChanged(vm.selectedEventId);
node.splice(i, 1);
}
vm.calendarDeleter = false;
ngDialog.open({
template:
'
All events have been deleted. Save the changes to confirm deletion, clearing will restore all the events from the server
',
plain: true
});
}
//Delete all past events
$scope.deleteAllPreviousEvents = function () {
vm.calendarDeleter = true;
vm.selectedEventId = -1;
var node = vm.treeview.currentNode.calendar.down[0];
for (var i = 0; i < node.length;) {
if (node[i].end < Date.now()) {
if (node[i].id != '00000000-0000-0000-0000-000000000000')
vm.treeview.currentNode.todelete.push(node[i].id);
$scope.setChanged(vm.selectedEventId);
node.splice(i, 1);
}
else
i++;
}
vm.calendarDeleter = false;
ngDialog.open({
template:
'
All previous events have been deleted. Save the changes to confirm deletion
',
plain: true
});
}
//Sets the status of a downtime AND the current resource to changed
$scope.setChanged = function (id) {
if (id != -1) {
if (vm.treeview.currentNode.calendar.down[0][id].title === "Unavailable")
vm.treeview.currentNode.calendar.down[0][id].color = '#0099cc';
else
vm.treeview.currentNode.calendar.down[0][id].color = '#ff5500';
vm.treeview.currentNode.calendar.down[0][id].changed = true;
}
vm.treeview.currentNode.changes = true;
$(".selected").addClass('changed');
}
//Adds event by click on empty space
$scope.calendarClick = function (date, jsEvent, view) {
var newid = 0;
if (vm.treeview.currentNode.calendar.down[0].length != 0)
newid = vm.treeview.currentNode.calendar.down[0][(vm.treeview.currentNode.calendar.down[0].length - 1)]._id + 1;
var dat = date.toDate();
var end = new Date(dat);
end.setHours(dat.getHours() + 2);
vm.treeview.currentNode.calendar.down[0].push({
id: '00000000-0000-0000-0000-000000000000',//Makes it recognizable as new for the server
title: 'Unavailable',
start: dat,
end: end,
allDay: date._ambigTime,
rec: {
recurrence: false,
recid: '0',
days: [false, false, false, false, false, false, false],
start: dat,
end: end
},
_id: (newid)
});
vm.selectedEventId = vm.treeview.currentNode.calendar.down[0].length - 1;
vm.setChanged(vm.selectedEventId);
$scope.currentcal = [];
$scope.currentcal = vm.treeview.currentNode.calendar;
}
//Finds array index for specific _id
function checkId(id) {
for (var i = 0; i < vm.treeview.currentNode.calendar.down[0].length ; i++) {
if (vm.treeview.currentNode.calendar.down[0][i]._id === id)
return i;
}
return -1;
}
//Set selected downtime
$scope.eventClick = function (date, jsEvent, view) {
vm.selectedEventId = checkId(date._id);
};
//Sets selected downtime by clicking on the button at bottom on the page, moves to date
$scope.eventClickBtn = function (id) {
vm.selectedEventId = checkId(id);
vm.goToDate();
};
//Drag and drop downtime
$scope.dragandDrop = function (event, delta, revertFunc, jsEvent, ui, view) {
vm.selectedEventId = checkId(event._id);
vm.setChanged(vm.selectedEventId);
if (event.end == null) {
event.end = moment(event.start).add(2, 'hours');
}
if (vm.treeview.currentNode.calendar.down[0][vm.selectedEventId].allDay && event.start._ambigTime == false) {
event.allDay = false;
vm.treeview.currentNode.calendar.down[0][vm.selectedEventId].allDay = false;
}
else if (!vm.treeview.currentNode.calendar.down[0][vm.selectedEventId].allDay && event.start._ambigTime == true) {
event.allDay = true;
vm.treeview.currentNode.calendar.down[0][vm.selectedEventId].allDay = true;
//vm.treeview.currentNode.calendar.down[0][vm.selectedEventId].end = null;
}
vm.treeview.currentNode.calendar.down[0][vm.selectedEventId].start = new Date(event.start);
vm.treeview.currentNode.calendar.down[0][vm.selectedEventId].end = new Date(event.end);
};
//Resize downtime
$scope.resizeEvent = function (event, delta, revertFunc, jsEvent, ui, view) {
vm.selectedEventId = checkId(event._id);
vm.setChanged(vm.selectedEventId);
vm.treeview.currentNode.calendar.down[0][vm.selectedEventId].start = new Date(event.start);
vm.treeview.currentNode.calendar.down[0][vm.selectedEventId].end = new Date(event.end);
};
//Removes a downtime from client (existing downtime gets referenced in todelete
$scope.remove = function (index) {
vm.selectedEventId = -1;
vm.setChanged(vm.selectedEventId);
if (vm.treeview.currentNode.calendar.down[0][index].id != "00000000-0000-0000-0000-000000000000") {
vm.treeview.currentNode.todelete.push(vm.treeview.currentNode.calendar.down[0][index].id);
}
vm.treeview.currentNode.calendar.down[0].splice(index, 1);
};
//Remove trough list view at bottom
$scope.removeList = function (id) {
var index = checkId(id);
vm.selectedEventId = -1;
vm.setChanged(vm.selectedEventId);
if (vm.treeview.currentNode.calendar.down[0][index].id != "00000000-0000-0000-0000-000000000000") {
vm.treeview.currentNode.todelete.push(vm.treeview.currentNode.calendar.down[0][index].id);
}
vm.treeview.currentNode.calendar.down[0].splice(index, 1);
};
//Check if start is before end AND start is after now (recursion)
$scope.checkDateStartEnd = function () {
if ($scope.currentcal.down[0][vm.selectedEventId].rec.start > $scope.currentcal.down[0][vm.selectedEventId].rec.end ||
$scope.currentcal.down[0][vm.selectedEventId].rec.end === undefined) {
$scope.currentcal.down[0][vm.selectedEventId].rec.end = new Date($scope.currentcal.down[0][vm.selectedEventId].rec.start);
}
if ($scope.currentcal.down[0][vm.selectedEventId].rec.start < Date.now())
$scope.currentcal.down[0][vm.selectedEventId].rec.start = new Date();
}
//pushes all changes to other existing recurs and creates new downtimes where needed
$scope.pushRecurChanges = function (recid) {
ngDialog.openConfirm({
template:
'
This will change all existing recurrences and create new recurrences where needed. Are you sure?
' +
'
' +
'No ' +
'Yes' +
'
',
plain: true
}).then(function (success) {
var arr = vm.treeview.currentNode.calendar.down[0];
//Deep copy of downtime for referencing
var ob = $.extend(true, {}, arr[vm.selectedEventId]);
for (var i = 0; i < arr.length;) {//Go trough downtimes and find all recurrences
if (arr[i].rec.recid === recid) {
if (new Date(arr[i].start) < new Date(ob.rec.start) ||
new Date(arr[i].end) > new Date(ob.rec.end) ||
!ob.rec.days[new Date(arr[i].start).getDay()]) {
//Downtime is out of recurrence boundaries
//Before start date OR After end date OR Not on right day of the week
vm.remove(i);//NO I++ -> REMOVE SPLICES ARRAY
vm.selectedEventId = -1;
}
else {
//Edit downtime when it is within the bounds
arr[i].start.setHours(ob.start.getHours(), ob.start.getMinutes());
arr[i].end.setHours(ob.end.getHours(), ob.end.getMinutes());
arr[i].title = ob.title;
arr[i].rec = ob.rec;
arr[i].allDay = ob.allDay;
vm.setChanged(i);
i++;
}
}
else
i++;
}
//init for new downtimes.
var start = new Date(ob.rec.start);
start.setHours(2, 0, 0, 0);//Beginning of the day + time conversion +02
var end = new Date(ob.rec.end);
end = new Date(end.setHours(24, 0, 0, 0) + 1000 * 3600 * 2);//End of the day + time conversion +02
loop1: //Loop start to end with single day increment
for (var d = (start.getTime()) ; d < (end.getTime()) ;) {
var tog = ob.rec.days[new Date(d).getDay()];
//Check if day of the week is included in recursion
if (tog) {
loop2://Loop checking existing downtime array to see if day is already filled or not
for (var i = 0; i < arr.length; i++) {
if (arr[i].rec.recid === recid) {
var dend = (d + 1000 * 3600 * 24);
if (arr[i].start.getTime() >= d &&
arr[i].end.getTime() <= dend) {
d += (1000 * 3600 * 24);//add day to loop 1
continue loop1;//breaks out of loop2 and skips to next day
}
}
}
//Made it here = new event needed: init
var ts = new Date(d);
ts.setHours(new Date(ob.start).getHours(), new Date(ob.start).getMinutes());
var te = new Date(d);
te.setHours(new Date(ob.end).getHours(), new Date(ob.end).getMinutes());
arr.push({
id: '00000000-0000-0000-0000-000000000000',
title: ob.title,
start: ts,
end: te,
allDay: ob.allDay,
rec: ob.rec
});
vm.setChanged(arr.length - 1);
}
d += (1000 * 3600 * 24);// adds one day
}
});
}
//Delete all bound recurrences
$scope.deleteAllRecurrences = function (recid) {
ngDialog.openConfirm({
template:
'
This will delete every found recurrence. Are you sure?
' +
'
' +
'No ' +
'Yes' +
'
',
plain: true
}).then(function (success) {
var arr = vm.treeview.currentNode.calendar.down[0];
for (var i = 0; i < arr.length;) {
if (arr[i].rec.recid === recid)
vm.remove(i);
else
i++;
}
});
}
//Moves calendar to selected date
$scope.goToDate = function () {
$("#resourcecalendar").fullCalendar('gotoDate', vm.currentcal.down[0][vm.selectedEventId].start);
}
/* Renders Tooltip */
$scope.eventRender = function (event, element, view) {
element.attr({
'tooltip': event.title,
'tooltip-append-to-body': true
});
//$compile(element)($scope);
};
//Calendar configuration
$scope.uiConfig = {
calendar: {
height: 500,
editable: true,
defaultView: 'agendaWeek',
firstDay: 1,
header: {
left: 'title',
center: 'agendaWeek, agendaDay',
right: 'today prev,next'
},
timezone: 'UTC',
timeFormat: 'HH:mm',
eventClick: $scope.eventClick,
eventDrop: $scope.dragandDrop,
eventResize: $scope.resizeEvent,
eventRender: $scope.eventRender,
dayClick: $scope.calendarClick
}
};
}).filter('disp', function () {
return function (input) {//Filter boolean to string
return input ? 'Disposable' : 'Not disposable';
}
}).directive('animateOnChange', function ($timeout) {
return function (scope, element, attr) {//Animation on downtime info to show change
scope.$watch(attr.animateOnChange, function (nv, ov) {
if (nv != ov) {
if (scope.currentcal.down[0][scope.selectedEventId].title === "Unavailable") {
element.addClass('changed');
$timeout(function () {
element.removeClass('changed');
}, 250);
}
else {
element.addClass('changedshut');
$timeout(function () {
element.removeClass('changedshut');
}, 250);
}
}
});
};
});