/* 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?

' + '
' + ' ' + '
', 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?

' + '
' + ' ' + '
', 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?

' + '
' + ' ' + '
', 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); } } }); }; });