admin: Add ability to append element to exercice history
This commit is contained in:
parent
ae5068f8b8
commit
977caccc1f
@ -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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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"),
|
||||||
|
@ -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 </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">×</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>
|
||||||
|
@ -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">×</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>
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user