diff --git a/admin/api/exercice.go b/admin/api/exercice.go index c4fdaed9..1df894bd 100644 --- a/admin/api/exercice.go +++ b/admin/api/exercice.go @@ -63,6 +63,7 @@ func declareExercicesRoutes(router *gin.RouterGroup) { apiFlagsRoutes.DELETE("/", deleteExerciceFlag) apiFlagsRoutes.GET("/dependancies", showExerciceFlagDeps) apiFlagsRoutes.GET("/statistics", showExerciceFlagStats) + apiFlagsRoutes.DELETE("/tries", deleteExerciceFlagTries) apiFlagsRoutes.GET("/choices/", listFlagChoices) apiFlagsChoicesRoutes := apiExercicesRoutes.Group("/choices/:cid") apiFlagsChoicesRoutes.Use(FlagChoiceHandler) @@ -78,6 +79,8 @@ func declareExercicesRoutes(router *gin.RouterGroup) { apiQuizRoutes.PUT("", updateExerciceQuiz) apiQuizRoutes.DELETE("", deleteExerciceQuiz) apiQuizRoutes.GET("/dependancies", showExerciceQuizDeps) + apiQuizRoutes.GET("/statistics", showExerciceQuizStats) + apiQuizRoutes.DELETE("/tries", deleteExerciceQuizTries) apiExercicesRoutes.GET("/tags", listExerciceTags) apiExercicesRoutes.POST("/tags", addExerciceTag) @@ -864,7 +867,7 @@ func showExerciceFlagStats(c *gin.Context) { return } - var completed, tries, nteams int64 + var completed int64 for _, hline := range history { if hline["kind"].(string) == "flag_found" { @@ -874,13 +877,39 @@ func showExerciceFlagStats(c *gin.Context) { } } + tries, err := flag.NbTries() + if err != nil { + log.Println("Unable to nbTries:", err.Error()) + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when retrieving flag tries"}) + return + } + + teams, err := flag.TeamsOnIt() + if err != nil { + log.Println("Unable to teamsOnIt:", err.Error()) + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when retrieving flag related teams"}) + return + } + c.JSON(http.StatusOK, gin.H{ "completed": completed, "tries": tries, - "nteams": nteams, + "teams": teams, }) } +func deleteExerciceFlagTries(c *gin.Context) { + flag := c.MustGet("flag-key").(*fic.FlagKey) + + err := flag.DeleteTries() + if err != nil { + c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) + return + } + + c.AbortWithStatusJSON(http.StatusOK, true) +} + func tryExerciceFlag(c *gin.Context) { flag := c.MustGet("flag-key").(*fic.FlagKey) @@ -1051,6 +1080,60 @@ func showExerciceQuizDeps(c *gin.Context) { c.JSON(http.StatusOK, deps) } +func showExerciceQuizStats(c *gin.Context) { + exercice := c.MustGet("exercice").(*fic.Exercice) + quiz := c.MustGet("flag-quiz").(*fic.MCQ) + + history, err := exercice.GetHistory() + if err != nil { + log.Println("Unable to getExerciceHistory:", err.Error()) + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when retrieving exercice history"}) + return + } + + var completed int64 + + for _, hline := range history { + if hline["kind"].(string) == "mcq_found" { + if *hline["secondary"].(*int) == quiz.Id { + completed += 1 + } + } + } + + tries, err := quiz.NbTries() + if err != nil { + log.Println("Unable to nbTries:", err.Error()) + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when retrieving flag tries"}) + return + } + + teams, err := quiz.TeamsOnIt() + if err != nil { + log.Println("Unable to teamsOnIt:", err.Error()) + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when retrieving flag related teams"}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "completed": completed, + "tries": tries, + "teams": teams, + }) +} + +func deleteExerciceQuizTries(c *gin.Context) { + quiz := c.MustGet("flag-quiz").(*fic.MCQ) + + err := quiz.DeleteTries() + if err != nil { + c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) + return + } + + c.AbortWithStatusJSON(http.StatusOK, true) +} + func updateExerciceQuiz(c *gin.Context) { quiz := c.MustGet("flag-quiz").(*fic.MCQ) diff --git a/admin/static/js/app.js b/admin/static/js/app.js index 2fa150f4..24e7b36b 100644 --- a/admin/static/js/app.js +++ b/admin/static/js/app.js @@ -358,6 +358,9 @@ angular.module("FICApp") }) .factory("ExerciceMCQDeps", function ($resource) { return $resource("api/exercices/:exerciceId/quiz/:mcqId/dependancies", { exerciceId: '@idExercice', mcqId: '@id' }) + }) + .factory("ExerciceMCQStats", function ($resource) { + return $resource("api/exercices/:exerciceId/quiz/:mcqId/statistics", { exerciceId: '@idExercice', mcqId: '@id' }) }); angular.module("FICApp") @@ -614,9 +617,18 @@ angular.module("FICApp") response.enableExerciceDepend = response.unlockedChallengeDepth >= 0; response.disabledsubmitbutton = response.disablesubmitbutton && response.disablesubmitbutton.length > 0; + if (response.end) { + $scope.duration = (response.end - response.start)/60000; + } }) $scope.challenge = SettingsChallenge.get(); $scope.duration = 360; + $scope.durationChange = function(endChanged) { + if (endChanged) + $scope.duration = (new Date($scope.config.end).getTime() - new Date($scope.config.start).getTime())/60000; + else + $scope.config.end = new Date(new Date($scope.config.start).getTime() + $scope.duration * 60000); + } $scope.activateTime = ""; $scope.challenge.$promise.then(function (c) { if (c.duration) @@ -651,7 +663,7 @@ angular.module("FICApp") $scope.saveChallengeInfo = function () { this.challenge.duration = $scope.duration; this.challenge.$update(function (response) { - $scope.addToast('success', 'Infos du challenge mises à jour avec succès !'); + $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); }); @@ -725,9 +737,9 @@ angular.module("FICApp") "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 ?', + $scope.addToast('warning', txts[type], 'Êtes-vous sûr de vouloir continuer ?', function () { - if (confirm("Êtes-vous vraiment sûr ?\n" + txts[type])) { + 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("/"); @@ -739,7 +751,7 @@ angular.module("FICApp") }); }; $scope.switchToProd = function () { - $scope.addToast('warning', "Activer le mode challenge ?", "L'activation du mode challenge est temporaire (vous devriez plutôt relancer le daemon avec l'option `-4real`). Ce mode permet d'éviter les mauvaises manipulations et désactive le hook git de synchronisation automatique. Êtes-vous sûr de vouloir continuer ?", + $scope.addToast('warning', "Activer le mode challenge ?", "L'activation du mode challenge est temporaire (vous devriez plutôt relancer le daemon avec l'option `-4real`). Ce mode permet d'éviter les mauvaises manipulations et désactive le hook git de synchronisation automatique. Êtes-vous sûr de vouloir continuer ?", function () { $http.put("api/prod", true).then(function (time) { $rootScope.refreshSettings() @@ -762,6 +774,20 @@ angular.module("FICApp") }); }; }) + .component('teamLink', { + bindings: { + idTeam: '=', + }, + controller: function (Team) { + var ctrl = this; + ctrl.team = {}; + + ctrl.$onInit = function () { + ctrl.team = Team.get({teamId: ctrl.idTeam}); + }; + }, + template: `{{ $ctrl.team.name }} ` + }) .component('repositoryUptodate', { bindings: { repository: '<', @@ -823,10 +849,10 @@ angular.module("FICApp") $scope.deepSync = function (theme) { if (theme) { - question = 'Faire une synchronisation intégrale du thème ' + theme.name + ' ?' + question = 'Faire une synchronisation intégrale du thème ' + theme.name + ' ?' url = "api/sync/deep/" + theme.id } else { - question = 'Faire une synchronisation intégrale ?' + question = 'Faire une synchronisation intégrale ?' url = "api/sync/deep" } $scope.addToast('warning', question, '', @@ -842,7 +868,7 @@ angular.module("FICApp") }); }; $scope.speedyDeepSync = function () { - $scope.addToast('warning', 'Faire une synchronisation profonde rapide, sans s\'occuper des fichiers ?', '', + $scope.addToast('warning', 'Faire une synchronisation profonde rapide, sans s\'occuper des fichiers ?', '', function () { $scope.deepSyncInProgress = true; $http.post("api/sync/speed").then(function () { @@ -855,7 +881,7 @@ angular.module("FICApp") }); }; $scope.baseSync = function () { - $scope.addToast('warning', 'Tirer les mises à jour du dépôt ?', '', + $scope.addToast('warning', 'Tirer les mises à jour du dépôt ?', '', function () { $scope.deepSyncInProgress = true; $http.post("api/sync/base").then(function () { @@ -868,7 +894,7 @@ angular.module("FICApp") }); }; $scope.syncVideos = function () { - $scope.addToast('warning', 'Synchroniser les vidéos de résolution ?', 'ATTENTION il ne faut pas lancer cette synchronisation durant le challenge. Seulement une fois le challenge terminé, cela permet de rendre les vidéos accessibles dans l\'interface joueurs.', + $scope.addToast('warning', 'Synchroniser les vidéos de résolution ?', 'ATTENTION il ne faut pas lancer cette synchronisation durant le challenge. Seulement une fois le challenge terminé, cela permet de rendre les vidéos accessibles dans l\'interface joueurs.', function () { $scope.deepSyncInProgress = true; $http.post("api/sync/videos").then(function () { @@ -1113,7 +1139,7 @@ angular.module("FICApp") }, { type: "countdown", - params: { color: "success", end: null, lead: "Go, go, go !", title: "Le challenge forensic va bientôt commencer !" }, + params: { color: "success", end: null, lead: "Go, go, go !", title: "Le challenge forensic va bientôt commencer !" }, }, ]; $scope.display.side = [ @@ -1232,8 +1258,8 @@ angular.module("FICApp") show: true, shadow: "#E8CF5C", end: new Date($rootScope.getSrvTime().getTime() + 1802000).toISOString(), - before: "Heure joyeuse : chaque résolution compte double !", - after: "Heure joyeuse terminée !", + before: "Heure joyeuse : chaque résolution compte double !", + after: "Heure joyeuse terminée !", } } else if (scene == "freehintquarter") { @@ -1241,8 +1267,8 @@ angular.module("FICApp") show: true, shadow: "#3DD28F", end: new Date($rootScope.getSrvTime().getTime() + 902000).toISOString(), - before: "Quart d'heure facile : indices dévoilés !", - after: "Quart d'heure facile terminée !", + before: "Quart d'heure facile : indices dévoilés !", + after: "Quart d'heure facile terminée !", } } }; @@ -1371,7 +1397,7 @@ angular.module("FICApp") }); }; $scope.clearFilesDir = function () { - $scope.addToast('warning', 'Êtes-vous sûr de vouloir continuer ?', "Ceci va supprimer tout le contenu du dossier FILES. Il s'agit des fichiers ci-dessous, il faudra refaire une synchronisation ensuite.", + $scope.addToast('warning', 'Êtes-vous sûr de vouloir continuer ?', "Ceci va supprimer tout le contenu du dossier FILES. Il s'agit des fichiers ci-dessous, il faudra refaire une synchronisation ensuite.", function () { $scope.clearFilesWIP = true; $http({ @@ -2279,7 +2305,7 @@ angular.module("FICApp") method: "POST" }).then(function (response) { flag.test_str = ""; - $scope.addToast('success', "Flag Ok !"); + $scope.addToast('success', "Flag Ok !"); }, function (response) { flag.test_str = ""; $scope.addToast('danger', 'An error occurs: ', response.data.errmsg); @@ -2348,10 +2374,16 @@ angular.module("FICApp") } }) - .controller("ExerciceFlagStatsController", function ($scope, $routeParams, ExerciceFlagStats) { + .controller("ExerciceFlagStatsController", function ($scope, $routeParams, ExerciceFlagStats, $http) { $scope.init = function (flag) { + $scope.flag_id = flag.id; $scope.stats = ExerciceFlagStats.get({ exerciceId: $routeParams.exerciceId, flagId: flag.id }); } + $scope.deleteTries = function () { + $http.delete(`/api/exercices/${$routeParams.exerciceId}/flags/${$scope.flag_id}/tries`).then(function () { + $scope.stats = ExerciceFlagStats.get({ exerciceId: $routeParams.exerciceId, flagId: $scope.flag_id }); + }); + } }) .controller("ExerciceMCQFlagsController", function ($scope, ExerciceMCQFlag, $routeParams, $rootScope) { @@ -2391,6 +2423,18 @@ angular.module("FICApp") } }) + .controller("ExerciceMCQStatsController", function ($scope, $routeParams, ExerciceMCQStats, $http) { + $scope.init = function (mcq) { + $scope.mcq_id = mcq.id; + $scope.stats = ExerciceMCQStats.get({ exerciceId: $routeParams.exerciceId, mcqId: mcq.id }); + } + $scope.deleteTries = function () { + $http.delete(`/api/exercices/${$routeParams.exerciceId}/quiz/${$scope.mcq_id}/tries`).then(function () { + $scope.stats = ExerciceMCQStats.get({ exerciceId: $routeParams.exerciceId, mcqId: $scope.mcq_id }); + }); + } + }) + .controller("TeamsListController", function ($scope, $rootScope, Team, $location, $http) { $scope.teams = Team.query(); $scope.fields = ["id", "name"]; diff --git a/admin/static/views/exercice-flags.html b/admin/static/views/exercice-flags.html index b23556ca..f765d9a9 100644 --- a/admin/static/views/exercice-flags.html +++ b/admin/static/views/exercice-flags.html @@ -83,9 +83,16 @@