admin: Add stats about submissions rate
This commit is contained in:
parent
116c061715
commit
329bd246c7
@ -3,6 +3,7 @@ package api
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@ -10,6 +11,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/admin/pki"
|
"srs.epita.fr/fic-server/admin/pki"
|
||||||
|
"srs.epita.fr/fic-server/libfic"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@ -31,6 +33,8 @@ func declareHealthRoutes(router *gin.RouterGroup) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
router.GET("/health.json", GetHealth)
|
router.GET("/health.json", GetHealth)
|
||||||
|
router.GET("/submissions-stats.json", GetSubmissionsStats)
|
||||||
|
router.GET("/validations-stats.json", GetValidationsStats)
|
||||||
}
|
}
|
||||||
|
|
||||||
type healthFileReport struct {
|
type healthFileReport struct {
|
||||||
@ -73,3 +77,67 @@ func GetHealth(c *gin.Context) {
|
|||||||
|
|
||||||
c.JSON(http.StatusOK, getHealth(TimestampCheck))
|
c.JSON(http.StatusOK, getHealth(TimestampCheck))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SubmissionsStats struct {
|
||||||
|
NbSubmissionLastMinute uint `json:"nbsubminute"`
|
||||||
|
NbSubmissionLast5Minute uint `json:"nbsub5minute"`
|
||||||
|
NbSubmissionLastQuarter uint `json:"nbsubquarter"`
|
||||||
|
NbSubmissionLastHour uint `json:"nbsubhour"`
|
||||||
|
NbSubmissionLastDay uint `json:"nbsubday"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func calcSubmissionsStats(tries []time.Time) (stats SubmissionsStats) {
|
||||||
|
lastMinute := time.Now().Add(-1 * time.Minute)
|
||||||
|
last5Minute := time.Now().Add(-5 * time.Minute)
|
||||||
|
lastQuarter := time.Now().Add(-15 * time.Minute)
|
||||||
|
lastHour := time.Now().Add(-1 * time.Hour)
|
||||||
|
lastDay := time.Now().Add(-24 * time.Hour)
|
||||||
|
|
||||||
|
for _, t := range tries {
|
||||||
|
if lastMinute.Before(t) {
|
||||||
|
stats.NbSubmissionLastMinute += 1
|
||||||
|
stats.NbSubmissionLast5Minute += 1
|
||||||
|
stats.NbSubmissionLastQuarter += 1
|
||||||
|
stats.NbSubmissionLastHour += 1
|
||||||
|
stats.NbSubmissionLastDay += 1
|
||||||
|
} else if last5Minute.Before(t) {
|
||||||
|
stats.NbSubmissionLast5Minute += 1
|
||||||
|
stats.NbSubmissionLastQuarter += 1
|
||||||
|
stats.NbSubmissionLastHour += 1
|
||||||
|
stats.NbSubmissionLastDay += 1
|
||||||
|
} else if lastQuarter.Before(t) {
|
||||||
|
stats.NbSubmissionLastQuarter += 1
|
||||||
|
stats.NbSubmissionLastHour += 1
|
||||||
|
stats.NbSubmissionLastDay += 1
|
||||||
|
} else if lastHour.Before(t) {
|
||||||
|
stats.NbSubmissionLastHour += 1
|
||||||
|
stats.NbSubmissionLastDay += 1
|
||||||
|
} else if lastDay.Before(t) {
|
||||||
|
stats.NbSubmissionLastDay += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSubmissionsStats(c *gin.Context) {
|
||||||
|
tries, err := fic.GetTries(nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to GetTries:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieves tries."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, calcSubmissionsStats(tries))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetValidationsStats(c *gin.Context) {
|
||||||
|
tries, err := fic.GetValidations(nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to GetTries:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieves tries."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, calcSubmissionsStats(tries))
|
||||||
|
}
|
||||||
|
@ -1723,6 +1723,32 @@ angular.module("FICApp")
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
.controller("SubmissionsStatsController", function($scope, $http, $interval) {
|
||||||
|
var refresh = function() {
|
||||||
|
$http({
|
||||||
|
url: "api/submissions-stats.json",
|
||||||
|
}).then(function(response) {
|
||||||
|
$scope.submissionsstats = response.data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
var myinterval = $interval(refresh, 15000);
|
||||||
|
refresh();
|
||||||
|
$scope.$on('$destroy', function () { $interval.cancel(myinterval); });
|
||||||
|
})
|
||||||
|
|
||||||
|
.controller("ValidationsStatsController", function($scope, $http, $interval) {
|
||||||
|
var refresh = function() {
|
||||||
|
$http({
|
||||||
|
url: "api/validations-stats.json",
|
||||||
|
}).then(function(response) {
|
||||||
|
$scope.validationsstats = response.data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
var myinterval = $interval(refresh, 15000);
|
||||||
|
refresh();
|
||||||
|
$scope.$on('$destroy', function () { $interval.cancel(myinterval); });
|
||||||
|
})
|
||||||
|
|
||||||
.controller("ExercicesStatsController", function($scope, Themes, ExercicesStats) {
|
.controller("ExercicesStatsController", function($scope, Themes, ExercicesStats) {
|
||||||
$scope.themes = Themes.get();
|
$scope.themes = Themes.get();
|
||||||
$scope.exercices = {};
|
$scope.exercices = {};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<div class="jumbotron text-light bg-dark">
|
<div class="jumbotron text-light bg-dark py-3">
|
||||||
<h1 class="display-4">Interface d'administration du challenge</h1>
|
<h1 class="display-4">Interface d'administration du challenge</h1>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
@ -9,6 +9,20 @@
|
|||||||
Latence frontend-backend : <span ng-class="{'text-success': t.diffFB <= 1000000000, 'text-warning': t.diffFB > 1000000000, 'text-danger': t.diffFB > 1500000000}"><ng-pluralize count="t.diffFB / 1000000000" when="{'one': '{} seconde', 'other': '{} secondes'}"></ng-pluralize></span><br>
|
Latence frontend-backend : <span ng-class="{'text-success': t.diffFB <= 1000000000, 'text-warning': t.diffFB > 1000000000, 'text-danger': t.diffFB > 1500000000}"><ng-pluralize count="t.diffFB / 1000000000" when="{'one': '{} seconde', 'other': '{} secondes'}"></ng-pluralize></span><br>
|
||||||
Dernière synchronisation du frontend : {{ t.frontend | date:"mediumTime" }}
|
Dernière synchronisation du frontend : {{ t.frontend | date:"mediumTime" }}
|
||||||
</p>
|
</p>
|
||||||
|
<ul class="pagination" ng-controller="SubmissionsStatsController">
|
||||||
|
<li class="page-item" title="Nombre de soumissions la dernière minute"><a class="page-link">{{ submissionsstats.nbsubminute }}</a></li>
|
||||||
|
<li class="page-item" title="Taux de soumission ces dernières 5 minutes ; total : {{ submissionsstats.nbsub5minute }}"><a class="page-link">{{ submissionsstats.nbsub5minute / 5 | number:2 }}</a></li>
|
||||||
|
<li class="page-item" title="Taux de soumission ces dernières 15 minutes ; total : {{ submissionsstats.nbsubquarter }}"><a class="page-link">{{ submissionsstats.nbsubquarter / 15 | number:2 }}</a></li>
|
||||||
|
<li class="page-item" title="Taux de soumission cette dernière heure ; total : {{ submissionsstats.nbsubhour }}"><a class="page-link">{{ submissionsstats.nbsubhour / 60 | number:2 }}</a></li>
|
||||||
|
<li class="page-item" title="Taux de soumission ce dernier jour ; total : {{ submissionsstats.nbsubday }}"><a class="page-link">{{ submissionsstats.nbsubday / 1440 | number:2 }}</a></li>
|
||||||
|
</ul>
|
||||||
|
<ul class="pagination" ng-controller="ValidationsStatsController">
|
||||||
|
<li class="page-item" title="Nombre de validations la dernière minute"><a class="page-link text-success">{{ validationsstats.nbsubminute }}</a></li>
|
||||||
|
<li class="page-item" title="Taux de validation ces dernières 5 minutes ; total : {{ validationsstats.nbsub5minute }}"><a class="page-link text-success">{{ validationsstats.nbsub5minute / 5 | number:2 }}</a></li>
|
||||||
|
<li class="page-item" title="Taux de validation ces dernières 15 minutes ; total : {{ validationsstats.nbsubquarter }}"><a class="page-link text-success">{{ validationsstats.nbsubquarter / 15 | number:2 }}</a></li>
|
||||||
|
<li class="page-item" title="Taux de validation cette dernière heure ; total : {{ validationsstats.nbsubhour }}"><a class="page-link text-success">{{ validationsstats.nbsubhour / 60 | number:2 }}</a></li>
|
||||||
|
<li class="page-item" title="Taux de validation ce dernier jour ; total : {{ validationsstats.nbsubday }}"><a class="page-link text-success">{{ validationsstats.nbsubday / 1440 | number:2 }}</a></li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col">
|
<div class="col">
|
||||||
|
@ -179,6 +179,41 @@ func GetTries(t *Team, e *Exercice) (times []time.Time, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetValidations retrieves all flag validation made by the matching Team or challenge (both can be nil to not filter).
|
||||||
|
func GetValidations(t *Team, e *Exercice) (times []time.Time, err error) {
|
||||||
|
var rows *sql.Rows
|
||||||
|
|
||||||
|
if t == nil {
|
||||||
|
if e == nil {
|
||||||
|
rows, err = DBQuery("SELECT time FROM flag_found UNION SELECT time FROM mcq_found ORDER BY time ASC")
|
||||||
|
} else {
|
||||||
|
rows, err = DBQuery("SELECT time FROM flag_found WHERE id_exercice = ? UNION SELECT time FROM mcq_found WHERE id_exercice = ? ORDER BY time ASC", e.Id, e.Id)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if e == nil {
|
||||||
|
rows, err = DBQuery("SELECT time FROM flag_found WHERE id_team = ? UNION SELECT time FROM mcq_found WHERE id_team = ? ORDER BY time ASC", t.Id, t.Id)
|
||||||
|
} else {
|
||||||
|
rows, err = DBQuery("SELECT time FROM flag_found WHERE id_team = ? AND id_exercice = ? UNION SELECT time FROM mcq_found WHERE id_team = ? AND id_exercice = ? ORDER BY time ASC", t.Id, e.Id, t.Id, e.Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var tm time.Time
|
||||||
|
if err = rows.Scan(&tm); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
times = append(times, tm)
|
||||||
|
}
|
||||||
|
err = rows.Err()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetTryRank generates a special rank based on number of attempts
|
// GetTryRank generates a special rank based on number of attempts
|
||||||
func GetTryRank() ([]int64, error) {
|
func GetTryRank() ([]int64, error) {
|
||||||
if rows, err := DBQuery("SELECT id_team, COUNT(*) AS score FROM exercice_tries GROUP BY id_team HAVING score > 0 ORDER BY score DESC"); err != nil {
|
if rows, err := DBQuery("SELECT id_team, COUNT(*) AS score FROM exercice_tries GROUP BY id_team HAVING score > 0 ORDER BY score DESC"); err != nil {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user