angular.module("FICApp", ["ngRoute", "ngResource", "ngSanitize"]) .config(function($routeProvider, $locationProvider) { $routeProvider .when("/themes", { controller: "ThemesListController", templateUrl: "views/theme-list.html" }) .when("/themes/:themeId", { controller: "ThemeController", templateUrl: "views/theme.html" }) .when("/themes/:themeId/exercices/:exerciceId", { controller: "ExerciceController", templateUrl: "views/exercice.html" }) .when("/sync", { controller: "SyncController", templateUrl: "views/sync.html" }) .when("/settings", { controller: "SettingsController", templateUrl: "views/settings.html" }) .when("/pki", { controller: "PKIController", templateUrl: "views/pki.html" }) .when("/exercices", { controller: "AllExercicesListController", templateUrl: "views/exercice-list.html" }) .when("/exercices/:exerciceId", { controller: "ExerciceController", templateUrl: "views/exercice.html" }) .when("/exercices/:exerciceId/flags", { controller: "ExerciceController", templateUrl: "views/exercice-flags.html" }) .when("/exercices/:exerciceId/resolution", { controller: "ExerciceController", templateUrl: "views/exercice-resolution.html" }) .when("/tags", { controller: "TagsListController", templateUrl: "views/tags.html" }) .when("/teams", { controller: "TeamsListController", templateUrl: "views/team-list.html" }) .when("/teams/print", { controller: "TeamsListController", templateUrl: "views/team-print.html" }) .when("/teams/export", { controller: "TeamsListController", templateUrl: "views/team-export.html" }) .when("/teams/:teamId", { controller: "TeamController", templateUrl: "views/team-edit.html" }) .when("/teams/:teamId/stats", { controller: "TeamController", templateUrl: "views/team-stats.html" }) .when("/teams/:teamId/score", { controller: "TeamController", templateUrl: "views/team-score.html" }) .when("/public/:screenId", { controller: "PublicController", templateUrl: "views/public.html" }) .when("/files", { controller: "FilesListController", templateUrl: "views/file-list.html" }) .when("/events", { controller: "EventsListController", templateUrl: "views/event-list.html" }) .when("/events/:eventId", { controller: "EventController", templateUrl: "views/event.html" }) .when("/claims", { controller: "ClaimsListController", templateUrl: "views/claim-list.html" }) .when("/claims/:claimId", { controller: "ClaimController", templateUrl: "views/claim.html" }) .when("/", { templateUrl: "views/home.html" }); $locationProvider.html5Mode(true); }); function setCookie(name, value, days) { var expires; if (days) { var date = new Date(); date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); expires = "; expires=" + date.toGMTString(); } else { expires = ""; } document.cookie = encodeURIComponent(name) + "=" + encodeURIComponent(value) + expires + "; path=/"; } function getCookie(name) { var nameEQ = encodeURIComponent(name) + "="; var ca = document.cookie.split(';'); for (var i = 0; i < ca.length; i++) { var c = ca[i]; while (c.charAt(0) === ' ') c = c.substring(1, c.length); if (c.indexOf(nameEQ) === 0) return decodeURIComponent(c.substring(nameEQ.length, c.length)); } return null; } angular.module("FICApp") .directive('autofocus', ['$timeout', function($timeout) { return { restrict: 'A', link : function($scope, $element) { $timeout(function() { $element[0].focus(); }); } } }]) .component('toast', { bindings: { date: '=', msg: '=', timeout: '=', title: '=', variant: '=', yesNo: '=', onyes: '=', onno: '=', }, controller: function($element) { if (this.timeout === 0) $element.children(0).toast({autohide: false}); else if (!this.timeout && this.timeout !== 0) $element.children(0).toast({delay: 7000}); else $element.children(0).toast({delay: this.timeout}); $element.children(0).toast('show'); $element.children(0).on('hidden.bs.toast', function() { $element.children(0).toast('dispose'); }); this.yesFunc = function() { $element.children(0).toast('hide'); if (this.onyes) this.onyes(); } this.noFunc = function() { $element.children(0).toast('hide'); if (this.onno) this.onno(); } }, template: `` }); angular.module("FICApp") .factory("Version", function($resource) { return $resource("api/version") }) .factory("Timestamp", function($resource) { return $resource("api/timestamps.json") }) .factory("Health", function($resource) { return $resource("api/health.json") }) .factory("Monitor", function($resource) { return $resource("api/monitor/:machineId", { machineId: '@id' }) }) .factory("Event", function($resource) { return $resource("api/events/:eventId", { eventId: '@id' }, { 'update': {method: 'PUT'}, }) }) .factory("Claim", function($resource) { return $resource("api/claims/:claimId", { claimId: '@id' }, { 'update': {method: 'PUT'}, }) }) .factory("ClaimAssignee", function($resource) { return $resource("api/claims-assignees/:assigneeId", { assigneeId: '@id' }, { 'update': {method: 'PUT'}, }) }) .factory("Certificate", function($resource) { return $resource("api/certs/:serial", { serial: '@id' }, { 'update': {method: 'PUT'}, }) }) .factory("CACertificate", function($resource) { return $resource("api/ca/:serial", { serial: '@id' }) }) .factory("File", function($resource) { return $resource("api/files/:fileId", { fileId: '@id' }) }) .factory("ROSettings", function($resource) { return $resource("api/settings-ro.json") }) .factory("Settings", function($resource) { return $resource("api/settings.json", null, { 'update': {method: 'PUT'}, }) }) .factory("NextSettings", function($resource) { return $resource("api/settings-next/:tsId", { tsId: '@id'}, { 'update': {method: 'PUT'}, }) }) .factory("SettingsChallenge", function($resource) { return $resource("api/challenge.json", null, { 'update': {method: 'PUT'}, }) }) .factory("Scene", function($resource) { return $resource("api/public/:screenId", { screenId: '@id' }, { 'update': {method: 'PUT'}, }) }) .factory("Team", function($resource) { return $resource("api/teams/:teamId", { teamId: '@id' }, { 'update': {method: 'PUT'}, }) }) .factory("TeamCertificate", function($resource) { return $resource("api/teams/:teamId/certificates", { teamId: '@id' }) }) .factory("TeamAssociation", function($resource) { return $resource("api/teams/:teamId/associations/:assoc", { teamId: '@teamId', assoc: '@assoc' }) }) .factory("TeamMember", function($resource) { return $resource("api/teams/:teamId/members", { teamId: '@id' }, { 'save': {method: 'PUT'}, }) }) .factory("TeamMy", function($resource) { return $resource("api/teams/:teamId/my.json", { teamId: '@id' }) }) .factory("Teams", function($resource) { return $resource("api/teams.json") }) .factory("TeamHistory", function($resource) { return $resource("api/teams/:teamId/history.json", { teamId: '@id' }) }) .factory("TeamScore", function($resource) { return $resource("api/teams/:teamId/score-grid.json", { teamId: '@id' }) }) .factory("TeamStats", function($resource) { return $resource("api/teams/:teamId/stats.json", { teamId: '@id' }) }) .factory("TeamPresence", function($resource) { return $resource("api/teams/:teamId/tries", { teamId: '@id' }) }) .factory("Theme", function($resource) { return $resource("api/themes/:themeId", { themeId: '@id' }, { update: {method: 'PUT'} }); }) .factory("Themes", function($resource) { return $resource("api/themes.json", null, { 'get': {method: 'GET'}, }) }) .factory("ThemedExercice", function($resource) { return $resource("api/themes/:themeId/exercices/:exerciceId", { themeId: '@id', exerciceId: '@idExercice' }, { update: {method: 'PUT'} }) }) .factory("Exercice", function($resource) { return $resource("api/exercices/:exerciceId", { exerciceId: '@id' }, { update: {method: 'PUT'}, patch: {method: 'PATCH'} }) }) .factory("ExerciceClaims", function($resource) { return $resource("api/exercices/:exerciceId/claims", { exerciceId: '@idExercice'}) }) .factory("ExerciceTags", function($resource) { return $resource("api/exercices/:exerciceId/tags", { exerciceId: '@idExercice'}, { update: {method: 'PUT'} }) }) .factory("ExerciceHistory", function($resource) { return $resource("api/exercices/:exerciceId/history.json", { exerciceId: '@id' }) }) .factory("ExercicesStats", function($resource) { return $resource("api/exercices_stats.json", { themeId: '@id' }) }) .factory("ExerciceStats", function($resource) { return $resource("api/exercices/:exerciceId/stats.json", { exerciceId: '@id' }) }) .factory("ExerciceFile", function($resource) { return $resource("api/exercices/:exerciceId/files/:fileId", { exerciceId: '@idExercice', fileId: '@id' }, { update: {method: 'PUT'} }) }) .factory("ExerciceHint", function($resource) { return $resource("api/exercices/:exerciceId/hints/:hintId", { exerciceId: '@idExercice', hintId: '@id' }, { update: {method: 'PUT'} }) }) .factory("ExerciceHintDeps", function($resource) { return $resource("api/exercices/:exerciceId/hints/:hintId/dependancies", { exerciceId: '@idExercice', hintId: '@id' }) }) .factory("ExerciceFlag", function($resource) { return $resource("api/exercices/:exerciceId/flags/:flagId", { exerciceId: '@idExercice', flagId: '@id' }, { update: {method: 'PUT'} }) }) .factory("ExerciceFlagChoices", function($resource) { return $resource("api/exercices/:exerciceId/flags/:flagId/choices/:choiceId", { exerciceId: '@idExercice', flagId: '@idFlag', choiceId: '@id' }, { 'update': {method: 'PUT'}, }) }) .factory("ExerciceFlagDeps", function($resource) { return $resource("api/exercices/:exerciceId/flags/:flagId/dependancies", { exerciceId: '@idExercice', flagId: '@id' }) }) .factory("ExerciceMCQFlag", function($resource) { return $resource("api/exercices/:exerciceId/quiz/:mcqId", { exerciceId: '@idExercice', mcqId: '@id' }, { update: {method: 'PUT'} }) }) .factory("ExerciceMCQDeps", function($resource) { return $resource("api/exercices/:exerciceId/quiz/:mcqId/dependancies", { exerciceId: '@idExercice', mcqId: '@id' }) }); angular.module("FICApp") .filter("countHints", function() { return function(input) { if (input == undefined) return 0; return input.reduce(function(sum, n){ return sum + (n.content || n.file) ? 1 : 0; }, 0); } }) .filter("toColor", function() { return function(num) { num >>>= 0; var b = num & 0xFF, g = (num & 0xFF00) >>> 8, r = (num & 0xFF0000) >>> 16, a = ( (num & 0xFF000000) >>> 24 ) / 255 ; return "#" + r.toString(16) + g.toString(16) + b.toString(16); } }) .filter("cksum", function() { return function(input) { if (input == undefined) return input; var raw = atob(input).toString(16); var hex = ''; for (var i = 0; i < raw.length; i++ ) { var _hex = raw.charCodeAt(i).toString(16) hex += (_hex.length == 2 ? _hex : '0' + _hex); } return hex } }) .component('dependancy', { bindings: { dep: '=', deleteDep: '=', }, controller: function() {}, template: `
  • Flag {{ $ctrl.dep.label }} QCM {{ $ctrl.dep.title }}
  • ` }) .directive('color', function() { return { require: 'ngModel', link: function(scope, ele, attr, ctrl){ ctrl.$formatters.unshift(function(num){ num >>>= 0; var b = num & 0xFF, g = (num & 0xFF00) >>> 8, r = (num & 0xFF0000) >>> 16, a = ( (num & 0xFF000000) >>> 24 ) / 255 ; return "#" + r.toString(16) + g.toString(16) + b.toString(16); }); ctrl.$parsers.unshift(function(viewValue){ var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(viewValue); return result ? ( parseInt(result[1], 16) * 256 * 256 + parseInt(result[2], 16) * 256 + parseInt(result[3], 16) ) : 0; }); } }; }) .directive('integer', function() { return { require: 'ngModel', link: function(scope, ele, attr, ctrl){ ctrl.$parsers.unshift(function(viewValue){ return parseInt(viewValue, 10); }); } }; }) .directive('float', function() { return { require: 'ngModel', link: function(scope, ele, attr, ctrl){ ctrl.$parsers.unshift(function(viewValue){ return parseFloat(viewValue, 10); }); } }; }) .run(function($rootScope, $http, $interval, Settings) { function refresh() { $http.get("api/settings.json").then(function(response) { response.data.start = new Date(response.data.start); response.data.end = new Date(response.data.end); response.data.generation = new Date(response.data.generation); $rootScope.settings = response.data; $rootScope.recvTime(response); }) } refresh(); $interval(refresh, 10000); $rootScope.toasts = []; $rootScope.addToast = function(kind, title, msg, yesFunc, noFunc, tmout) { $rootScope.toasts.unshift({ variant: kind, title: title, msg: msg, timeout: tmout, yesFunc: yesFunc, noFunc: noFunc, }); } $rootScope.staticFilesNeedUpdate = 0; $rootScope.regenerateStaticFiles = function() { Settings.get().$promise.then(function(config) { config.generation = (new Date()).toISOString(); config.$update(function() { $rootScope.staticFilesNeedUpdate = 0; $rootScope.addToast('success', "Regeneration in progress..."); }, function (response) { $rootScope.addToast('success', 'An error occurs when saving settings:', response.data.errmsg); }) }) } }) .controller("VersionController", function($scope, Version) { $scope.v = Version.get(); }) .controller("TimestampController", function($scope, $interval, Timestamp) { $scope.t = Timestamp.get(); var refresh = function() { $scope.t = Timestamp.get(); } var myinterval = $interval(refresh, 2500); $scope.$on('$destroy', function () { $interval.cancel(myinterval); }); }) .controller("HealthController", function($scope, $interval, Health) { var refresh = function() { $scope.health = Health.query(); } refresh(); var myinterval = $interval(refresh, 2500); $scope.$on('$destroy', function () { $interval.cancel(myinterval); }); }) .controller("MonitorController", function($scope, Monitor) { $scope.monitor = Monitor.get(); }) .controller("SettingsController", function($scope, $rootScope, NextSettings, Settings, SettingsChallenge, $location, $http, $interval) { $scope.nextsettings = NextSettings.query(); $scope.erase = false; $scope.editNextSettings = function(ns) { $scope.activateTime = ns.date; $scope.erase = true; Object.keys(ns.values).forEach(function(k) { $scope.config[k] = ns.values[k]; }); $scope.config.enableExerciceDepend = $scope.config.unlockedChallengeDepth >= 0; } $scope.deleteNextSettings = function(ns) { ns.$delete().then(function() { $scope.nextsettings = NextSettings.query(); }) } $scope.displayDangerousActions = false; $scope.config = Settings.get(); $scope.dist_config = {}; $scope.config.$promise.then(function(response) { $scope.dist_config = Object.assign({}, response); response.enableExerciceDepend = response.unlockedChallengeDepth >= 0; $rootScope.settings.start = new Date(response.start); $rootScope.settings.end = new Date(response.end); $rootScope.settings.generation = new Date(response.generation); $rootScope.settings.activateTime = new Date(response.activateTime); }) $scope.challenge = SettingsChallenge.get(); $scope.duration = 360; $scope.activateTime = "0001-01-01T00:00:00Z"; $scope.challenge.$promise.then(function(c) { if (c.duration) $scope.duration = c.duration; }); $scope.exerciceDependChange = function() { if ($scope.config.enableExerciceDepend) $scope.config.unlockedChallengeDepth = 0; else $scope.config.unlockedChallengeDepth = -1; }; $scope.saveChallengeInfo = function() { this.challenge.duration = $scope.duration; this.challenge.$update(function(response) { $scope.addToast('success', 'Infos du challenge mises à jour avec succès !'); }, function(response) { $scope.addToast('danger', 'An error occurs when saving challenge info:', response.data.errmsg); }); } $scope.saveSettings = function(msg) { if (msg === undefined) { msg = 'New settings saved!'; } var nStart = this.config.start; var nEnd = this.config.end; var nGen = this.config.generation; var state = this.config.enableExerciceDepend; this.config.unlockedChallengeDepth = (this.config.enableExerciceDepend?this.config.unlockedChallengeDepth:-1) var updateQuery = {}; if (this.activateTime && this.activateTime != '0001-01-01T00:00:00Z') { updateQuery['t'] = this.activateTime; } if (this.erase) { updateQuery['erase'] = true; this.erase = false; } this.config.$update(updateQuery, function(response) { $scope.dist_config = Object.assign({}, response); $scope.addToast('success', msg); $scope.nextsettings = NextSettings.query(); response.enableExerciceDepend = response.unlockedChallengeDepth >= 0; $rootScope.settings.start = new Date(nStart); $rootScope.settings.end = new Date(nEnd); $rootScope.settings.generation = new Date(nGen); }, function(response) { $scope.addToast('danger', 'An error occurs when saving settings:', response.data.errmsg); }); } $scope.regenerate = function() { this.config.generation = (new Date()).toISOString(); $rootScope.settings.generation = new Date(this.config.generation); $scope.saveSettings("Regeneration in progress..."); } $scope.launchChallenge = function() { var ts = Date.now() - Date.now() % 60000; var d = new Date(ts + 120000); this.config.start = d.toISOString(); var f = new Date(ts + 120000 + this.duration * 60000); this.config.end = f.toISOString(); $scope.addToast('info', 'Challenge ready to start,', 'propagate the changes?', function() { $scope.saveSettings(); }); } $scope.updateActivateTime = function() { $rootScope.settings.activateTime = this.activateTime; } $scope.updActivateTime = function(modulo) { var ts = Math.floor((new Date(this.config.end) - Date.now() - (60000 * modulo / 2)) / (60000 * modulo)) * (60000 * modulo); var d = new Date(this.config.end) - ts; this.activateTime = new Date(d).toISOString(); this.updateActivateTime(); } $scope.reset = function(type) { var txts = { "settings": "En validant, vous remettrez les paramètres de cette page à leur valeur initiale, y compris la date de début du challenge.", "challengeInfo": "En validant, vous effacerez les informations descriptives du challenge.", "challenges": "En validant, vous retirerez toutes les données statiques des challenges.", "teams": "En validant, vous supprimerez l'ensemble des équipes enregistreées.", "game": "En validant, vous supprimerez toutes les tentatives, les validations, ... faites par les équipes.", } $scope.addToast('warning', txts[type], 'Êtes-vous sûr de vouloir continuer ?', function() { if (confirm("Êtes-vous vraiment sûr ?\n" + txts[type])) { $http.post("api/reset", {"type": type}).then(function(time) { $scope.addToast('success', type + 'reseted'); $location.url("/"); }, function(response) { $scope.addToast('danger', 'An error occurs when reseting ' + type + ':', response.data.errmsg); }); } }); }; }) .controller("SyncController", function($scope, $rootScope, ROSettings, $location, $http, $interval) { $scope.displayDangerousActions = false; $scope.configro = ROSettings.get(); var needRefreshSyncReportWhenReady = false; var refreshSyncReport = function() { needRefreshSyncReportWhenReady = false; $http.get("full_import_report.json").then(function(response) { $scope.syncReport = response.data; }) }; refreshSyncReport() var progressInterval = $interval(function() { $http.get("api/sync/deep").then(function(response) { if (response.data.progress && response.data.progress != 255) needRefreshSyncReportWhenReady = true; else if (needRefreshSyncReportWhenReady) refreshSyncReport(); if (response.data && response.data.progress) { $scope.syncPercent = Math.floor(response.data.progress * 100 / 255); $scope.syncProgress = Math.floor(response.data.progress * 100 / 255) + " %"; } else { $scope.syncProgress = response.data; $scope.syncPercent = 0; } }, function(response) { $scope.syncPercent = 0; if (response.data && response.data.errmsg) $scope.syncProgress = response.data.errmsg; else $scope.syncProgress = response.data; }) }, 1500); $scope.$on('$destroy', function () { $interval.cancel(progressInterval); }); $scope.deepSyncInProgress = false; $scope.deepSync = function(theme) { if (theme) { question = 'Faire une synchronisation intégrale du thème ' + theme.name + ' ?' url = "api/sync/deep/" + theme.id } else { question = 'Faire une synchronisation intégrale ?' url = "api/sync/deep" } $scope.addToast('warning', question, '', function() { $scope.deepSyncInProgress = true; $http.post(url).then(function() { $scope.deepSyncInProgress = false; $scope.addToast('success', 'Synchronisation intégrale terminée.', 'Voir le rapport.', null, null, 15000); }, function(response) { $scope.deepSyncInProgress = false; $scope.addToast('warning', 'Synchronisation intégrale terminée.', 'Voir le rapport.', null, null, 15000); }); }); }; $scope.speedyDeepSync = function() { $scope.addToast('warning', 'Faire une synchronisation profonde rapide, sans s\'occuper des fichiers ?', '', function() { $scope.deepSyncInProgress = true; $http.post("api/sync/speed").then(function() { $scope.deepSyncInProgress = false; $scope.addToast('success', 'Synchronisation profonde rapide terminée.', 'Voir le rapport.', null, null, 15000); }, function(response) { $scope.deepSyncInProgress = false; $scope.addToast('warning', 'Synchronisation profinde rapide terminée.', 'Voir le rapport.', null, null, 15000); }); }); }; $scope.baseSync = function() { $scope.addToast('warning', 'Tirer les mises à jour du dépôt ?', '', function() { $scope.deepSyncInProgress = true; $http.post("api/sync/base").then(function() { $scope.deepSyncInProgress = false; $scope.addToast('success', 'Mise à jour terminée.'); }, function(response) { $scope.deepSyncInProgress = false; $scope.addToast('danger', 'Mise à jour terminée.', response.data.errmsg); }); }); }; }) .controller("PKIController", function($scope, $rootScope, Certificate, CACertificate, Team, $location, $http) { var ts = Date.now() - Date.now() % 86400000; var d = new Date(ts); var f = new Date(ts + 3 * 86400000); $scope.newca = { notAfter: f.toISOString(), notBefore: d.toISOString(), }; $scope.teams = Team.query(); $scope.certificates = Certificate.query(); $scope.ca = CACertificate.get(); $scope.revoke = function() { var targetserial = $("#revokeModal").data("certificate"); if (targetserial) { Certificate.delete({ serial: targetserial }).$promise.then( function() { $('#revokeModal').modal('hide'); $scope.certificates = Certificate.query().$promise.then(function(certificates) { certificates.forEach(function(certificate, cid) { certificate.serial = parseInt(certificate.id).toString(16); }); }); }, function(response) { $scope.addToast('danger', 'An error occurs when trying to associate certificate:', response.data.errmsg); } ); } }; $scope.validateSearch = function(keyEvent) { if (keyEvent.which === 13) { var myCertificate = null; $scope.certificates.forEach(function(certificate) { if (String(certificate.id).indexOf($scope.query.toUpperCase()) >= 0) { if (myCertificate === null) myCertificate = certificate; else myCertificate = false; } }); if (myCertificate && myCertificate.id_team == null) { $('#associationModal').data('certificate', myCertificate.id) $('#associationModal').modal() } } }; $scope.validatePKIForm = function(keyEvent) { if (keyEvent.which === 13) $scope.associate() }; $scope.associate = function() { var targetserial = $("#associationModal").data("certificate"); if (!targetserial) return; Certificate.update({ serial: targetserial }, { id_team: $scope.selectedTeam }).$promise.then( function() { $('#associationModal').modal('hide'); $scope.certificates = Certificate.query(); $scope.selectedTeam = null; }, function(response) { $scope.addToast('danger', 'An error occurs when trying to associate certificate:', response.data.errmsg); } ); }; $scope.generateCA = function() { $http.post("api/ca/new", $scope.newca).then(function() { $scope.ca = CACertificate.get(); }, function(response) { $scope.addToast('danger', 'An error occurs when generating CA:', response.data.errmsg); }); }; $scope.renewCA = function() { $scope.ca = {}; }; $scope.generateCert = function() { $http.post("api/certs").then(function() { $scope.certificates = Certificate.query(); }, function(response) { $scope.addToast('danger', 'An error occurs when generating certificate:', response.data.errmsg); }); }; $scope.generateHtpasswd = function() { $http.post("api/htpasswd").then(function() { $scope.addToast('success', 'Fichier htpasswd généré avec succès'); }, function(response) { $scope.addToast('danger', 'An error occurs when generating htpasswd file:', response.data.errmsg); }); }; $scope.removeHtpasswd = function() { $http.delete("api/htpasswd").then(function() { $scope.addToast('success', 'Fichier htpasswd supprimé avec succès'); }, function(response) { $scope.addToast('danger', 'An error occurs when deleting htpasswd file:', response.data.errmsg); }); }; }) .controller("PublicController", function($scope, $rootScope, $routeParams, $location, Scene, Theme, Teams, Exercice) { $scope.screens = [0,1,2,3,4,5,6,7,8,9]; $scope.screenid = $routeParams.screenId; $scope.display = Scene.get({ screenId: $routeParams.screenId }); $scope.themes = Theme.query(); $scope.teams = Teams.get(); $scope.chScreen = function(sid) { if ($scope.screenid) $location.url("/public/" + $scope.screenid); else $location.url("/public/"); } $scope.types = { "welcome": "Messages de bienvenue", "countdown": "Compte à rebours", "message": "Message", "panel": "Boîte", "carousel": "Carousel", "exercice": "Exercice", "table": "Tableau", "rank": "Classement", "graph": "Graphique", }; $scope.typeside = { "welcome": "Messages de bienvenue", "themes": "Présentation des thèmes", "exercice_follow": "Dernier exercice des événements", "exercice": "Exercice", "rank": "Classement", "graph": "Graphique", "message": "Message", "panel": "Boîte", }; $scope.welcome_types = { "teams": "Accueil des équipes", "public": "Accueil du public", }; $scope.carousel_types = { "exercices": "Exercices", "teams": "Équipes", "themes": "Thèmes", "ranking": "Classement", }; $scope.colors = { "primary": "Primaire", "secondary": "Secondaire", "info": "Info", "success": "Success", "warning": "Warning", "danger": "Danger", "light": "Clair", "dark": "Foncé", }; $scope.rank_types = { "general": "Classement général", "final": "Classement final", }; $scope.rank_types_side = { "carousel": "Classement général carousel", "general": "Classement général", }; $scope.table_types = { "levels": "Niveaux d'exercices", "teams": "Équipes", }; $scope.exercices = Exercice.query(); $scope.clearScene = function() { $scope.someUpdt = true; $scope.display.scenes = []; }; $scope.presetScene = function(scene) { $scope.someUpdt = true; if (scene == "registration") { $scope.display.scenes = [ { type: "welcome", params: { kind: "teams", url: "https://fic.srs.epita.fr/" }, }, { type: "welcome", params: { kind: "public", notitle: true }, }, ]; $scope.display.side = [ { type: "themes", params: { }, }, ]; } else if (scene == "welcome") { $scope.display.scenes = [ { type: "carousel", params: { color: "info", kind: "themes", title: "Présentation des entreprises ciblées"}, }, ]; $scope.display.side = [ { type: "welcome", params: { kind: "public" }, }, ]; } else if (scene == "start") { $scope.display.scenes = [ { type: "welcome", params: { kind: "public" }, }, { type: "countdown", params: { color: "success", end: null, lead: "Go, go, go !", title: "Le challenge forensic va bientôt commencer !" }, }, ]; $scope.display.side = [ { type: "themes", params: { }, }, ]; } else if (scene == "end") { $scope.display.scenes = [ { type: "rank", params: { which: "final" }, }, { type: "table", params: { kind: "teams", themes: $scope.themes.map(function(z, i) { return z.id; }), total: true, teams: [] }, }, ]; angular.forEach($scope.teams, function(team, tid) { if (team.rank >= 1 && team.rank <= 4) $scope.display.scenes[1].params.teams.push(tid) }); $scope.display.side = [ { type: "rank", params: { which: "carousel" }, }, { type: "graph", params: { teams: [], height: 400, legend: false, hide: true }, }, { type: "message", params: { html: '
    Epita
    Réserves de cyberdéfense
    ', hide: true }, }, ]; angular.forEach($scope.teams, function(team, tid) { if (team.rank >= 1 && team.rank <= 9) $scope.display.side[1].params.teams.push(tid) }); $scope.display.hideEvents = true; $scope.display.hideCountdown = true; } else if (scene == "summary") { $scope.display.scenes = [ { type: "table", params: { kind: "levels", levels: [1,2,3,4,5], themes: $scope.themes.map(function(z, i) { return z.id; }), total: true }, }, { type: "graph", params: { teams: [], height: 337, legend: true }, }, ]; angular.forEach($scope.teams, function(team, tid) { if (team.rank >= 1 && team.rank <= 9) $scope.display.scenes[1].params.teams.push(tid) }); $scope.display.side = [ { type: "exercice_follow", params: { }, }, ]; $scope.display.hideEvents = false; $scope.display.hideCountdown = false; } else if (scene == "summary2") { $scope.display.scenes = [ { type: "graph", params: { teams: [], height: 400, legend: false }, }, { type: "rank", params: { limit: 10, which: "general", legend: true }, }, ]; angular.forEach($scope.teams, function(team, tid) { if (team.rank >= 1 && team.rank <= 9) $scope.display.scenes[0].params.teams.push(tid) }); $scope.display.side = [ { type: "exercice_follow", params: { }, }, ]; $scope.display.hideEvents = false; $scope.display.hideCountdown = false; } else if (scene == "summary3") { $scope.display.scenes = [ { type: "table", params: { kind: "levels", levels: [1,2,3,4,5], themes: $scope.themes.map(function(z, i) { return z.id; }), total: true }, }, { type: "rank", params: { limit: 10, which: "general", legend: false }, }, ]; $scope.display.side = [ { type: "exercice_follow", params: { }, }, ]; $scope.display.hideEvents = false; $scope.display.hideCountdown = false; } else if (scene == "happyhour") { $scope.display.customCountdown = { show: true, shadow: "#E8CF5C", end: new Date(Date.now() + 1802000).toISOString(), before: "Heure joyeuse : chaque résolution compte double !", after: "Heure joyeuse terminée !", } } else if (scene == "freehintquarter") { $scope.display.customCountdown = { show: true, shadow: "#3DD28F", end: new Date(Date.now() + 902000).toISOString(), before: "Quart d'heure facile : indices dévoilés !", after: "Quart d'heure facile terminée !", } } }; $scope.genSceneCountdownDate = function(scene, duration) { scene.params.end = (new Date(Date.now() + duration)).toISOString(); } $scope.genCustomCountdownDate = function(duration) { if (duration == null) { $scope.display.customCountdown.end = $rootScope.settings.activateTime; } else { $scope.display.customCountdown.end = (new Date(Date.now() + duration)).toISOString(); } } $scope.saveScenes = function() { $scope.someUpdt = false; var prms = Scene.update({ screenId: $scope.screenid }, $scope.display); prms.$promise.then(function() { $scope.addToast('success', 'Scene successfully published!'); }, function(response) { $scope.addToast('danger', 'An error occurs when saving scene:', response.data.errmsg); }); }; $scope.addSide = function() { $scope.someUpdt = true; $scope.display.side.push({params: {}}); }; $scope.delSide = function(s) { $scope.someUpdt = true; angular.forEach($scope.display.side, function(scene, k) { if (scene == s) $scope.display.side.splice(k, 1); }); }; $scope.upSide = function(s) { $scope.someUpdt = true; angular.forEach($scope.display.side, function(scene, k) { if (scene == s && k > 0) { $scope.display.side.splice(k, 1); $scope.display.side.splice(k - 1, 0, scene); } }); }; $scope.downSide = function(s) { $scope.someUpdt = true; var move = true; angular.forEach($scope.display.side, function(scene, k) { if (move && scene == s) { $scope.display.side.splice(k, 1); $scope.display.side.splice(k + 1, 0, scene); move = false; } }); }; $scope.addScene = function() { $scope.someUpdt = true; $scope.display.scenes.push({params: {}}); }; $scope.delScene = function(s) { $scope.someUpdt = true; angular.forEach($scope.display.scenes, function(scene, k) { if (scene == s) $scope.display.scenes.splice(k, 1); }); }; $scope.upScene = function(s) { $scope.someUpdt = true; angular.forEach($scope.display.scenes, function(scene, k) { if (scene == s && k > 0) { $scope.display.scenes.splice(k, 1); $scope.display.scenes.splice(k - 1, 0, scene); } }); }; $scope.downScene = function(s) { $scope.someUpdt = true; var move = true; angular.forEach($scope.display.scenes, function(scene, k) { if (move && scene == s) { $scope.display.scenes.splice(k, 1); $scope.display.scenes.splice(k + 1, 0, scene); move = false; } }); }; }) .controller("FilesListController", function($scope, File, $location, $http, $rootScope) { $scope.files = File.query(); $scope.errfnd = 0; $scope.fields = ["id", "path", "name", "checksum", "size"]; $scope.clearFiles = function(id) { File.delete(function() { $rootScope.staticFilesNeedUpdate++; $scope.files = []; }); }; $scope.checksum = function(f) { $http({ url: "api/files/" + f.id + "/check", method: "POST" }).then(function(response) { f.err = true; }, function(response) { $scope.inSync = false; $scope.errfnd += 1; f.err = response.data.errmsg; }) }; $scope.checksumAll = function() { $scope.errfnd = 0; angular.forEach($scope.files, function(file) { $scope.checksum(file); }); }; $scope.show = function(f) { $location.url("/exercices/" + f.idExercice); }; }) .controller("EventsListController", function($scope, Event, $location) { $scope.events = Event.query(); $scope.fields = ["id", "kind", "txt", "time"]; $scope.clearEvents = function(id) { Event.delete(function() { $scope.events = []; }); }; $scope.show = function(id) { $location.url("/events/" + id); }; }) .controller("EventController", function($scope, Event, $routeParams, $location) { $scope.event = Event.get({ eventId: $routeParams.eventId }); $scope.fields = ["kind", "txt", "time"]; $scope.kinds = { "secondary": "Par défaut", "primary": "Mise en valeur", "info": "Info", "warning": "Warning", "success": "Success", "danger": "Danger", "light": "Clair", "dark": "Foncé", }; $scope.saveEvent = function() { if (this.event.id) { this.event.$update(); } else { this.event.$save(function() { $location.url("/events/" + $scope.event.id); }); } } $scope.deleteEvent = function() { this.event.$remove(function() { $location.url("/events/");}); } }) .controller("AssigneesListController", function($scope, ClaimAssignee, $location) { $scope.assignees = ClaimAssignee.query(); $scope.setMyAId = function(aid) { setCookie("myassignee", aid, 5); $location.url("/claims/"); } $scope.whoami = getCookie("myassignee"); $scope.newAssignee = function() { $scope.assignees.push(new ClaimAssignee()); } $scope.edit = function(a) { a.edit = true; } $scope.updateAssignee = function(a) { if (a.id) { a.$update(function() { $location.url("/claims/");}); } else { a.$save() } } $scope.removeAssignee = function(a) { a.$remove(function() { $location.url("/claims/");}); } }) .controller("ClaimsTinyListController", function($scope, Claim, ClaimAssignee, $interval) { $scope.whoami = getCookie("myassignee"); var priorities = { "low": 1, "medium": 2, "high": 3, "critical": 4, }; $scope.priorities = [ "secondary", "light", "info", "warning", "danger", ]; var refresh = function() { Claim.query().$promise.then(function(claims) { $scope.newClaims = 0; $scope.newClaimsMaxLevel = 0; $scope.myClaims = 0; $scope.myClaimsMaxLevel = 0; claims.forEach(function(claim, cid) { if ($scope.whoami && !claim.id_assignee && claim.state == 'new') { $scope.newClaims++; if (priorities[claim.priority] > $scope.newClaimsMaxLevel) $scope.newClaimsMaxLevel = priorities[claim.priority]; } else if ($scope.whoami && claim.id_assignee == $scope.whoami && claim.state != 'closed' && claim.state != 'invalid') { $scope.myClaims++; if (claim.state == 'new' && priorities[claim.priority] > $scope.myClaimsMaxLevel) $scope.myClaimsMaxLevel = priorities[claim.priority]; } }) }); }; refresh(); $interval(refresh, 10000); }) .controller("ClaimsListController", function($scope, Claim, ClaimAssignee, Teams, $interval, $location) { var refresh = function() { $scope.claims = Claim.query(); $scope.assignees = ClaimAssignee.query(); } refresh(); var myInterval = $interval(refresh, 10000); $scope.$on('$destroy', function () { $interval.cancel(myinterval); }); $scope.whoami = getCookie("myassignee"); $scope.teams = Teams.get(); $scope.fields = ["subject", "id_team", "state", "id_assignee", "last_update", "id"]; $scope.order = "priority"; $scope.chOrder = function(no) { $scope.order = no; }; $scope.clearClaims = function(id) { Claim.delete(function() { $scope.claims = []; }); }; $scope.show = function(id) { $location.url("/claims/" + id); }; }) .controller("ClaimLastUpdateController", function($scope, $http) { $scope.init = function(claim) { $http.get("api/claims/" + claim.id + "/last_update").then(function(response) { if (response.data) $scope.last_update = response.data; else $scope.last_update = claim.creation; claim.last_update = $scope.last_update; }, function(response) { $scope.last_update = claim.creation; }) } }) .controller("ClaimController", function($scope, Claim, ClaimAssignee, Teams, Exercice, $routeParams, $location, $http, $rootScope) { $scope.claim = Claim.get({ claimId: $routeParams.claimId }, function(v) { v.id_team = "" + v.id_team; if (!v.priority) v.priority = "medium"; }); if ($routeParams.claimId == "new") $scope.fields = ["id_team", "id_exercice", "subject", "priority", "id_assignee"]; else $scope.fields = ["subject", "priority", "id_exercice", "id_assignee", "id_team", "creation", "state"]; $scope.assignees = ClaimAssignee.query(); $scope.comm = { ndescription: ""}; $scope.whoami = Math.floor(getCookie("myassignee")); $scope.teams = Teams.get(); $scope.exercices = Exercice.query(); $scope.namedFields = { "subject": "Objet", "id_assignee": "Assigné à", "state": "État", "id_team": "Équipe", "id_exercice": "Challenge", "creation": "Création", "priority": "Priorité", "description": "Description", }; $scope.states = { "new": "Nouveau", "need-info": "Besoin d'infos", "confirmed": "Confirmé", "in-progress": "En cours", "need-review": "Fait", "closed": "Clos", "invalid": "Invalide", }; $scope.priorities = { "low": "Basse", "medium": "Moyenne", "high": "Haute", "critical": "Critique", }; $scope.changeState = function(state) { this.claim.state = state; if ((state == "in-progress" || state == "invalid") && this.claim.id_assignee) this.claim.id_assignee = $scope.whoami; if (this.claim.id) this.saveClaim(state == "invalid" || state == "closed"); } $scope.assignToMe = function() { this.claim.id_assignee = $scope.whoami; if (this.claim.id) this.saveClaim(false); } $scope.updateDescription = function(description) { $http({ url: "api/claims/" + $scope.claim.id + "/descriptions", method: "PUT", data: description }).then(function(response) { $scope.claim = Claim.get({ claimId: $routeParams.claimId }, function(v) { v.id_team = "" + v.id_team; if (!v.priority) v.priority = "medium"; }); }); } $scope.saveDescription = function() { $http({ url: "api/claims/" + $scope.claim.id, method: "POST", data: { "id_assignee": $scope.whoami, "content": $scope.comm.ndescription } }).then(function(response) { $location.url("/claims/" + $scope.claim.id + "/"); }); } $scope.saveClaim = function(backToList) { this.claim.whoami = $scope.whoami; if (this.claim.id_team) { this.claim.id_team = parseInt(this.claim.id_team, 10); } else { this.claim.id_team = null; } if (this.claim.id) { this.claim.$update(function(v) { v.id_team = "" + v.id_team; if ($scope.comm.ndescription) $scope.saveDescription(); else if (backToList) $location.url("/claims/"); else $scope.claim = Claim.get({ claimId: $routeParams.claimId }, function(v) { v.id_team = "" + v.id_team; if (!v.priority) v.priority = "medium"; }); }); } else { this.claim.$save(function() { if (!$scope.comm.ndescription) $scope.comm.ndescription = "Création de la tâche"; $scope.saveDescription(); }, function(response) { $scope.addToast('danger', 'An error occurs when trying to save claim:', response.data.errmsg); }); } } $scope.deleteClaim = function() { this.claim.$remove(function() { $location.url("/claims/");}); } }) .controller("ThemesListController", function($scope, Theme, $location, $rootScope, $http) { $scope.themes = Theme.query(); $scope.fields = ["name", "authors", "headline", "path"]; $scope.validateSearch = function(keyEvent) { if (keyEvent.which === 13) { var myTheme = null; $scope.themes.forEach(function(theme) { if (String(theme.name.toLowerCase()).indexOf($scope.query.toLowerCase()) >= 0) { if (myTheme === null) myTheme = theme; else myTheme = false; } }); if (myTheme) $location.url("themes/" + myTheme.id); } }; $scope.show = function(id) { $location.url("/themes/" + id); }; $scope.inSync = false; $scope.sync = function() { $scope.inSync = true; $http({ url: "api/sync/themes", method: "POST" }).then(function(response) { $scope.inSync = false; $scope.themes = Theme.query(); $rootScope.staticFilesNeedUpdate++; if (response.data) $scope.addToast('danger', 'An error occurs when synchronizing theme list:', response.data); else $scope.addToast('success', 'Synchronisation de la liste des thèmes terminée avec succès.'); }, function(response) { $scope.inSync = false; $scope.addToast('danger', 'An error occurs when synchronizing theme list:', response.data.errmsg); }); }; }) .controller("ThemeController", function($scope, Theme, $routeParams, $location, $rootScope, $http) { $scope.theme = Theme.get({ themeId: $routeParams.themeId }); $scope.fields = ["name", "urlid", "authors", "headline", "intro", "image", "partner_txt", "partner_href", "partner_img"]; $scope.saveTheme = function() { if (this.theme.id) { this.theme.$update(); } else { this.theme.$save(function() { $location.url("/themes/" + $scope.theme.id); }); } $rootScope.staticFilesNeedUpdate++; } $scope.deleteTheme = function() { this.theme.$remove(function() { $rootScope.staticFilesNeedUpdate++; $location.url("/themes/"); }, function(response) { $scope.addToast('danger', 'An error occurs when trying to delete theme:', response.data.errmsg); }); } }) .controller("TagsListController", function($scope, $http) { $scope.tags = []; $http({ url: "api/tags", method: "GET" }).then(function(response) { $scope.tags = response.data }); }) .controller("AllExercicesListController", function($scope, Exercice, Theme, $routeParams, $location, $rootScope, $http, $filter) { $http({ url: "api/themes.json", method: "GET" }).then(function(response) { $scope.themes = response.data }); $scope.exercices = Exercice.query(); $scope.exercice = {}; // Array used to save fields to updates in selected exercices $scope.fields = ["title", "headline"]; $scope.validateSearch = function(keyEvent) { if (keyEvent.which === 13) { var myExercice = null; $scope.exercices.forEach(function(exercice) { if (String(exercice.title.toLowerCase()).indexOf($scope.query.toLowerCase()) >= 0) { if (myExercice === null) myExercice = exercice; else myExercice = false; } }); if (myExercice) $location.url("exercices/" + myExercice.id); } }; $scope.toggleSelectAll = function() { angular.forEach($filter('filter')($scope.exercices, $scope.query), function(ex) { ex.selected = !$scope.selectall }) } $scope.updateExercices = function() { angular.forEach($scope.exercices, function(ex) { if (ex.selected) { Exercice.patch({exerciceId: ex.id}, $scope.exercice); } }) $scope.exercice = {}; $rootScope.staticFilesNeedUpdate++; $scope.addToast('success', 'Édition de masse terminée avec succès'); } $scope.show = function(id) { $location.url("/exercices/" + id); }; $scope.inSync = false; $scope.syncFull = function() { $scope.inSync = true; $scope.done = -1; $scope.total = 0; var work = []; var go = function() { if (!work.length) { $scope.addToast('info', "Synchronisation des exercices terminée."); $scope.inSync = false; return; } var u = work.pop(); $http({ url: u, method: "GET" }).then(function(response) { $rootScope.staticFilesNeedUpdate++; $scope.done += 1; go(); }, function(response) { $scope.done += 1; go(); }); }; angular.forEach($scope.exercices, function(ex) { if ($scope.syncFiles) work.push("api/sync/exercices/" + ex.id + "/files"); if ($scope.syncHints) work.push("api/sync/exercices/" + ex.id + "/hints"); if ($scope.syncFlags) work.push("api/sync/exercices/" + ex.id + "/flags"); }); $scope.total = work.length; go(); }; $scope.syncFiles = true; $scope.syncHints = true; $scope.syncFlags = true; }) .controller("ExercicesListController", function($scope, ThemedExercice, $location, $rootScope, $http) { $scope.exercices = ThemedExercice.query({ themeId: $scope.theme.id }); $scope.fields = ["title", "headline", "issue"]; $scope.show = function(id) { $location.url("/themes/" + $scope.theme.id + "/exercices/" + id); }; $scope.inSync = false; $scope.syncExo = function() { $scope.inSync = true; $http({ url: "api/sync/themes/" + $scope.theme.id + "/exercices", method: "POST" }).then(function(response) { $scope.inSync = false; $scope.exercices = ThemedExercice.query({ themeId: $scope.theme.id }); $rootScope.staticFilesNeedUpdate++; if (response.data) $scope.addToast('warning', 'An error occurs when synchrinizing exercices:', response.data); else $scope.addToast('success', 'Synchronisation de la liste des exercices terminée avec succès.'); }, function(response) { $scope.inSync = false; $scope.addToast('danger', 'An error occurs when synchrinizing exercices:', response.data.errmsg); }); }; }) .controller("ExerciceController", function($scope, $rootScope, Exercice, ThemedExercice, $routeParams, $location, $http) { if ($routeParams.themeId && $routeParams.exerciceId == "new") { $scope.exercice = new ThemedExercice(); } else { $scope.exercice = Exercice.get({ exerciceId: $routeParams.exerciceId }); } $http({ url: "api/themes.json", method: "GET" }).then(function(response) { $scope.themes = response.data var last_exercice = null; angular.forEach($scope.themes[$scope.exercice.id_theme].exercices, function(exercice, k) { if (last_exercice != null) { $scope.themes[$scope.exercice.id_theme].exercices[last_exercice].next = k; exercice.previous = last_exercice; } last_exercice = k; exercice.id = k; }); }); $scope.exercices = Exercice.query(); $scope.fields = ["title", "urlid", "statement", "headline", "overview", "finished", "depend", "gain", "coefficient", "videoURI", "resolution", "issue", "issuekind"]; $scope.inSync = false; $scope.syncExo = function() { $scope.inSync = true; $http({ url: "api/sync/themes/" + $scope.exercice.id_theme + "/exercices/" + $routeParams.exerciceId, method: "POST" }).then(function(response) { $scope.inSync = false; $scope.exercice = Exercice.get({ exerciceId: $routeParams.exerciceId }); $rootScope.staticFilesNeedUpdate++; if (response.data) $scope.addToast('danger', 'An error occurs when synchronizing exercice:', response.data); else $scope.addToast('success', "Synchronisation de l'exercice terminée avec succès."); }, function(response) { $scope.inSync = false; $scope.addToast('danger', 'An error occurs when synchronizing exercice:', response.data.errmsg); }); }; $scope.deleteExercice = function() { var tid = $scope.exercice.id_theme; this.exercice.$remove(function() { $rootScope.staticFilesNeedUpdate++; $location.url("/themes/" + tid); }, function(response) { $scope.addToast('danger', 'An error occurs when trying to delete exercice:', response.data.errmsg); }); } $scope.saveExercice = function() { if (this.exercice.id) { this.exercice.$update(); $rootScope.staticFilesNeedUpdate++; } else if ($routeParams.themeId) { this.exercice.$save({ themeId: $routeParams.themeId }, function() { $rootScope.staticFilesNeedUpdate++; $location.url("/themes/" + $scope.exercice.idTheme + "/exercices/" + $scope.exercice.id); }, function(response) { $scope.addToast('danger', 'An error occurs when trying to save exercice:', response.data.errmsg); }); } } }) .controller("ExercicesStatsController", function($scope, ExercicesStats) { $scope.exercices = ExercicesStats.query(); }) .controller("ExerciceStatsController", function($scope, ExerciceStats, $routeParams) { $scope.stats = ExerciceStats.get({ exerciceId: $routeParams.exerciceId }); }) .controller("ExerciceClaimsController", function($scope, ExerciceClaims, Team, ClaimAssignee, $routeParams, $location) { $scope.claims = ExerciceClaims.query({ exerciceId: $routeParams.exerciceId }); $scope.assignees = ClaimAssignee.query(); $scope.claims.$promise.then(function(claims) { claims.forEach(function(claim, cid) { $scope.claims[cid].team = Team.get({ teamId: claim.id_team }); }) }); $scope.showClosed = false; $scope.show = function(id) { $location.url("/claims/" + id); }; }) .controller("ExerciceTagsController", function($scope, ExerciceTags, $routeParams, $rootScope) { $scope.tags = ExerciceTags.query({ exerciceId: $routeParams.exerciceId }); $scope.addTag = function() { $scope.tags.push(""); } $scope.deleteTag = function() { $scope.tags.splice($scope.tags.indexOf(this.tag), 1); return $scope.saveTags(); } $scope.saveTags = function() { ExerciceTags.update({ exerciceId: $routeParams.exerciceId }, this.tags); $rootScope.staticFilesNeedUpdate++; } }) .controller("ExerciceHistoryController", function($scope, ExerciceHistory, $routeParams, $http, $rootScope) { $scope.history = ExerciceHistory.query({ exerciceId: $routeParams.exerciceId }); $scope.updHistory = function() { var target = { team_id: $("#updHistory").data("idteam"), kind: $("#updHistory").data("kind"), time: $("#updHistory").data("time"), secondary: $("#updHistory").data("secondary") != "" ? $("#updHistory").data("secondary") : null, coeff: parseFloat($('#historycoeff').val()), }; if (target) { $http({ url: "api/exercices/" + $routeParams.exerciceId + "/history.json", method: "PATCH", data: target }).then(function(response) { $rootScope.staticFilesNeedUpdate++; $scope.history = ExerciceHistory.query({ exerciceId: $routeParams.exerciceId }); $('#updHistory').modal('hide'); }, function(response) { $scope.addToast('danger', 'An error occurs when updating history item: ', response.data.errmsg); }); } } $scope.delHistory = function(row) { $http({ url: "api/exercices/" + $routeParams.exerciceId + "/history.json", method: "DELETE", data: row }).then(function(response) { $rootScope.staticFilesNeedUpdate++; $scope.history = ExerciceHistory.query({ exerciceId: $routeParams.exerciceId }); }, function(response) { $scope.addToast('danger', 'An error occurs when removing history item: ', response.data.errmsg); }); } }) .controller("ExerciceFilesController", function($scope, ExerciceFile, $routeParams, $rootScope, $http) { $scope.files = ExerciceFile.query({ exerciceId: $routeParams.exerciceId }); $scope.deleteFile = function() { this.file.$delete(function() { $scope.files = ExerciceFile.query({ exerciceId: $routeParams.exerciceId }); }); $rootScope.staticFilesNeedUpdate++; return false; } $scope.deleteFileDep = function() { $http({ url: "api//files/" + this.file.id + "/dependancies/" + this.dep.id, method: "DELETE" }).then(function(response) { $scope.files = ExerciceFile.query({ exerciceId: $routeParams.exerciceId }); }, function(response) { $scope.addToast('danger', 'An error occurs when removing file dep:', response.data.errmsg); }); $rootScope.staticFilesNeedUpdate++; return false; } $scope.saveFile = function() { this.file.$update(); $rootScope.staticFilesNeedUpdate++; } $scope.inSync = false; $scope.syncFiles = function() { $scope.inSync = true; $http({ url: "api/sync/exercices/" + $routeParams.exerciceId + "/files", method: "POST" }).then(function(response) { $scope.inSync = false; $rootScope.staticFilesNeedUpdate++; $scope.files = ExerciceFile.query({ exerciceId: $routeParams.exerciceId }); if (response.data) $scope.addToast('danger', 'An error occurs when synchronizing flags list:', response.data); else $scope.addToast('success', "Synchronisation de la liste de fichiers terminée avec succès."); }, function(response) { $scope.inSync = false; $scope.addToast('danger', 'An error occurs when synchronizing flags list:', response.data.errmsg); }); }; }) .controller("ExerciceHintsController", function($scope, ExerciceHint, $routeParams, $rootScope, $http) { $scope.hints = ExerciceHint.query({ exerciceId: $routeParams.exerciceId }); $scope.addHint = function() { $scope.hints.push(new ExerciceHint()); } $scope.deleteHint = function() { this.hint.$delete(function() { $scope.hints = ExerciceHint.query({ exerciceId: $routeParams.exerciceId }); $rootScope.staticFilesNeedUpdate++; }, function(response) { $scope.addToast('danger', 'An error occurs when trying to delete hint:', response.data.errmsg); }); } $scope.saveHint = function() { if (this.hint.id) { this.hint.$update(); } else { this.hint.$save({ exerciceId: $routeParams.exerciceId }); } $rootScope.staticFilesNeedUpdate++; } $scope.inSync = false; $scope.syncHints = function() { $scope.inSync = true; $http({ url: "api/sync/exercices/" + $routeParams.exerciceId + "/hints", method: "POST" }).then(function(response) { $scope.inSync = false; $scope.hints = ExerciceHint.query({ exerciceId: $routeParams.exerciceId }); $rootScope.staticFilesNeedUpdate++; if (response.data) $scope.addToast('danger', 'An error occurs when synchronizing hints list:', response.data); else $scope.addToast('success', "Synchronisation de la liste d'indices terminée avec succès."); }, function(response) { $scope.inSync = false; $scope.addToast('danger', 'An error occurs when synchronizing hints list:', response.data.errmsg); }); }; }) .controller("ExerciceHintDepsController", function($scope, $routeParams, ExerciceHintDeps) { $scope.init = function(hint) { $scope.deps = ExerciceHintDeps.query({ exerciceId: $routeParams.exerciceId, hintId: hint.id }); } }) .controller("ExerciceFlagsController", function($scope, ExerciceFlag, $routeParams, $rootScope, $http) { $scope.flags = ExerciceFlag.query({ exerciceId: $routeParams.exerciceId }); $scope.flags.$promise.then(function(flags) { flags.forEach(function(flag, fid) { flags[fid].values = ['']; }); }); $scope.changeValue = function(flag) { flag.value = undefined; flag.show_raw = true; } $scope.addFlag = function() { $scope.flags.push(new ExerciceFlag()); } $scope.deleteFlag = function() { this.flag.$delete(function() { $scope.flags = ExerciceFlag.query({ exerciceId: $routeParams.exerciceId }); $rootScope.staticFilesNeedUpdate++; }, function(response) { $scope.addToast('danger', 'An error occurs when trying to delete flag:', response.data.errmsg); }); } $scope.saveFlag = function() { if (this.flag.id) { this.flag.$update(); } else { this.flag.$save({ exerciceId: $routeParams.exerciceId }); } $rootScope.staticFilesNeedUpdate++; } $scope.testFlag = function(flag) { if (flag.values) { var val = flag.value; treatFlagKey(flag); flag.test_str = flag.value; flag.value = val; } if (flag.test_str) { $http({ url: "api/exercices/" + $routeParams.exerciceId + "/flags/" + flag.id + "/try", data: {"flag": flag.test_str}, method: "POST" }).then(function(response) { flag.test_str = ""; $scope.addToast('success', "Flag Ok !"); }, function(response) { flag.test_str = ""; $scope.addToast('danger', 'An error occurs: ', response.data.errmsg); }); } } $scope.inSync = false; $scope.syncFlags = function() { $scope.inSync = true; $http({ url: "api/sync/exercices/" + $routeParams.exerciceId + "/flags", method: "POST" }).then(function(response) { $scope.inSync = false; $scope.flags = ExerciceFlag.query({ exerciceId: $routeParams.exerciceId }); $rootScope.staticFilesNeedUpdate++; if (response.data) $scope.addToast('danger', 'An error occurs when synchronizing flags list:', response.data); else $scope.addToast('success', "Synchronisation de la liste de drapeaux terminée avec succès."); }, function(response) { $scope.inSync = false; $scope.addToast('danger', 'An error occurs when synchronizing flags list:', response.data.errmsg); }); }; }) .controller("ExerciceFlagChoicesController", function($scope, ExerciceFlagChoices, $routeParams) { $scope.choices = ExerciceFlagChoices.query({ exerciceId: $routeParams.exerciceId, flagId: $scope.flag.id }) $scope.flag.wantchoices = function() { $scope.flag.choices = {}; $scope.choices.forEach(function(choice) { $scope.flag.choices[choice.value] = choice.label }) } $scope.choices.$promise.then(function(choices) { if ($scope.flag.choices_cost == 0 && choices.length > 0) { $scope.flag.wantchoices() } }) $scope.addChoice = function() { $scope.choices.push(new ExerciceFlagChoices()) } $scope.saveChoice = function() { if (this.choice.id) this.choice.$update({exerciceId: $routeParams.exerciceId, flagId: this.flag.id}) else this.choice.$save({exerciceId: $routeParams.exerciceId, flagId: this.flag.id}) } $scope.deleteChoice = function() { if (this.choice.id) this.choice.$delete({exerciceId: $routeParams.exerciceId, flagId: this.flag.id}, function() { $scope.choices = ExerciceFlagChoices.query({ exerciceId: $routeParams.exerciceId, flagId: $scope.flag.id }) }) } }) .controller("ExerciceFlagDepsController", function($scope, $routeParams, ExerciceFlagDeps) { $scope.init = function(flag) { $scope.deps = ExerciceFlagDeps.query({ exerciceId: $routeParams.exerciceId, flagId: flag.id }); } }) .controller("ExerciceMCQFlagsController", function($scope, ExerciceMCQFlag, $routeParams, $rootScope) { $scope.quiz = ExerciceMCQFlag.query({ exerciceId: $routeParams.exerciceId }); $scope.addQuiz = function() { $scope.quiz.push(new ExerciceMCQFlag()); } $scope.deleteQuiz = function() { this.q.$delete(function() { $scope.quiz = ExerciceMCQFlag.query({ exerciceId: $routeParams.exerciceId }); $rootScope.staticFilesNeedUpdate++; }, function(response) { $scope.addToast('danger', 'An error occurs when trying to delete flag:', response.data.errmsg); }); } $scope.saveQuiz = function() { if (this.q.id) { this.q.$update(); } else { this.q.$save({ exerciceId: $routeParams.exerciceId }); } $rootScope.staticFilesNeedUpdate++; } $scope.addChoice = function() { this.quiz[this.qk].entries.push({label: "", response: false}) } $scope.deleteChoice = function() { this.quiz[this.qk].entries.splice(this.quiz[this.qk].entries.indexOf(this.choice), 1); } }) .controller("ExerciceMCQDepsController", function($scope, $routeParams, ExerciceMCQDeps) { $scope.init = function(flag) { $scope.deps = ExerciceMCQDeps.query({ exerciceId: $routeParams.exerciceId, mcqId: flag.id }); } }) .controller("TeamsListController", function($scope, $rootScope, Team, $location, $http) { $scope.teams = Team.query(); $scope.fields = ["id", "name"]; $scope.validateSearch = function(keyEvent) { if (keyEvent.which === 13) { var myTeam = null; $scope.teams.forEach(function(team) { if (String(team.name.toLowerCase()).indexOf($scope.query.toLowerCase()) >= 0) { if (myTeam === null) myTeam = team; else myTeam = false; } }); if (myTeam) $location.url("teams/" + myTeam.id); } }; $scope.genDexCfg = function() { $http.post("api/dex.yaml").then(function() { $http.post("api/dex-password.tpl").then(function() { $scope.addToast('success', 'Dex config refreshed.', "Don't forget to reload/reboot frontend host."); }, function(response) { $scope.addToast('danger', 'An error occurs when generating dex password tpl:', response.data.errmsg); }); }, function(response) { $scope.addToast('danger', 'An error occurs when generating dex config:', response.data.errmsg); }); } $scope.desactiveTeams = function() { $http.post("api/disableinactiveteams").then(function() { $scope.teams = Team.query(); }, function(response) { $scope.addToast('danger', 'An error occurs when disabling inactive teams:', response.data.errmsg); }); } $scope.show = function(id) { $location.url("/teams/" + id); }; }) .controller("TeamMembersController", function($scope, TeamMember) { $scope.fields = ["firstname", "lastname", "nickname", "company"]; if ($scope.team != null) { $scope.members = TeamMember.query({ teamId: $scope.team.id }); $scope.newMember = function() { $scope.members.push(new TeamMember()); } $scope.saveTeamMembers = function() { if (this.team.id) { TeamMember.save({ teamId: this.team.id }, $scope.members); } } $scope.removeMember = function(member) { angular.forEach($scope.members, function(m, k) { if (member == m) $scope.members.splice(k, 1); }); } } }) .controller("TeamController", function($scope, $rootScope, $location, Team, TeamMember, $routeParams, $http) { if ($scope.team && $scope.team.id) $routeParams.teamId = $scope.team.id; $scope.team = Team.get({ teamId: $routeParams.teamId }); $scope.fields = ["name", "color", "external_id"]; $scope.saveTeam = function() { if (this.team.id) { this.team.$update(); $rootScope.staticFilesNeedUpdate++; } else { this.team.$save(function() { $rootScope.staticFilesNeedUpdate++; $location.url("/teams/" + $scope.team.id); }); } } $scope.resetPasswd = function(team) { $http({ url: "api/password", method: "POST" }).then(function(response) { team.password = response.data.password; }); } $scope.deleteTeam = function() { backName = this.team.name; this.team.$remove(function() { $scope.addToast('success', 'Team ' + backName + ' successfully removed.'); $location.url("/teams/"); $rootScope.staticFilesNeedUpdate++; }, function(response) { $scope.addToast('danger', 'An error occurs during suppression of the team:', response.data.errmsg); }); } $scope.showStats = function() { $location.url("/teams/" + $scope.team.id + "/stats"); } $scope.showScore = function() { $location.url("/teams/" + $scope.team.id + "/score"); } }) .controller("TeamCertificatesController", function($scope, $rootScope, TeamCertificate, $routeParams, $http) { $scope.certificates = TeamCertificate.query({ teamId: $routeParams.teamId }); $scope.certificates.$promise.then(function(certificates) { certificates.forEach(function(certificate, cid) { certificate.serial = parseInt(certificate.id).toString(16); }); }); $scope.dissociateCertificate = function(certificate) { $http({ url: "api/certs/" + certificate.id, method: "PUT", data: { id_team: null } }).then(function(response) { $scope.certificates = TeamCertificate.query({ teamId: $routeParams.teamId }).$promise.then(function(certificates) { certificates.forEach(function(certificate, cid) { certificate.serial = parseInt(certificate.id).toString(16); }); }); $scope.addToast('success', 'Certificate successfully dissociated!'); }, function(response) { $scope.addToast('danger', 'An error occurs when dissociating certiticate:', response.data.errmsg); }); } }) .controller("TeamAssociationsController", function($scope, $rootScope, TeamAssociation, $routeParams, $http) { $scope.associations = TeamAssociation.query({ teamId: $routeParams.teamId }); $scope.form = { "newassoc": "" }; $scope.addAssociation = function() { if ($scope.form.newassoc) { TeamAssociation.save({ teamId: $scope.team.id, assoc: $scope.form.newassoc }).$promise.then( function() { $scope.form.newassoc = ""; $scope.associations = TeamAssociation.query({ teamId: $routeParams.teamId }); }, function(response) { if (response.data) $scope.addToast('danger', 'An error occurs when creating user association: ', response.data.errmsg); }); } } $scope.dropAssociation = function(assoc) { TeamAssociation.delete({ teamId: $routeParams.teamId, assoc: assoc }).$promise.then( function() { $scope.associations = TeamAssociation.query({ teamId: $routeParams.teamId }); }, function(response) { $scope.addToast('danger', 'An error occurs when removing user association: ', response.data.errmsg); }); } }) .controller("TeamHistoryController", function($scope, TeamHistory, $routeParams, $http, $rootScope) { $scope.history = TeamHistory.query({ teamId: $routeParams.teamId }); $scope.updHistory = function() { var target = { team_id: parseInt($routeParams.teamId), kind: $("#updHistory").data("kind"), time: $("#updHistory").data("time"), secondary: $("#updHistory").data("secondary") != "" ? $("#updHistory").data("secondary") : null, coeff: parseFloat($('#historycoeff').val()), }; if (target) { $http({ url: "api/exercices/" + $("#updHistory").data("primary") + "/history.json", method: "PATCH", data: target }).then(function(response) { $rootScope.staticFilesNeedUpdate++; $scope.history = TeamHistory.query({ teamId: $routeParams.teamId }); $('#updHistory').modal('hide'); }, function(response) { $scope.addToast('danger', 'An error occurs when updating history item: ', response.data.errmsg); }); } } $scope.delHistory = function(row) { $http({ url: "api/teams/" + $routeParams.teamId + "/history.json", method: "DELETE", data: row }).then(function(response) { $rootScope.staticFilesNeedUpdate++; $scope.history = TeamHistory.query({ teamId: $routeParams.teamId }); }, function(response) { $scope.addToast('danger', 'An error occurs when removing history item: ', response.data.errmsg); }); } }) .controller("TeamScoreController", function($scope, TeamScore, TeamMy, Exercice, $routeParams) { $scope.scores = TeamScore.query({ teamId: $routeParams.teamId }); $scope.my = TeamMy.get({ teamId: $routeParams.teamId }); $scope.exercices = Exercice.query(); }) .controller("TeamStatsController", function($scope, TeamStats, $routeParams) { $scope.teamstats = TeamStats.get({ teamId: $routeParams.teamId }); $scope.teamstats.$promise.then(function(res) { solvedByLevelPie("#pieLevels", res.levels); var themes = []; angular.forEach(res.themes, function(theme, tid) { themes.push(theme); }) solvedByThemesPie("#pieThemes", themes); }); }) .controller("TeamsJSONController", function($scope, Teams) { $scope.teams = Teams.get(); $scope.teams.$promise.then(function(teams) { $scope.rank = []; Object.keys(teams).forEach(function(team) { $scope.teams[team].id = team; $scope.rank.push($scope.teams[team]); }) }); }) .controller("TeamExercicesController", function($scope, Teams, Themes, TeamMy, Exercice, $routeParams) { $scope.teams = Teams.get(); $scope.themes = Themes.get(); $scope.exercices = Exercice.query(); $scope.my = TeamMy.get({ teamId: $routeParams.teamId }); $scope.teams.$promise.then(function(res){ $scope.nb_teams = 0; $scope.nb_reg_teams = Object.keys(res).length; angular.forEach(res, function(team, tid) { if (team.rank) $scope.nb_teams += 1; }, 0); }); $scope.my.$promise.then(function(res){ $scope.solved_exercices = 0; angular.forEach(res.exercices, function(exercice, eid) { if (exercice.solved_rank) { $scope.solved_exercices += 1; } }, 0); }); }) .controller("PresenceController", function($scope, TeamPresence, $routeParams) { $scope.presence = TeamPresence.query({ teamId: $routeParams.teamId }); $scope.presence.$promise.then(function(res) { presenceCal($scope, "#presenceCal", res); }); }); function solvedByLevelPie(location, data) { var width = d3.select(location).node().getBoundingClientRect().width - parseInt(d3.select(location).style("padding-right")) - parseInt(d3.select(location).style("padding-left")), height = d3.select(location).node().getBoundingClientRect().width - 10, radius = Math.min(width, height) / 2, innerRadius = 0.1 * radius; var color = d3.scale.ordinal() .range(["#9E0041", "#C32F4B", "#E1514B", "#F47245", "#FB9F59", "#FEC574", "#FAE38C", "#EAD195", "#C7E89E", "#9CD6A4", "#6CC4A4", "#4D9DB4", "#4776B4", "#5E4EA1"]); var pie = d3.layout.pie() .sort(null) .value(function(d) { return d.width; }); var arc = d3.svg.arc() .innerRadius(innerRadius) .outerRadius(function (d) { return (radius - innerRadius) * (d.data.score / 100.0) + innerRadius; }); var outlineArc = d3.svg.arc() .innerRadius(innerRadius) .outerRadius(radius); var svg = d3.select(location).append("svg") .attr("width", width) .attr("height", height) .append("g") .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); data.forEach(function(d) { d.score = d.solved * 100 / d.total; d.width = d.tries + 1; }); var path = svg.selectAll(".solidArc") .data(pie(data)) .enter().append("path") .attr("fill", function(d) { return color(d.data.tip); }) .attr("class", "solidArc") .attr("stroke", "gray") .attr("d", arc); var outerPath = svg.selectAll(".outlineArc") .data(pie(data)) .enter().append("path") .attr("fill", "none") .attr("stroke", "gray") .attr("class", "outlineArc") .attr("d", outlineArc); var labelArc = d3.svg.arc() .outerRadius(0.8 * radius) .innerRadius(0.8 * radius); svg.selectAll(".labelArc") .data(pie(data)) .enter().append("text") .attr("transform", function(d) { return "translate(" + labelArc.centroid(d) + ")"; }) .attr("dy", ".35em") .attr("text-anchor", "middle") .text(function(d) { return d.data.tip + ": " + d.data.solved + "/" + d.data.total; }); svg.selectAll(".label2Arc") .data(pie(data)) .enter().append("text") .attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; }) .attr("dy", ".35em") .attr("text-anchor", "middle") .text(function(d) { return d.data.tries; }); } function solvedByThemesPie(location, data) { var width = d3.select(location).node().getBoundingClientRect().width - parseInt(d3.select(location).style("padding-right")) - parseInt(d3.select(location).style("padding-left")), height = d3.select(location).node().getBoundingClientRect().width, radius = Math.min(width, height) / 2, innerRadius = 0.1 * radius; var color = d3.scale.ordinal() .range(["#9E0041", "#C32F4B", "#E1514B", "#F47245", "#FB9F59", "#FEC574", "#FAE38C", "#EAD195", "#C7E89E", "#9CD6A4", "#6CC4A4", "#4D9DB4", "#4776B4", "#5E4EA1"]); var pie = d3.layout.pie() .sort(null) .value(function(d) { return d.width; }); var arc = d3.svg.arc() .innerRadius(innerRadius) .outerRadius(function (d) { return (radius - innerRadius) * (d.data.score / 100.0) + innerRadius; }); var outlineArc = d3.svg.arc() .innerRadius(innerRadius) .outerRadius(radius); var svg = d3.select(location).append("svg") .attr("width", width) .attr("height", height) .append("g") .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); data.forEach(function(d) { d.score = d.solved * 100 / d.total; d.width = d.tries + 0.5; }); var path = svg.selectAll(".solidArc") .data(pie(data)) .enter().append("path") .attr("fill", function(d) { return color(d.data.tip); }) .attr("class", "solidArc") .attr("stroke", "gray") .attr("d", arc); var outerPath = svg.selectAll(".outlineArc") .data(pie(data)) .enter().append("path") .attr("fill", "none") .attr("stroke", "gray") .attr("class", "outlineArc") .attr("d", outlineArc); svg.selectAll(".label2Arc") .data(pie(data)) .enter().append("text") .attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; }) .attr("dy", ".35em") .attr("text-anchor", "middle") .text(function(d) { return d.data.solved; }); var labelArc = d3.svg.arc() .outerRadius(0.8 * radius) .innerRadius(0.8 * radius); svg.selectAll(".labelArc") .data(pie(data)) .enter().append("text") .attr("transform", function(d) { return "translate(" + labelArc.centroid(d) + ")"; }) .attr("dy", ".35em") .attr("text-anchor", "middle") .text(function(d) { return d.data.tip + ": " + d.data.tries; }); } function presenceCal(scope, location, data) { var width = d3.select(location).node().getBoundingClientRect().width, height = 80, cellSize = 17; // cell size var percent = d3.format(".1%"), format = d3.time.format("%H:%M"); var color = d3.scale.quantize() .domain([0, 16]) .range(d3.range(8).map(function(d) { return "q" + d + "-8"; })); var svg = d3.select(location).selectAll("svg") .data(d3.range(scope.settings.start, scope.time.start + (scope.settings.start % 86400000 + scope.settings.end - scope.settings.start), 86400000).map(function(t) { return new Date(t); })) .enter().append("svg") .attr("width", width) .attr("height", height) .attr("class", "RdYlGn") .append("g") .attr("transform", "translate(" + ((width - cellSize * 24) / 2) + "," + (height - cellSize * 4 - 1) + ")"); svg.append("text") .attr("transform", "translate(-6," + cellSize * 2.6 + ")rotate(-90)") .style("text-anchor", "middle") .text(function(d) { return d.getDate() + "-" + (d.getMonth() + 1); }); var rect = svg.selectAll(".quarter") .data(function(d) { return d3.time.minutes(new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0), new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23), 15); }) .enter().append("rect") .attr("width", cellSize) .attr("height", cellSize) .attr("transform", function(d) { return "translate(" + (d.getHours() * cellSize) + "," + (d.getMinutes() / 15 * cellSize) + ")"; }) .attr("class", function(d) { if (d >= scope.settings.start && d < scope.settings.start + scope.settings.end - scope.settings.start) return color(data.reduce(function(prev, cur){ cur = new Date(cur).getTime(); dv = d.getTime(); return prev + ((dv <= cur && cur < dv+15*60000)?1:0); }, 0)); }); }