admin: Add ability to append element to exercice history

This commit is contained in:
nemunaire 2024-03-17 10:19:35 +01:00
parent ae5068f8b8
commit 977caccc1f
6 changed files with 237 additions and 3 deletions

View File

@ -35,6 +35,7 @@ func declareExercicesRoutes(router *gin.RouterGroup) {
apiExercicesRoutes.GET("/stats.json", getExerciceStats) apiExercicesRoutes.GET("/stats.json", getExerciceStats)
apiExercicesRoutes.GET("/history.json", getExerciceHistory) apiExercicesRoutes.GET("/history.json", getExerciceHistory)
apiExercicesRoutes.PUT("/history.json", appendExerciceHistory)
apiExercicesRoutes.PATCH("/history.json", updateExerciceHistory) apiExercicesRoutes.PATCH("/history.json", updateExerciceHistory)
apiExercicesRoutes.DELETE("/history.json", delExerciceHistory) apiExercicesRoutes.DELETE("/history.json", delExerciceHistory)
@ -476,6 +477,26 @@ type uploadedExerciceHistory struct {
Coeff float32 Coeff float32
} }
func appendExerciceHistory(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
var uh uploadedExerciceHistory
err := c.ShouldBindJSON(&uh)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
err = exercice.AppendHistoryItem(uh.IdTeam, uh.Kind, uh.Secondary)
if err != nil {
log.Println("Unable to appendExerciceHistory:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during history moditication."})
return
}
c.JSON(http.StatusOK, uh)
}
func updateExerciceHistory(c *gin.Context) { func updateExerciceHistory(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice) exercice := c.MustGet("exercice").(*fic.Exercice)

View File

@ -325,6 +325,7 @@ func ApplySettings(config *settings.Settings) {
fic.SubmissionCostBase = config.SubmissionCostBase fic.SubmissionCostBase = config.SubmissionCostBase
fic.HintCoefficient = config.HintCurCoefficient fic.HintCoefficient = config.HintCurCoefficient
fic.WChoiceCoefficient = config.WChoiceCurCoefficient fic.WChoiceCoefficient = config.WChoiceCurCoefficient
fic.ExerciceCurrentCoefficient = config.ExerciceCurCoefficient
fic.GlobalScoreCoefficient = config.GlobalScoreCoefficient fic.GlobalScoreCoefficient = config.GlobalScoreCoefficient
fic.SubmissionCostBase = config.SubmissionCostBase fic.SubmissionCostBase = config.SubmissionCostBase
fic.SubmissionUniqueness = config.SubmissionUniqueness fic.SubmissionUniqueness = config.SubmissionUniqueness

View File

@ -1908,6 +1908,54 @@ angular.module("FICApp")
}); });
} }
} }
$scope.selectedTeam = "";
$scope.validateForTeam = function() {
var flagid = $("#validationModal").data("flagid");
if (!flagid) return;
var target = {
team_id: parseInt($("#tteam").val().replace(/number:/, '')),
kind: $("#validationModal").data("kind"),
secondary: flagid,
};
$http({
url: "api/exercices/" + $scope.exercice.id + "/history.json",
method: "PUT",
data: target
}).then(function(response) {
$rootScope.staticFilesNeedUpdate++;
$("#validationModal").modal('hide');
$scope.addToast('success', 'Flag validé avec succès');
}, function(response) {
$scope.addToast('danger', 'An error occurs when trying to validate flag for team:', response.data.errmsg);
});
}
$scope.historyAppend = function() {
var secondary = null;
if ($("#historyEvent").val() == "hint")
secondary = parseInt($("#historySecondaryHint").val().replace(/number:/, ''));
else if ($("#historyEvent").val() == "wchoices" || $("#historyEvent").val() == "flag_found")
secondary = parseInt($("#historySecondaryFlag").val().replace(/number:/, ''));
else if ($("#historyEvent").val() == "mcq_found")
secondary = parseInt($("#historySecondaryQuiz").val().replace(/number:/, ''));
var target = {
team_id: parseInt($("#tteam").val().replace(/number:/, '')),
kind: $("#historyEvent").val(),
secondary: secondary,
};
$http({
url: "api/exercices/" + $scope.exercice.id + "/history.json",
method: "PUT",
data: target
}).then(function(response) {
$rootScope.staticFilesNeedUpdate++;
$("#appendHistoryModal").modal('hide');
$scope.addToast('success', 'Événement ajouté avec succès');
$scope.refreshHistory();
}, function(response) {
$scope.addToast('danger', 'An error occurs when trying to add event in history:', response.data.errmsg);
});
}
}) })
.controller("SubmissionsStatsController", function($scope, $http, $interval) { .controller("SubmissionsStatsController", function($scope, $http, $interval) {
@ -1983,7 +2031,12 @@ angular.module("FICApp")
}) })
.controller("ExerciceHistoryController", function($scope, ExerciceHistory, $routeParams, $http, $rootScope) { .controller("ExerciceHistoryController", function($scope, ExerciceHistory, $routeParams, $http, $rootScope) {
$scope.history = [];
$scope.refreshHistory = function() {
$scope.history = ExerciceHistory.query({ exerciceId: $routeParams.exerciceId }); $scope.history = ExerciceHistory.query({ exerciceId: $routeParams.exerciceId });
}
$scope.$parent.refreshHistory = $scope.refreshHistory;
$scope.refreshHistory();
$scope.updHistory = function() { $scope.updHistory = function() {
var target = { var target = {
team_id: $("#updHistory").data("idteam"), team_id: $("#updHistory").data("idteam"),

View File

@ -82,9 +82,12 @@
</div> </div>
<div class="col-4" ng-controller="ExerciceFlagChoicesController"> <div class="col-4" ng-controller="ExerciceFlagChoicesController">
<div class="btn-toolbar justify-content-end mb-2" role="toolbar"> <div class="btn-toolbar justify-content-end mb-2" role="toolbar">
<div class="btn-group" role="group"> <div class="btn-group mx-2" role="group">
<button type="button" ng-click="addChoice()" class="btn btn-sm btn-primary"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Ajouter choix</button> <button type="button" ng-click="addChoice()" class="btn btn-sm btn-primary"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Ajouter choix</button>
</div> </div>
<div class="btn-group" role="group">
<button type="button" class="btn btn-sm btn-dark" data-toggle="modal" data-target="#validationModal" data-flag="{{ flag.label }}" data-flagid="{{ flag.id }}" data-kind="flag_found"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> Valider pour &nbsp;</button>
</div>
</div> </div>
<div ng-repeat="choice in choices" ng-if="choices.length > 0"> <div ng-repeat="choice in choices" ng-if="choices.length > 0">
<div class="row"> <div class="row">
@ -170,3 +173,56 @@
</div> </div>
</div> </div>
</div> </div>
<div class="modal fade" id="validationModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content bg-light">
<div class="modal-header">
<h5 class="modal-title">Valider ce flag pour une équipe</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<form ng-submit="validateForTeam()">
<div class="modal-body">
<div class="form-group row">
<label for="labelflag" class="col-md-3 col-form-label">Flag</label>
<div class="col-md-9">
<input type="text" readonly class="form-control-plaintext" id="labelflag">
</div>
</div>
<div class="form-group row">
<label for="tteam" class="col-md-3 col-form-label">Équipe</label>
<div class="col-md-9" ng-controller="TeamsListController">
<select class="custom-select custom-select-sm" id="tteam" ng-model="selectedTeam" ng-options="t.id as t.name for t in teams"></select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annuler</button>
<button type="submit" class="btn btn-primary">Enregistrer</button>
</div>
</div>
</form>
</div>
</div>
</div>
<script>
$('#validationModal').on('shown.bs.modal', function (event) {
$('#tteam').trigger('focus');
var button = $(event.relatedTarget);
var flag = button.data('flag');
var flagid = button.data('flagid');
var kind = button.data('kind');
var modal = $(this);
modal.data('kind', kind);
modal.data('flagid', flagid);
modal.data('flag', flag);
if (modal.data('flag')) {
$("#labelflag").val(modal.data('flag'))
}
});
</script>

View File

@ -307,7 +307,12 @@
</div> </div>
<div class="mt-2" style="overflow-y: scroll; height: 450px" ng-controller="ExerciceHistoryController"> <div class="mt-2" style="overflow-y: scroll; height: 450px" ng-controller="ExerciceHistoryController">
<div class="d-flex justify-content-between align-items-start">
<h3>Historique</h3> <h3>Historique</h3>
<div class="btn-group" role="group">
<button type="button" class="btn btn-sm btn-dark" data-toggle="modal" data-target="#appendHistoryModal"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span></button>
</div>
</div>
<table class="table table-hover table-striped table-bordered bg-primary text-light"> <table class="table table-hover table-striped table-bordered bg-primary text-light">
<tbody> <tbody>
<tr ng-repeat="row in history" ng-class="{'bg-ffound': row.kind == 'flag_found', 'bg-mfound': row.kind == 'mcq_found', 'bg-wchoices': row.kind == 'wchoices', 'bg-success': row.kind == 'solved', 'bg-info': row.kind == 'hint', 'bg-warning': row.kind == 'tries'}"> <tr ng-repeat="row in history" ng-class="{'bg-ffound': row.kind == 'flag_found', 'bg-mfound': row.kind == 'mcq_found', 'bg-wchoices': row.kind == 'wchoices', 'bg-success': row.kind == 'solved', 'bg-info': row.kind == 'hint', 'bg-warning': row.kind == 'tries'}">
@ -379,3 +384,73 @@
$('#historycoeff').focus(); $('#historycoeff').focus();
}); });
</script> </script>
<div class="modal fade" id="appendHistoryModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content bg-light">
<div class="modal-header">
<h5 class="modal-title">Ajout d'une entrée dans l'historique</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<form ng-submit="historyAppend()">
<div class="modal-body">
<div class="form-group row">
<label for="labelflag" class="col-md-3 col-form-label">Exercice</label>
<div class="col-md-9">
<input type="text" readonly class="form-control-plaintext" value="{{ exercice.title }}">
</div>
</div>
<div class="form-group row">
<label for="tteam" class="col-md-3 col-form-label">Équipe</label>
<div class="col-md-9" ng-controller="TeamsListController">
<select class="custom-select custom-select-sm" id="tteam" ng-model="selectedTeam" ng-options="t.id as t.name for t in teams"></select>
</div>
</div>
<div class="form-group row">
<label for="tteam" class="col-md-3 col-form-label">Type d'événement</label>
<div class="col-md-9">
<select class="custom-select custom-select-sm" id="historyEvent" ng-model="selectedEvent">
<option value="tries">Tentative</option>
<option value="hint">Indice dévoilé</option>
<option value="wchoices">Liste de choix affichée</option>
<option value="flag_found">Flag trouvé</option>
<option value="mcq_found">QCM validé</option>
<option value="solved">Exercice terminé</option>
</select>
</div>
</div>
<div class="form-group row" ng-if="selectedEvent == 'hint'">
<label for="tteam" class="col-md-3 col-form-label">Indice</label>
<div class="col-md-9" ng-controller="ExerciceHintsController">
<select class="custom-select custom-select-sm" id="historySecondaryHint" ng-model="selectedSecondaryHint" ng-options="h.id as h.title for h in hints"></select>
</div>
</div>
<div class="form-group row" ng-if="selectedEvent == 'wchoices' || selectedEvent == 'flag_found'">
<label for="tteam" class="col-md-3 col-form-label">Flag</label>
<div class="col-md-9" ng-controller="ExerciceFlagsController">
<select class="custom-select custom-select-sm" id="historySecondaryFlag" ng-model="selectedSecondaryFlag" ng-options="f.id as f.label for f in flags"></select>
</div>
</div>
<div class="form-group row" ng-if="selectedEvent == 'mcq_found'">
<label for="tteam" class="col-md-3 col-form-label">QCM</label>
<div class="col-md-9" ng-controller="ExerciceMCQFlagsController">
<select class="custom-select custom-select-sm" id="historySecondaryQuiz" ng-model="selectedSecondaryFlag" ng-options="q.id as q.title for q in quiz"></select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annuler</button>
<button type="submit" class="btn btn-primary">Enregistrer</button>
</div>
</div>
</form>
</div>
</div>
</div>
<script>
$('#appendHistoryModal').on('shown.bs.modal', function (event) {
$('#tteam').trigger('focus');
});
</script>

View File

@ -1,7 +1,9 @@
package fic package fic
import ( import (
"encoding/binary"
"fmt" "fmt"
"math/rand"
"time" "time"
) )
@ -56,6 +58,32 @@ func (e *Exercice) GetHistory() ([]map[string]interface{}, error) {
return hist, nil return hist, nil
} }
// AppendHistoryItem sets values an entry from the history.
func (e *Exercice) AppendHistoryItem(tId int64, kind string, secondary *int64) error {
team, err := GetTeam(tId)
if err != nil {
return err
}
if kind == "tries" {
bid := make([]byte, 5)
binary.LittleEndian.PutUint32(bid, rand.Uint32())
return (&Exercice{Id: e.Id}).NewTry(team, bid)
} else if kind == "hint" && secondary != nil {
return team.OpenHint(&EHint{Id: *secondary})
} else if kind == "wchoices" && secondary != nil {
return team.DisplayChoices(&FlagKey{Id: int(*secondary)})
} else if kind == "flag_found" && secondary != nil {
return (&FlagKey{Id: int(*secondary)}).FoundBy(team)
} else if kind == "mcq_found" && secondary != nil {
return (&MCQ{Id: int(*secondary)}).FoundBy(team)
} else if kind == "solved" {
return (&Exercice{Id: e.Id}).Solved(team)
} else {
return nil
}
}
// UpdateHistoryItem sets values an entry from the history. // UpdateHistoryItem sets values an entry from the history.
func (e *Exercice) UpdateHistoryItem(coeff float32, tId int64, kind string, h time.Time, secondary *int64) (interface{}, error) { func (e *Exercice) UpdateHistoryItem(coeff float32, tId int64, kind string, h time.Time, secondary *int64) (interface{}, error) {
if kind == "hint" && secondary != nil { if kind == "hint" && secondary != nil {