Compare commits

..

69 Commits

Author SHA1 Message Date
3aea59f1ad chore(deps): update alpine docker tag to v3.21 2025-02-22 04:34:20 +00:00
526d693ffd chore(deps): update module golang.org/x/crypto to v0.33.0 2025-02-11 16:18:38 +00:00
df8a759134 chore(deps): update dependency sass to v1.84.0 2025-02-11 12:56:59 +00:00
03e48b749c chore(deps): update dependency @sveltejs/kit to v2.17.1 2025-02-11 12:56:55 +00:00
97f7e3fa59 chore(deps): update dependency @sveltestrap/sveltestrap to v7 2025-02-11 12:56:18 +00:00
e421c91ac2 chore(deps): update module github.com/go-git/go-git/v5 to v5.13.2 2025-02-11 12:56:05 +00:00
baccc54d02 chore(deps): update module golang.org/x/oauth2 to v0.26.0 2025-02-05 02:55:02 +00:00
e8e87c9958 chore(deps): update module golang.org/x/image to v0.24.0 2025-02-05 00:42:32 +00:00
08a31898df admin: New button to delete tries for a flag 2025-02-04 19:08:44 +01:00
b409fa6806 admin: Retrieve stats on exercices 2025-02-04 19:08:44 +01:00
63b4cdc622 admin: Use non-breakable whitespaces 2025-02-04 19:08:44 +01:00
650f933993 admin: duration change impact the expected end 2025-02-04 19:08:44 +01:00
603b226955 fickit: Prepare team registration through checker 2025-02-04 19:08:44 +01:00
55e829fa64 fickit: Allow admin to remove submissions 2025-02-04 19:08:44 +01:00
45a0504c44 chore(deps): lock file maintenance 2025-01-27 02:27:27 +00:00
ad7489e558 admin: Start compute flag stats 2025-01-25 14:46:47 +01:00
57c3cd8fd6 admin: Fix mcq entry update 2025-01-24 23:49:37 +01:00
24686a6a24 admin: Fix check file on disk for compressed files 2025-01-24 23:49:37 +01:00
407b67f4c2 sync: Ensure placeholder and raw are not the same 2025-01-24 23:49:37 +01:00
c28d974105 fickit: Update images 2025-01-24 23:49:37 +01:00
ffb69663b6 fickit: Initiate sshd config with keys on first run 2025-01-24 23:49:37 +01:00
4ec4f47951 fickit: keep last metadata iso when dm-crypt key change 2025-01-24 23:49:37 +01:00
96707e3a29 configs: Detect mkisofs 2025-01-24 23:49:37 +01:00
a4001759f6 ui: Fix file disclaimer not showing 2025-01-24 23:49:37 +01:00
f15cd29f78 chore(deps): update dependency @sveltestrap/sveltestrap to v6.2.8 2025-01-23 18:41:07 +00:00
7e41ddd664 chore(deps): update dependency eslint-config-prettier to v10 2025-01-23 18:40:58 +00:00
89b7710544 chore(deps): update dependency @sveltejs/kit to v2.16.1 2025-01-23 18:40:43 +00:00
79ec20d11c chore(deps): update dependency sass to v1.83.4 2025-01-21 14:57:58 +00:00
09206df20a chore(deps): update module golang.org/x/crypto to v0.32.0 2025-01-18 12:40:53 +00:00
a14c151b04 admin: Implement button to delete the entire FILES dir 2025-01-14 16:03:37 +01:00
68dad00930 ui: Force download of XML files 2025-01-14 16:03:37 +01:00
c74eadc801 admin: Also fill lastSyncError in autosync 2025-01-14 16:03:37 +01:00
Leo Blanc Di Pasquale
376e112130 Update authorized_keys 2025-01-14 16:00:45 +01:00
Maxence Michot
1d2a09f612 feat: added maxence.michot to the authorized_keys 2025-01-14 16:00:45 +01:00
Alexandra Delin
6a35bd6345 Update authorized_keys 2025-01-14 16:00:45 +01:00
Hugo Rubio
a463e88a90 Update authorized_keys 2025-01-14 16:00:45 +01:00
Victor Chartraire
3d066fbdeb Update authorized_keys 2025-01-14 16:00:45 +01:00
liryc116
7dd3f64a08 chore: added personnal authorized key 2025-01-14 16:00:45 +01:00
bd5050b24a admin: Fix missing replacement 2025-01-14 10:58:17 +01:00
0ab453811c qa: Bump version 2025-01-14 10:51:11 +01:00
e71dc24a27 qa: Improve work assignation 2025-01-14 10:45:01 +01:00
fb5147fac3 repochecker: Rely on archive.org to find grammalecte 2025-01-14 10:45:01 +01:00
f073ea0fd0 fickit: Update images 2025-01-13 22:36:26 +01:00
34cf1789f3 frontend: Sync lock file 2025-01-13 22:21:55 +01:00
9e75386038 qa: New to delete assigned work 2025-01-13 20:21:03 +01:00
a1b0e6a79b qa: Add logs to gitlab export 2025-01-13 20:21:03 +01:00
092d2256f7 qa: Improve manager dashboard 2025-01-13 20:21:03 +01:00
28b4e7e529 ui: Improve dev by specifying hmr port 2025-01-13 20:21:03 +01:00
32632322d4 qa: Update the reverse proxy 2025-01-13 20:21:03 +01:00
4473166ee7 qa: Try to fix GitLab connection return on live infra 2025-01-13 20:21:03 +01:00
12feb91d48 qa: Update lock file 2025-01-13 20:21:03 +01:00
03d02669ea admin: Refactor synchronization status report + display last git error 2025-01-13 20:21:03 +01:00
c1924c0e92 admin: Can delete a repository directory if needed 2025-01-13 20:21:03 +01:00
7692f92aa4 Readd disclaimer to my.json 2025-01-13 20:21:03 +01:00
724f985770 chore(deps): update module golang.org/x/oauth2 to v0.25.0 2025-01-04 16:38:23 +00:00
fa1b21e49f chore(deps): update module github.com/go-git/go-git/v5 to v5.13.1 2025-01-04 16:13:40 +00:00
6b9283f7ca chore(deps): update dependency @sveltejs/kit to v2.15.1 2025-01-04 16:13:27 +00:00
2a0a0dc9d4 chore(deps): update dependency sass to v1.83.1 2025-01-04 11:40:04 +00:00
ad6e59d8eb chore(deps): update dependency @sveltejs/adapter-static to v3.0.8 2025-01-04 11:09:14 +00:00
6a1120898b chore(deps): update module github.com/gin-contrib/sessions to v1.0.2 2025-01-04 11:08:58 +00:00
e178f7a80f chore(deps): update module github.com/asticode/go-astisub to v0.32.0 2025-01-04 11:08:33 +00:00
895e34fef4 chore(deps): lock file maintenance 2024-12-17 13:59:31 +00:00
148deb77ec chore(deps): update module github.com/studio-b12/gowebdav to v0.10.0 2024-12-17 11:52:22 +00:00
5321d499b2 chore(deps): lock file maintenance 2024-12-16 00:47:57 +00:00
f6d2794fbd chore(deps): lock file maintenance 2024-12-11 19:52:19 +00:00
ba37309ee5 chore(deps): update module golang.org/x/crypto to v0.31.0 2024-12-11 18:41:38 +00:00
6dfe4115c3 chore(deps): lock file maintenance 2024-12-09 00:49:21 +00:00
0296c9cc85 chore(deps): update module golang.org/x/image to v0.23.0 2024-12-06 16:57:48 +00:00
180fcbfa33 chore(deps): update module golang.org/x/crypto to v0.30.0 2024-12-05 21:40:59 +00:00
48 changed files with 1534 additions and 727 deletions

@ -23,7 +23,7 @@ RUN go get -d -v ./repochecker && \
ENV GRAMMALECTE_VERSION 2.1.1
ADD https://grammalecte.net/zip/Grammalecte-fr-v$GRAMMALECTE_VERSION.zip /srv/grammalecte.zip
ADD https://web.archive.org/web/20240926154729if_/https://grammalecte.net/zip/Grammalecte-fr-v$GRAMMALECTE_VERSION.zip /srv/grammalecte.zip
RUN mkdir /srv/grammalecte && cd /srv/grammalecte && unzip /srv/grammalecte.zip && sed -i 's/if sys.version_info.major < (3, 7):/if False:/' /srv/grammalecte/grammalecte-server.py

@ -62,6 +62,8 @@ func declareExercicesRoutes(router *gin.RouterGroup) {
apiFlagsRoutes.POST("/try", tryExerciceFlag)
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)
@ -77,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)
@ -852,6 +856,60 @@ func showExerciceFlagDeps(c *gin.Context) {
c.JSON(http.StatusOK, deps)
}
func showExerciceFlagStats(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
flag := c.MustGet("flag-key").(*fic.FlagKey)
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) == "flag_found" {
if *hline["secondary"].(*int) == flag.Id {
completed += 1
}
}
}
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,
"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)
@ -1022,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)

@ -5,6 +5,7 @@ import (
"fmt"
"log"
"net/http"
"os"
"path/filepath"
"strconv"
@ -143,12 +144,27 @@ func listFiles(c *gin.Context) {
}
func clearFiles(c *gin.Context) {
_, err := fic.ClearFiles()
err := os.RemoveAll(fic.FilesDir)
if err != nil {
log.Println("Unable to remove files:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
err = os.MkdirAll(fic.FilesDir, 0751)
if err != nil {
log.Println("Unable to create FILES:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
_, err = fic.ClearFiles()
if err != nil {
log.Println("Unable to clean DB files:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Les fichiers ont bien été effacés. Mais il n'a pas été possible d'effacer la base de données. Refaites une synchronisation maintenant. " + err.Error()})
return
}
c.JSON(http.StatusOK, true)
}

@ -41,5 +41,27 @@ func declareRepositoriesRoutes(router *gin.RouterGroup) {
}
c.JSON(http.StatusOK, mod)
})
router.DELETE("/repositories/*repopath", func(c *gin.Context) {
di, ok := sync.GlobalImporter.(sync.DeletableImporter)
if !ok {
c.AbortWithStatusJSON(http.StatusNotImplemented, gin.H{"errmsg": "Not implemented"})
return
}
if strings.Contains(c.Param("repopath"), "..") {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Repopath contains invalid characters"})
return
}
repopath := strings.TrimPrefix(c.Param("repopath"), "/")
err := di.DeleteDir(repopath)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
c.JSON(http.StatusOK, true)
})
}
}

@ -8,7 +8,6 @@ import (
"net/http"
"os"
"path"
"reflect"
"strconv"
"time"
@ -26,7 +25,6 @@ func declareSettingsRoutes(router *gin.RouterGroup) {
router.GET("/challenge.json", getChallengeInfo)
router.PUT("/challenge.json", saveChallengeInfo)
router.GET("/settings-ro.json", getROSettings)
router.GET("/settings.json", getSettings)
router.PUT("/settings.json", saveSettings)
router.DELETE("/settings.json", func(c *gin.Context) {
@ -98,24 +96,6 @@ func fullGeneration(c *gin.Context) {
})
}
func getROSettings(c *gin.Context) {
syncMtd := "Disabled"
if sync.GlobalImporter != nil {
syncMtd = sync.GlobalImporter.Kind()
}
var syncId *string
if sync.GlobalImporter != nil {
syncId = sync.GlobalImporter.Id()
}
c.JSON(http.StatusOK, gin.H{
"sync-type": reflect.TypeOf(sync.GlobalImporter).Name(),
"sync-id": syncId,
"sync": syncMtd,
})
}
func GetChallengeInfo() (*settings.ChallengeInfo, error) {
var challengeinfo string
var err error

@ -7,6 +7,7 @@ import (
"net/url"
"os"
"path"
"reflect"
"strings"
"srs.epita.fr/fic-server/admin/generation"
@ -17,6 +18,8 @@ import (
"go.uber.org/multierr"
)
var lastSyncError = ""
func flatifySyncErrors(errs error) (ret []string) {
for _, err := range multierr.Errors(errs) {
ret = append(ret, err.Error())
@ -27,12 +30,37 @@ func flatifySyncErrors(errs error) (ret []string) {
func declareSyncRoutes(router *gin.RouterGroup) {
apiSyncRoutes := router.Group("/sync")
// Return the global sync status
apiSyncRoutes.GET("/status", func(c *gin.Context) {
syncMtd := "Disabled"
if sync.GlobalImporter != nil {
syncMtd = sync.GlobalImporter.Kind()
}
var syncId *string
if sync.GlobalImporter != nil {
syncId = sync.GlobalImporter.Id()
}
c.JSON(http.StatusOK, gin.H{
"sync-type": reflect.TypeOf(sync.GlobalImporter).Name(),
"sync-id": syncId,
"sync": syncMtd,
"pullMutex": !sync.OneGitPullStatus(),
"syncMutex": !sync.OneDeepSyncStatus() && !sync.OneThemeDeepSyncStatus(),
"progress": sync.DeepSyncProgress,
"lastError": lastSyncError,
})
})
// Base sync checks if the local directory is in sync with remote one.
apiSyncRoutes.POST("/base", func(c *gin.Context) {
err := sync.GlobalImporter.Sync()
if err != nil {
lastSyncError = err.Error()
c.JSON(http.StatusExpectationFailed, gin.H{"errmsg": err.Error()})
} else {
lastSyncError = ""
c.JSON(http.StatusOK, true)
}
})
@ -45,15 +73,10 @@ func declareSyncRoutes(router *gin.RouterGroup) {
})
// Deep sync: a fully recursive synchronization (can be limited by theme).
apiSyncRoutes.GET("/deep", func(c *gin.Context) {
if sync.DeepSyncProgress == 0 {
c.AbortWithStatusJSON(http.StatusTooEarly, gin.H{"errmsg": "Pas de synchronisation en cours"})
return
}
c.JSON(http.StatusOK, gin.H{"progress": sync.DeepSyncProgress})
})
apiSyncRoutes.POST("/deep", func(c *gin.Context) {
c.JSON(http.StatusOK, sync.SyncDeep(sync.GlobalImporter))
r := sync.SyncDeep(sync.GlobalImporter)
lastSyncError = ""
c.JSON(http.StatusOK, r)
})
apiSyncDeepRoutes := apiSyncRoutes.Group("/deep/:thid")
@ -66,6 +89,7 @@ func declareSyncRoutes(router *gin.RouterGroup) {
}
sync.EditDeepReport(&sync.SyncReport{Exercices: st}, false)
sync.DeepSyncProgress = 255
lastSyncError = ""
c.JSON(http.StatusOK, st)
})
apiSyncDeepRoutes.POST("", func(c *gin.Context) {
@ -79,6 +103,7 @@ func declareSyncRoutes(router *gin.RouterGroup) {
}
sync.EditDeepReport(&sync.SyncReport{Themes: map[string][]string{theme.Name: st}}, false)
sync.DeepSyncProgress = 255
lastSyncError = ""
c.JSON(http.StatusOK, st)
})
@ -90,6 +115,7 @@ func declareSyncRoutes(router *gin.RouterGroup) {
apiSyncRoutes.POST("/themes", func(c *gin.Context) {
_, errs := sync.SyncThemes(sync.GlobalImporter)
lastSyncError = ""
c.JSON(http.StatusOK, flatifySyncErrors(errs))
})
@ -258,10 +284,12 @@ func autoSync(c *gin.Context) {
if !IsProductionEnv {
if err := sync.GlobalImporter.Sync(); err != nil {
lastSyncError = err.Error()
log.Println("Unable to sync.GI.Sync:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to perform the pull."})
return
}
lastSyncError = ""
}
themes, err := fic.GetThemes()

@ -230,9 +230,6 @@ angular.module("FICApp")
.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' },
@ -351,6 +348,9 @@ angular.module("FICApp")
.factory("ExerciceFlagDeps", function ($resource) {
return $resource("api/exercices/:exerciceId/flags/:flagId/dependancies", { exerciceId: '@idExercice', flagId: '@id' })
})
.factory("ExerciceFlagStats", function ($resource) {
return $resource("api/exercices/:exerciceId/flags/:flagId/statistics", { exerciceId: '@idExercice', flagId: '@id' })
})
.factory("ExerciceMCQFlag", function ($resource) {
return $resource("api/exercices/:exerciceId/quiz/:mcqId", { exerciceId: '@idExercice', mcqId: '@id' }, {
update: { method: 'PUT' }
@ -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()
@ -755,7 +767,27 @@ angular.module("FICApp")
$http.get("api/repositories").then(function (response) {
$scope.repositories = response.data.repositories;
});
$scope.deleteRepository = function(repo) {
$http.delete("api/repositories/" + repo.path).then(function (response) {
$scope.repositories[$scope.repositories.indexOf(repo)].hash = "- DELETED -";
});
};
})
.component('teamLink', {
bindings: {
idTeam: '=',
},
controller: function (Team) {
var ctrl = this;
ctrl.team = {};
ctrl.$onInit = function () {
ctrl.team = Team.get({teamId: ctrl.idTeam});
};
},
template: `<a href="/teams/{{ $ctrl.idTeam }}">{{ $ctrl.team.name }}</a> `
})
.component('repositoryUptodate', {
bindings: {
repository: '<',
@ -781,9 +813,8 @@ angular.module("FICApp")
template: `<span class="badge {{ $ctrl.color }}">{{ $ctrl.status.hash }}</span> <small>{{ $ctrl.status.text }}</small>`
})
.controller("SyncController", function ($scope, $rootScope, ROSettings, $location, $http, $interval) {
.controller("SyncController", function ($scope, $rootScope, $location, $http, $interval) {
$scope.displayDangerousActions = false;
$scope.configro = ROSettings.get();
var needRefreshSyncReportWhenReady = false;
var refreshSyncReport = function () {
@ -794,36 +825,34 @@ angular.module("FICApp")
};
refreshSyncReport()
$scope.deepSyncInProgress = false;
var progressInterval = $interval(function () {
$http.get("api/sync/deep").then(function (response) {
$http.get("api/sync/status").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) + " %";
$scope.deepSyncInProgress = response.data.pullMutex && response.data.syncMutex;
} else {
$scope.syncProgress = response.data;
$scope.syncPercent = 0;
}
$scope.syncStatus = response.data;
}, function (response) {
$scope.syncPercent = 0;
if (response.data && response.data.errmsg)
$scope.syncProgress = response.data.errmsg;
else
$scope.syncProgress = response.data;
$scope.syncStatus = 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 + ' ?'
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, '',
@ -839,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 () {
@ -852,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 () {
@ -865,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 () {
@ -1110,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 = [
@ -1229,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") {
@ -1238,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!",
}
}
};
@ -1358,6 +1387,7 @@ angular.module("FICApp")
$scope.files = File.query();
$scope.errfnd = null;
$scope.errzip = null;
$scope.clearFilesWIP = false;
$scope.fields = ["id", "path", "name", "size"];
$scope.clearFiles = function (id) {
@ -1366,6 +1396,21 @@ angular.module("FICApp")
$scope.files = [];
});
};
$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.",
function () {
$scope.clearFilesWIP = true;
$http({
url: "api/files",
method: "DELETE"
}).then(function (response) {
$scope.clearFilesWIP = false;
}, function (response) {
$scope.clearFilesWIP = false;
$scope.addToast('danger', 'An error occurs when trying to clear files:', response.data.errmsg);
});
});
};
$scope.gunzipFile = function (f) {
f.gunzipWIP = true;
$http({
@ -2260,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);
@ -2329,6 +2374,18 @@ angular.module("FICApp")
}
})
.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) {
$scope.quiz = ExerciceMCQFlag.query({ exerciceId: $routeParams.exerciceId });
@ -2366,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"];

@ -73,12 +73,28 @@
</div>
<div class="col-4">
<div ng-controller="ExerciceFlagDepsController" ng-init="init(flag)">
Dépendances&nbsp;:
<strong>Dépendances&nbsp;:</strong>
<ul ng-if="deps.length > 0">
<dependancy ng-repeat="dep in deps" dep="dep"></dependancy>
</ul>
<span ng-if="deps.length == 0"> sans</span>
</div>
<hr>
<div ng-controller="ExerciceFlagStatsController" ng-init="init(flag)">
<strong>Statistiques</strong>
<ul>
<li>Validés: {{ stats["completed"] }}</li>
<li>
Tentés: {{ stats["tries"] }}
<button type="button" ng-click="deleteTries()" class="btn btn-sm btn-danger" ng-if="stats['tries'] > 0"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span></button>
</li>
<li>
Équipes:
<span ng-if="stats['teams'].length == 0">aucune</span>
<team-link ng-repeat="team in stats['teams']" id-team="team"></team-link>
</li>
</ul>
</div>
</div>
<div class="col-4" ng-controller="ExerciceFlagChoicesController">
<div class="btn-toolbar justify-content-end mb-2" role="toolbar">
@ -168,6 +184,22 @@
<dependancy ng-repeat="dep in deps" dep="dep"></dependancy>
</ul>
<span ng-if="deps.length == 0"> sans</span>
<hr>
<div ng-controller="ExerciceMCQStatsController" ng-init="init(q)">
<strong>Statistiques</strong>
<ul>
<li>Validés: {{ stats["completed"] }}</li>
<li>
Tentés: {{ stats["tries"] }}
<button type="button" ng-click="deleteTries()" class="btn btn-sm btn-danger" ng-if="stats['tries'] > 0"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span></button>
</li>
<li>
Équipes:
<span ng-if="stats['teams'].length == 0">aucune</span>
<team-link ng-repeat="team in stats['teams']" id-team="team"></team-link>
</li>
</ul>
</div>
</div>
</form>
</div>

@ -4,6 +4,7 @@
<small class="text-muted" ng-if="errzip > 0"><span class="glyphicon glyphicon-exclamation-sign"></span> <ng-pluralize count="errzip" when="{'one': '{} décompression problématique', 'other': '{} décompressions problématiques'}"></ng-pluralize></small>
<button type="button" ng-click="checksumAll()" class="float-right btn btn-sm" ng-class="{'btn-secondary': errfnd === null, 'btn-success': errfnd === 0, 'btn-danger': errfnd > 0}"><span class="glyphicon glyphicon-flash" aria-hidden="true"></span> Vérifier les fichiers</button>
<button type="button" ng-click="gunzipFiles()" class="float-right btn btn-sm mx-1" ng-class="{'btn-secondary': errzip === null, 'btn-success': errzip === 0, 'btn-danger': errzip > 0}" title="Décompresse tous les fichiers compressés afin d'afficher la bonne taille au moment du téléchargement"><span class="glyphicon glyphicon-compressed" aria-hidden="true"></span> Gunzip</button>
<button type="button" ng-click="clearFilesDir()" class="float-right btn btn-danger btn-sm mx-1" title="Supprime l'arborescence des fichiers"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Nuke files</button>
</h2>
<p><input type="search" class="form-control" placeholder="Search" ng-model="query" autofocus></p>
@ -29,13 +30,13 @@
<div class="spinner-border spinner-border-sm" role="status" ng-if="file.gunzipWIP"></div>
</button>
</td>
<td ng-repeat="field in fields">
<td ng-repeat="field in fields" class="text-truncate" style="max-width: 30vw" title="{{ file[field] }}">
{{ file[field] }}
<span ng-if="field == 'id' && file.err !== undefined && file.err !== true" title="{{ file.err }}" class="glyphicon glyphicon-exclamation-sign"></span>
</td>
<td>
{{ file.checksum | bto16 }}
<div ng-if="file.checksum_shown">{{ file.checksum_shown | bto16 }}</div>
<td style="max-width: 100px">
<div class="text-truncate" title="{{ file.checksum | bto16 }}">{{ file.checksum | bto16 }}</div>
<div class="text-truncate" ng-if="file.checksum_shown" title="{{ file.checksum_shown | bto16 }}">{{ file.checksum_shown | bto16 }}</div>
</td>
</tr>
</tbody>

@ -9,16 +9,20 @@
<tr>
<th>Chemin</th>
<th>Branche</th>
<th>Commit</th>
<th>Plus récent</th>
<th>Commit <span class="text-muted">Plus récent</span></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="repository in repositories">
<td>{{ repository.path }}</td>
<td>{{ repository.branch }}</td>
<td>{{ repository.hash }}</td>
<td><repository-uptodate repository="repository" /></td>
<td>
{{ repository.hash }}<br>
<repository-uptodate repository="repository" />
</td>
<td>
<button type="button" ng-click="deleteRepository(repository)" class="btn btn-sm btn-danger"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span></button>
</td>
</tr>
</tbody>
</table>

@ -35,7 +35,7 @@
<label for="startTime" class="col-sm-3 col-form-label col-form-label-sm" ng-class="{'text-primary font-weight-bold': config.start != dist_config.start}">Début du challenge</label>
<div class="col-sm-9">
<div class="input-group">
<input type="datetime-local" class="form-control form-control-sm" id="startTime" ng-model="config.start" ng-class="{'border-primary': config.start != dist_config.start}">
<input type="datetime-local" class="form-control form-control-sm" id="startTime" ng-model="config.start" ng-change="durationChange()" ng-class="{'border-primary': config.start != dist_config.start}">
<div class="input-group-append">
<button ng-click="launchChallenge()" class="btn btn-sm btn-secondary" type="button"><span class="glyphicon glyphicon-play" aria-hidden="true"></span> Lancer le challenge</button>
</div>
@ -46,14 +46,14 @@
<div class="form-group row">
<label for="endTime" class="col-sm-3 col-form-label col-form-label-sm" ng-class="{'text-primary font-weight-bold': config.end != dist_config.end}">Fin du challenge</label>
<div class="col-sm-6">
<input type="datetime-local" class="form-control form-control-sm" id="endTime" ng-model="config.end" ng-class="{'border-primary': config.end != dist_config.end}">
<input type="datetime-local" class="form-control form-control-sm" id="endTime" ng-model="config.end" ng-change="durationChange(true)" ng-class="{'border-primary': config.end != dist_config.end}">
</div>
<div class="col-sm-1 text-right">
<label for="duration" class="col-form-label col-form-label-sm">Durée</label>
</div>
<div class="col-sm-2">
<div class="input-group input-group-sm">
<input type="text" class="form-control form-control-sm" id="duration" ng-model="duration" integer>
<input type="number" class="form-control form-control-sm" id="duration" ng-model="duration" ng-change="durationChange()" integer>
<div class="input-group-append">
<span class="input-group-text">min</span>
</div>

@ -22,7 +22,7 @@
<div class="badge badge-success align-self-center" ng-if="syncReport" title="{{ syncReport._updated[syncReport._updated.length-1] }}">
Dernier import&nbsp;: {{ syncReport._updated[syncReport._updated.length-1] | date:"medium" }}
</div>
<a ng-if="configro['sync-type'] === 'GitImporter'" href="repositories" class="btn btn-secondary">
<a ng-if="syncStatus['sync-type'] === 'GitImporter'" href="repositories" class="btn btn-secondary">
Voir les dépôts
</a>
</div>
@ -32,17 +32,25 @@
<div class="card-body">
<dl class="row">
<dt class="col-2">Type</dt>
<dd class="col-10" ng-bind="configro['sync-type']"></dd>
<dd class="col-10" ng-bind="syncStatus['sync-type']"></dd>
<dt class="col-2">Synchronisation</dt>
<dd class="col-10" title="{{ configro['sync'] }}" ng-bind="configro.sync"></dd>
<dt class="col-2" ng-if="configro['sync-id']">ID</dt>
<dd class="col-10" ng-if="configro['sync-id']">{{ configro['sync-id'] }}</dd>
<dt class="col-2" ng-if="configro['sync']">Statut</dt>
<dd class="col-10" ng-if="configro['sync']">{{ syncProgress }}</dd>
<dd class="col-10" title="{{ syncStatus['sync'] }}" ng-bind="syncStatus.sync"></dd>
<dt class="col-2" ng-if="syncStatus['sync-id']">ID</dt>
<dd class="col-10" ng-if="syncStatus['sync-id']">
{{ syncStatus['sync-id'] }}
<button ng-if="syncStatus['sync-type'] === 'GitImporter'" type="button" class="btn btn-sm btn-dark" ng-click="baseSync()" ng-disabled="deepSyncInProgress"><span class="glyphicon glyphicon-repeat" aria-hidden="true"></span> Pull</button>
</dd>
<dt class="col-2" ng-if="syncStatus['sync']">Statut</dt>
<dd class="col-10" ng-if="syncStatus['sync']">
<span ng-if="(syncStatus.syncMutex !== undefined && syncStatus.syncMutex) || (syncPercent > 0 && syncPercent < 100)">{{ syncPercent }}&nbsp;%</span>
<span class="badge badge-pill" ng-class="{'badge-success': !syncStatus.pullMutex, 'badge-danger': syncStatus.pullMutex}" ng-if="syncStatus.pullMutex !== undefined">Pull</span>
<span class="badge badge-pill" ng-class="{'badge-success': !syncStatus.syncMutex, 'badge-danger': syncStatus.syncMutex}" ng-if="syncStatus.syncMutex !== undefined">Synchronisation</span>
</dd>
</dl>
<div class="d-flex justify-content-around" ng-if="configro.sync">
<button ng-if="configro['sync-type'] === 'GitImporter'" type="button" class="btn btn-info" ng-click="baseSync()" ng-disabled="deepSyncInProgress"><span class="glyphicon glyphicon-repeat" aria-hidden="true"></span> Pull</button>
<pre style="background: black; color: white;" class="p-2" ng-if="syncStatus.lastError">{{ syncStatus.lastError }}</pre>
<div class="d-flex justify-content-around" ng-if="syncStatus.sync">
<div class="btn-group dropright">
<button type="button" class="btn btn-secondary" ng-click="deepSync()" ng-disabled="deepSyncInProgress"><span class="glyphicon glyphicon-import" aria-hidden="true"></span> Synchronisation intégrale</button>
<button type="button" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" ng-disabled="deepSyncInProgress">

@ -279,6 +279,7 @@ func buildKeyFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, defau
}
type importFlag struct {
origin ExerciceFlag
Line int
Flag fic.Flag
JustifyOf *fic.MCQ_entry
@ -392,8 +393,9 @@ func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nl
errs = multierr.Append(errs, berrs)
if addedFlag != nil {
ret = append(ret, importFlag{
Line: nline + 1,
Flag: addedFlag,
origin: flag,
Line: nline + 1,
Flag: addedFlag,
})
}
} else if flag.Type == "key" || strings.HasPrefix(flag.Type, "number") || flag.Type == "text" || flag.Type == "ucq" || flag.Type == "radio" || flag.Type == "vector" {
@ -401,6 +403,7 @@ func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nl
errs = multierr.Append(errs, berrs)
if addedFlag != nil {
ret = append(ret, importFlag{
origin: flag,
Line: nline + 1,
Flag: *addedFlag,
Choices: choices,
@ -462,6 +465,7 @@ func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nl
errs = multierr.Append(errs, berrs)
if addedFlag != nil {
ret = append(ret, importFlag{
origin: flag,
Line: nline + 1,
Flag: *addedFlag,
JustifyOf: entry,
@ -479,8 +483,9 @@ func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nl
}
ret = append([]importFlag{importFlag{
Line: nline + 1,
Flag: &addedFlag,
origin: flag,
Line: nline + 1,
Flag: &addedFlag,
}}, ret...)
}
return
@ -570,6 +575,10 @@ func CheckExerciceFlags(i Importer, exercice *fic.Exercice, files []string, exce
if int64(fk.ChoicesCost) >= exercice.Gain {
errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("flag's choice_cost is higher than exercice gain")))
}
if raw, ok := flag.origin.Raw.(string); ok && raw == fk.Placeholder {
errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("flag's placeholder and raw are identical")))
}
}
// Check dependency loop

@ -24,6 +24,22 @@ var oneThemeDeepSync sync.Mutex
// DeepSyncProgress expose the progression of the depp synchronization (0 = 0%, 255 = 100%).
var DeepSyncProgress uint8
func OneDeepSyncStatus() bool {
if oneDeepSync.TryLock() {
oneDeepSync.Unlock()
return true
}
return false
}
func OneThemeDeepSyncStatus() bool {
if oneThemeDeepSync.TryLock() {
oneThemeDeepSync.Unlock()
return true
}
return false
}
type SyncReport struct {
DateStart time.Time `json:"_started"`
DateEnd time.Time `json:"_ended"`

@ -12,6 +12,14 @@ var gitRemoteRe = regexp.MustCompile(`^(?:(?:git@|https://)([\w.@]+)(?:/|:))((?:
var oneGitPull sync.Mutex
func OneGitPullStatus() bool {
if oneGitPull.TryLock() {
oneGitPull.Unlock()
return true
}
return false
}
func countFileInDir(dirname string) (int, error) {
files, err := os.ReadDir(dirname)
if err != nil {
@ -57,6 +65,10 @@ func (i GitImporter) Kind() string {
return "git originated from " + i.Remote + " on " + i.li.Kind()
}
func (i GitImporter) DeleteDir(filename string) error {
return i.li.DeleteDir(filename)
}
func getForgeBaseLink(remote string) (u *url.URL, err error) {
res := gitRemoteRe.FindStringSubmatch(remote)
u, err = url.Parse(res[2])

@ -113,3 +113,11 @@ func (i LocalImporter) ListDir(filename string) ([]string, error) {
func (i LocalImporter) Stat(filename string) (os.FileInfo, error) {
return os.Stat(path.Join(i.Base, filename))
}
type DeletableImporter interface {
DeleteDir(filename string) error
}
func (i LocalImporter) DeleteDir(filename string) error {
return os.RemoveAll(path.Join(i.Base, filename))
}

@ -20,3 +20,24 @@ ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCUKevt/f1n2byv5oH43iQsZ7b4kAATHlHNUF6WMQjk
# ?
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICo5yumHfQbMwhZAtEZByQR0xIVcoealS7g4MNTMEVaX roote@roote-VirtualBox
# cyril.blin
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCit9xqC+/EcC0D+x4Irg/AgnTx9rJDbE4FdKK2Z2vE0nSPzp1MbAtijVi5ndvr/JPlY3jUHeGEZBJHmADXeOvdJl1nvkqry/69phfr4nDYacvH0v66nDTRipqmCmebaYOkfXYG55oy40+6C6DwAGETTYq+PIaRcA/mSf6V9UxKBfnLVqdml7LFYEo1SbihAIFd0EZkwq1wevXdVmrDwF7VLiCin/5Axa6LUOe4l1SAYBpsV8pCY3PQ/KxpgCyJuYj2szhOl0shTPiV48f194xGtYrpx1uGhOHRDx6Rm/5LKY/5DUvKbHCa/ZAdUSoMTnd1TshAPJe1sYKSAAI1xPVmffOgF/Jh98QEuAuFmHfZXVgPdvApJ9r9Ea7gEyN6Xe37emkW1Dond4ARdNdaslVu0iwV6bQnDOGcEdAl3x3seRVRAiPAKp2tAEtVEqu7uFwX6v2mmpE5/uw8rfsl/wNgttjuYa/kJURNkto3bN02XNfzOnXXZ3bRtbNjHEyJQuM= cyril@nixos
# victor.chartraire
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJgDuQ4FrDuDjoo1Xv7pA0WEev1hhgJ8lXpT/17QXsds celian@DESKTOP-BQA641E
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPfUVWWSUrfuQYofWwvotQip4uqG34dF1Ybn4tTU1l0n celian@DESKTOP-BQA641E
# hugo.rubio
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC68cnL/66U9dKfGZe3Bfr+ggosVXZh5OymRuHEdl26lYr3uJAQ7tKtfgXXyfuIW87vxGuzZUOEAnDafJx0J24BVyTwNjJVRarB8efHVIvlL/S9hHPVKWaQyo1ZIgizCiRdljJLWweZKjRE6Yr8xPc6zlC7grQ0wG/WGtAKaedhxYUZcCkdxPMz9Jf2ufx23DB/ALUZ63KVwbWIof8LPtlBP3G74ooeuykR+BTH+6NinoOWlL94JsSTAhuVnKsSnG1eQ742Cv4XhhGleAStP3wFhyakdsvCOfw9bKN/z3dXA0nZGzwOqvHyI9OJx01GLHhyykfRL7PtpD8MMwzyq3r5 hugo0@LAPTOP-C4LN612T
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA1BINotXNgrTmHrep0BbxtUIAvCXOjSN8GrfTq03Sfd ryugo@LAPTOP-C4LN612T
# alexandra.delin
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOJGg902VI7w6nOSiGm6qyUnPn9w3sD3VKlMin3fbYK6 alex@AlexandraSRS
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIArw17NpbJRNf8m9eji5K+GcD8oiyJi/3ygceojEVtBL alexa@Alexandra_SRS
# maxence.michot
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCqiBbRBWhnG5+LU/pKevR5oJV3+2ZQRQ6Fu87F8873DkKP0okvAYqVXYEDIm/bq9+VocZHI8y9O+zbCXwIqdWRGESJFiM7r1L3HtimFlmWhzM+nzduS8dWGcGGa9XCw+OBHFMGUFEMyzNbi/YcF7QGybOGETdcyzXQng1JtW4IURdrFJm/0/EavRwO2O2xTexmHv8l7JAe0ChteeuDatADrWgNnoUXIqeQQIsPxKQJ9D4QZ7+QrD5K2iLCx8sqC8Eu10p7cRqB3pCcBzKc6MEOgA1FXJzYgNtWkkLR9s7cOVjKL6mL3DuYQla3VaSvkTh8SAKEkadgYXpYifF6ygL298EmhQIrqRdjDYAm+VDGfCvmtcEW4NM68tt74xLuhdDutLq9Riqx4n2pGPo8ws8ecCokcu5/ESGxpZwYmY+nMj8392yH4sgmW/nrs0WmAqawdwlDj09LIqbYXiGMd5zYYOu8V2nuMKG0zRtRESKbs04Uw2DUd/gTwfP2l5YqTn3K93xDNU2iZY+yLJai3vYaBLHzt6PEvESdhAceYBMTIN2iA6hGZfaMWJ4JHImoqCgwbnJw6qrIPHaj1SCWKWLPMjjYZbwndkH3Iy4oJ6gA6I/vT9YrzNvGYY7rVspa+O0RM+huVH1a0YoXSSzx6dop1k1ZWm7VwRCUJQ2ri+L2jw== thecr@maxence-portable-desktop
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMwyRf67AH/rUzSFsKpFfGDD/La4JqpYk9xCM04kJ6+P maxence@vokunaav
# leo.blanc-di-pasquale
ssh-rsa AAAAB4NzaC1yc2EAAAADAQABAAABgQDBovLxpJA0+gJiAVWrpWmjy9fyEQwOXv3Ytou7C4Y1O3lAyvmmHfBWuW1ZqIY/8yKIpJeSLCoGeN5kyMD9ITvMVcfSAxnDHXj04Q4rJzBGjQ/LF/CInx+HHjJWzGVgJC76m0BS7J0J7g5gAxfprGKEb3Z1kJmCTRQ470Azv6WIPDCw7aLNd1o5JqAsrmWSh/LwYBSapyp3Tk0KjcbxRifvnBJfYLHwNm1ZoaLCGAT3u3TKIifawEomx5QEel/211FaEdbglkeDtRL0YuvKQSuZCf4KSg7xSRQoWHmSuPothE9eQGzEKpJONKViqkxBo12O04cBEhYzQBh7GHmH3U3/9weNUX4EaKJWkfqh43eAnWohN07IXDnANYPRxWL4ITv+MNBWxYL5Zp3Zr85rhJZYhkyKEfcGjvolHaESegnsndhC74QW3eOXazuscAeROQbiMShk5iBIceTsEFTqI02/+XqTs9gXkuq3H3StJ6+9cavDwRyObGx5nVHmhQYnMEE= leo21@Aled

@ -20,6 +20,8 @@ escape_newline () {
sed 's/$/\\n/g' | tr -d '\n'
}
which mkisofs > /dev/null 2> /dev/null || { echo "Please install genisoimage (Debian/Ubuntu) or cdrkit (Alpine)" >&2; exit 1; }
if [ $# -gt 0 ]
then
which jq > /dev/null 2> /dev/null || { echo "Please install jq" >&2; exit 1; }

@ -21,6 +21,8 @@ OLD_KEY=$(cat /run/config/dm-crypt/key)
[ "${NEW_KEY}" != "${OLD_KEY}" ] && {
read -p "DM-CRYPT key changed in metadata, are you sure you want to erase it? (y/N) " V
[ "$V" != "y" ] && [ "$V" != "Y" ] && while true; do
mv /boot/imgs/fickit-metadata.iso /boot/imgs/fickit-metadata.iso.skipped
cp /boot/imgs/fickit-metadata.iso.bak /boot/imgs/fickit-metadata.iso
echo
echo "Metadata drive not erased"
echo

@ -4,51 +4,51 @@ kernel:
cmdline: "console=ttyS0 console=tty0"
init:
- linuxkit/init:v1.0.0
- linuxkit/runc:6062483d748609d505f2bcde4e52ee64a3329f5f
- linuxkit/containerd:v1.0.0
- linuxkit/ca-certificates:v1.0.0
- linuxkit/getty:v1.0.0
- linuxkit/init:7135424f6836ee166d1199e88cfb95ee88efaf91
- linuxkit/runc:efcece75889aec4e2de0d95ba27ccc46438522b3
- linuxkit/containerd:ce79d5d4ab9c46f4763735c6e4ab5c51c3feb5d8
- linuxkit/ca-certificates:d4cc1b82c73d272e94d0e71ea375fe56b0c0626a
- linuxkit/getty:bae9e3d4861173bacf78f14a4fe44997a430d13b
- nemunaire/mdadm:04814350d71ba9417e1f861be1685de26adf7a67
- nemunaire/kexec:839b4eedfce02a56c581dec2383dc6faff120855
onboot:
- name: mod
image: linuxkit/modprobe:v1.0.0
image: linuxkit/modprobe:e3de97ac10970edee33faa78d9780117174bd1ac
command: ["/bin/sh", "-c", "modprobe xhci_pci ahci intel_lpss_pci i2c_i801 megaraid_sas tg3 bnxt_en"]
- name: sysctl
image: linuxkit/sysctl:v1.0.0
image: linuxkit/sysctl:c5f4b4895844b993dce4e8b35fd8263a6b557807
binds:
- /etc/sysctl.d/01-fic.conf:/etc/sysctl.d/01-fic.conf:ro
# Metadata
- name: metadata
image: linuxkit/metadata:v1.0.0
image: linuxkit/metadata:f35b5aafc7d19bb6a44a900840727902dad78e44
command: ["/usr/bin/metadata", "-v", "cdrom"]
# Filesystem
- name: swap
image: linuxkit/swap:v1.0.0
image: linuxkit/swap:8a1fd15d56b6ddf67d6d8ce25361178e1f36128b
command: ["/sbin/swapon", "/dev/sda3"]
- name: dm-crypt
image: linuxkit/dm-crypt:d49723bc9d10c5ada9e03b0670f4e57416d5d084
image: linuxkit/dm-crypt:19fa6affe9da03afc91694e36d72a4924c65a0e0
command: ["/usr/bin/crypto", "-l", "crypt_fic", "/dev/sda4"]
binds:
- /dev:/dev
- /run/config/dm-crypt:/etc/dm-crypt
- name: mount
image: linuxkit/mount:v1.0.0
image: linuxkit/mount:4413ebd50bfbe026058e4a60463259cece2b8bb5
command: ["/usr/bin/mountie", "-device", "/dev/mapper/crypt_fic", "/var/lib/fic" ]
# Network
# - name: dhcpcd
# image: linuxkit/dhcpcd:v1.0.0
# image: linuxkit/dhcpcd:330839488cd122db3c44738e265c035c9729a963
# command: ["/sbin/dhcpcd", "--nobackground", "-f", "/dhcpcd.conf", "-1"]
# - name: ntp
# image: linuxkit/openntpd:v1.0.0
# image: linuxkit/openntpd:da26954c2f98a274091e5ed0bbdd2079a77a47c1
- name: synchro-ip-setup
image: linuxkit/ip:v1.0.0
image: linuxkit/ip:af77c3f93143ff352a07ad5233d25a665012bcce
command: ["/bin/sh", "-c", "ip a add 10.10.10.1/29 dev eth2; ip link set eth2 up;" ]
net: new
runtime:
@ -57,7 +57,7 @@ onboot:
bindNS:
net: /run/netns/synchro
- name: qa-ip-setup
image: linuxkit/ip:v1.0.0
image: linuxkit/ip:af77c3f93143ff352a07ad5233d25a665012bcce
command: ["/bin/sh", "-c", "ip link show eth1 2> /dev/null && { ip a add 10.10.10.1/29 dev eth1; ip link set eth1 up; }; ip a add 172.17.0.6/24 dev vethin-qa; ip link set vethin-qa up" ]
net: new
runtime:
@ -69,7 +69,7 @@ onboot:
bindNS:
net: /run/netns/fic-qa
- name: admin-ip-setup
image: linuxkit/ip:v1.0.0
image: linuxkit/ip:af77c3f93143ff352a07ad5233d25a665012bcce
#command: ["/bin/sh", "-c", "ip link add link eth3 name adminiface type vlan id 99; ip a add 172.16.99.219/24 dev adminiface; ip link set eth3 up; ip link set adminiface up; ip r add default via 172.16.99.1; ip a add 172.17.0.2/24 dev vethin-admin; ip link set vethin-admin up; ping -W 10 -c 1 172.16.99.1;" ]
command: ["/bin/sh", "-c", "ip link set eth3 up; while read IP; do ip a add ${IP} dev eth3; done < /run/config/ip_config/backend-admin; ip r add default via $(cat /run/config/ip_config/backend-router); ip a add 172.17.0.2/24 dev vethin-admin; ip link set vethin-admin up; echo 'Waiting for' $(cat /run/config/ip_config/backend-router); ping -W 10 -c 1 $(cat /run/config/ip_config/backend-router); ip link show eth1 2> /dev/null && { ip a add 10.0.0.1/24 dev eth1; ip link set eth1 up; };" ]
net: new
@ -85,7 +85,7 @@ onboot:
bindNS:
net: /run/netns/fic-admin
- name: checker-ip-setup
image: linuxkit/ip:v1.0.0
image: linuxkit/ip:af77c3f93143ff352a07ad5233d25a665012bcce
command: ["/bin/sh", "-c", "ip a add 172.17.0.3/24 dev vethin-checker; ip link set vethin-checker up;" ]
net: new
runtime:
@ -96,7 +96,7 @@ onboot:
bindNS:
net: /run/netns/fic-checker
- name: generator-ip-setup
image: linuxkit/ip:v1.0.0
image: linuxkit/ip:af77c3f93143ff352a07ad5233d25a665012bcce
command: ["/bin/sh", "-c", "ip a add 172.17.0.5/24 dev vethin-generat; ip link set vethin-generat up;" ]
net: new
runtime:
@ -107,7 +107,7 @@ onboot:
bindNS:
net: /run/netns/fic-generator
- name: mysql-ip-setup
image: linuxkit/ip:v1.0.0
image: linuxkit/ip:af77c3f93143ff352a07ad5233d25a665012bcce
command: ["/bin/sh", "-c", "ip a add 172.17.0.4/24 dev vethin-db; ip link set vethin-db up;" ]
net: new
runtime:
@ -118,7 +118,7 @@ onboot:
bindNS:
net: /run/netns/db
- name: bridge-setup
image: linuxkit/ip:v1.0.0
image: linuxkit/ip:af77c3f93143ff352a07ad5233d25a665012bcce
command: ["/bin/sh", "-c", "ip a add 172.17.0.1/24 dev br0; ip link set veth-admin master br0; ip link set veth-checker master br0; ip link set veth-generator master br0; ip link set veth-db master br0; ip link set veth-qa master br0; ip link set br0 up; ip link set veth-admin up; ip link set veth-checker up; ip link set veth-generator up; ip link set veth-db up; ip link set veth-qa up;" ]
runtime:
interfaces:
@ -126,7 +126,7 @@ onboot:
add: bridge
- name: firewall-synchro
image: linuxkit/ip:v1.0.0
image: linuxkit/ip:af77c3f93143ff352a07ad5233d25a665012bcce
command: ["/bin/bash", "-c", "/sbin/iptables-restore < /etc/iptables/rules-synchro.v4; /sbin/ip6tables-restore < /etc/iptables/rules.v6" ]
binds:
- /etc/iptables/rules-synchro.v4:/etc/iptables/rules-synchro.v4:ro
@ -136,7 +136,7 @@ onboot:
mkdir:
- /var/lib/fic/teams
- name: firewall-admin
image: linuxkit/ip:v1.0.0
image: linuxkit/ip:af77c3f93143ff352a07ad5233d25a665012bcce
command: ["/bin/bash", "-c", "/sbin/iptables-restore < /etc/iptables/rules-admin.v4; /sbin/ip6tables-restore < /etc/iptables/rules.v6" ]
binds:
- /etc/iptables/rules-admin.v4:/etc/iptables/rules-admin.v4:ro
@ -153,17 +153,26 @@ onboot:
mkdir:
- /var/lib/fic/secrets
- name: create-ssh-keys
image: nemunaire/rsync:a3d76b2dd0a9ad73be44dc77ad765b20d96a3285
command: ["/bin/sh", "-c", "touch /etc/ssh/sshd_config && ssh-keygen -A"]
binds:
- /var/lib/fic/ssh:/etc/ssh
runtime:
mkdir:
- /var/lib/fic/ssh
services:
# - name: getty
# image: linuxkit/getty:v1.0.0
# image: linuxkit/getty:bae9e3d4861173bacf78f14a4fe44997a430d13b
# env:
# - INSECURE=true
# Enable acpi to shutdown on power events
- name: acpid
image: linuxkit/acpid:v1.0.0
image: linuxkit/acpid:6379700e2f3341250432e37a4cac36e35c7caac8
- name: rngd
image: linuxkit/rngd:v1.0.0
image: linuxkit/rngd:814d1a3a76e84eae01a94575c038fd22652f94e3
- name: db
image: mariadb:11
command: ["/bin/bash", "/usr/local/bin/docker-entrypoint.sh", "mariadbd"]
@ -228,7 +237,7 @@ services:
- /var/lib/fic/generator:/srv/GENERATOR:ro
- /var/lib/fic/pki:/srv/PKI
- /var/lib/fic/settings:/srv/SETTINGS
- /var/lib/fic/submissions:/srv/submissions:ro
- /var/lib/fic/submissions:/srv/submissions
- /var/lib/fic/sync:/srv/SYNC
- /var/lib/fic/teams:/srv/TEAMS
net: /run/netns/fic-admin
@ -269,7 +278,10 @@ services:
binds:
- /etc/hosts:/etc/hosts:ro
- /var/lib/fic/generator:/srv/GENERATOR:ro
# Uncomment this to disallow registrations
- /var/lib/fic/teams:/srv/TEAMS:ro
# Uncomment this to allow registrations
#- /var/lib/fic/teams:/srv/TEAMS
- /var/lib/fic/secrets/mysql_password:/run/secrets/mysql_password:ro
- /var/lib/fic/settingsdist:/srv/SETTINGSDIST:ro
- /var/lib/fic/submissions:/srv/submissions
@ -361,7 +373,6 @@ services:
- /var/lib/fic/files
- /var/lib/fic/pki/shared
- /var/lib/fic/settingsdist
- /var/lib/fic/ssh
- /var/lib/fic/submissions
- /var/lib/fic/teams
- /var/log/frontend

@ -4,47 +4,47 @@ kernel:
cmdline: "console=ttyS0 console=tty0"
init:
- linuxkit/init:v1.0.0
- linuxkit/runc:6062483d748609d505f2bcde4e52ee64a3329f5f
- linuxkit/containerd:v1.0.0
- linuxkit/ca-certificates:v1.0.0
- linuxkit/getty:v1.0.0
- linuxkit/init:7135424f6836ee166d1199e88cfb95ee88efaf91
- linuxkit/runc:efcece75889aec4e2de0d95ba27ccc46438522b3
- linuxkit/containerd:ce79d5d4ab9c46f4763735c6e4ab5c51c3feb5d8
- linuxkit/ca-certificates:d4cc1b82c73d272e94d0e71ea375fe56b0c0626a
- linuxkit/getty:bae9e3d4861173bacf78f14a4fe44997a430d13b
- nemunaire/mdadm:04814350d71ba9417e1f861be1685de26adf7a67
- nemunaire/kexec:839b4eedfce02a56c581dec2383dc6faff120855
- nemunaire/fic-frontend-ui:latest
onboot:
- name: mod
image: linuxkit/modprobe:v1.0.0
image: linuxkit/modprobe:e3de97ac10970edee33faa78d9780117174bd1ac
command: ["/bin/sh", "-c", "modprobe xhci_pci ahci intel_lpss_pci i2c_i801 megaraid_sas tg3 bnxt_en"]
- name: sysctl
image: linuxkit/sysctl:v1.0.0
image: linuxkit/sysctl:c5f4b4895844b993dce4e8b35fd8263a6b557807
# Metadata
- name: metadata
image: linuxkit/metadata:v1.0.0
image: linuxkit/metadata:f35b5aafc7d19bb6a44a900840727902dad78e44
command: ["/usr/bin/metadata", "-v", "cdrom"]
# Filesystem
- name: swap
image: linuxkit/swap:v1.0.0
image: linuxkit/swap:8a1fd15d56b6ddf67d6d8ce25361178e1f36128b
command: ["/sbin/swapon", "/dev/sda3"]
- name: dm-crypt
image: linuxkit/dm-crypt:d49723bc9d10c5ada9e03b0670f4e57416d5d084
image: linuxkit/dm-crypt:19fa6affe9da03afc91694e36d72a4924c65a0e0
command: ["/usr/bin/crypto", "-l", "crypt_fic", "/dev/sda4"]
binds:
- /dev:/dev
- /run/config/dm-crypt:/etc/dm-crypt
- name: mount
image: linuxkit/mount:v1.0.0
image: linuxkit/mount:4413ebd50bfbe026058e4a60463259cece2b8bb5
command: ["/usr/bin/mountie", "-device", "/dev/mapper/crypt_fic", "/var/lib/fic" ]
# Network
# - name: ntp
# image: linuxkit/openntpd:v1.0.0
# image: linuxkit/openntpd:da26954c2f98a274091e5ed0bbdd2079a77a47c1
- name: nginx-ip-setup
image: linuxkit/ip:v1.0.0
image: linuxkit/ip:af77c3f93143ff352a07ad5233d25a665012bcce
command: ["/bin/sh", "-c", "ip a add 172.17.1.2/24 dev vethin-nginx; ip link set vethin-nginx up;" ]
net: new
runtime:
@ -55,7 +55,7 @@ onboot:
bindNS:
net: /run/netns/nginx
- name: frontal-ip-setup # without bonding
image: linuxkit/ip:v1.0.0
image: linuxkit/ip:af77c3f93143ff352a07ad5233d25a665012bcce
command: ["/bin/sh", "-c", "ip link set name bond-frontal eth3; ip link set bond-frontal up; while read IP; do ip a add ${IP} dev bond-frontal; done < /run/config/ip_config/frontend-players; ip r add default via $(cat /run/config/ip_config/frontend-router); ip link add link bond-frontal name internet type vlan id 4; ip a add 10.10.10.2/29 dev internet; ip link set internet up;" ]
net: /run/netns/nginx
binds:
@ -67,7 +67,7 @@ onboot:
- name: eth3
# - name: eth4
# - name: frontal-ip-setup # with bonding
# image: linuxkit/ip:v1.0.0
# image: linuxkit/ip:af77c3f93143ff352a07ad5233d25a665012bcce
# command: ["/bin/sh", "-c", "ip link set dev bond-frontal type bond mode balance-alb; ip link set bond-frontal up; ifenslave bond-frontal eth1 eth2 eth3 eth4; while read IP; do ip a add ${IP} dev bond-frontal; done < /run/config/ip_config/frontend-players; ip r add default via $(cat /run/config/ip_config/frontend-router); ip link add link bond-frontal name internet type vlan id 4; ip link set internet up; sysctl -w net.ipv4.ip_forward=1;" ]
# net: /run/netns/nginx
# binds:
@ -81,7 +81,7 @@ onboot:
# - name: bond-frontal
# add: bond
- name: receiver-ip-setup
image: linuxkit/ip:v1.0.0
image: linuxkit/ip:af77c3f93143ff352a07ad5233d25a665012bcce
command: ["/bin/sh", "-c", "ip a add 172.17.1.3/24 dev vethin-receiver; ip link set vethin-receiver up;" ]
net: new
runtime:
@ -92,7 +92,7 @@ onboot:
bindNS:
net: /run/netns/fic-receiver
- name: sshd-ip-setup
image: linuxkit/ip:v1.0.0
image: linuxkit/ip:af77c3f93143ff352a07ad5233d25a665012bcce
command: ["/bin/sh", "-c", "ip a add 10.10.10.2/29 dev eth2; ip link set eth2 up;" ]
net: new
runtime:
@ -101,7 +101,7 @@ onboot:
bindNS:
net: /run/netns/sshd
- name: auth-ip-setup
image: linuxkit/ip:v1.0.0
image: linuxkit/ip:af77c3f93143ff352a07ad5233d25a665012bcce
command: ["/bin/sh", "-c", "ip a add 172.17.1.4/24 dev vethin-auth; ip link set vethin-auth up;" ]
net: new
runtime:
@ -112,7 +112,7 @@ onboot:
bindNS:
net: /run/netns/auth
- name: bridge-setup
image: linuxkit/ip:v1.0.0
image: linuxkit/ip:af77c3f93143ff352a07ad5233d25a665012bcce
command: ["/bin/sh", "-c", "ip a add 172.17.1.1/24 dev br0; ip link set veth-nginx master br0; ip link set veth-receiver master br0; ip link set veth-auth master br0; ip link set br0 up; ip link set veth-nginx up; ip link set veth-receiver up; ip link set veth-auth up;" ]
runtime:
interfaces:
@ -120,7 +120,7 @@ onboot:
add: bridge
- name: firewall-frontal
image: linuxkit/ip:v1.0.0
image: linuxkit/ip:af77c3f93143ff352a07ad5233d25a665012bcce
command: ["/bin/bash", "-c", "/sbin/iptables-restore < /etc/iptables/rules-frontal.v4; /sbin/ip6tables-restore < /etc/iptables/rules.v6; [ -f /run/config/remote_sync/destination ] && /sbin/iptables -I OUTPUT 7 -o bond-frontal -d $(cat /run/config/remote_sync/destination | tr -d '\n') -p tcp -m tcp --dport https -j ACCEPT;" ]
binds:
- /etc/iptables/rules-frontal.v4:/etc/iptables/rules-frontal.v4:ro
@ -129,26 +129,35 @@ onboot:
- /run/config/remote_sync/:/run/config/remote_sync/:ro
net: /run/netns/nginx
- name: firewall-sshd
image: linuxkit/ip:v1.0.0
image: linuxkit/ip:af77c3f93143ff352a07ad5233d25a665012bcce
command: ["/bin/bash", "-c", "/sbin/iptables-restore < /etc/iptables/rules-sshd.v4; /sbin/ip6tables-restore < /etc/iptables/rules.v6" ]
binds:
- /etc/iptables/rules-sshd.v4:/etc/iptables/rules-sshd.v4:ro
- /etc/iptables/rules.v6:/etc/iptables/rules.v6:ro
net: /run/netns/sshd
- name: create-ssh-keys
image: nemunaire/rsync:a3d76b2dd0a9ad73be44dc77ad765b20d96a3285
command: ["/bin/sh", "-c", "touch /etc/ssh/sshd_config && ssh-keygen -A"]
binds:
- /var/lib/fic/ssh:/etc/ssh
runtime:
mkdir:
- /var/lib/fic/ssh
services:
# - name: getty
# image: linuxkit/getty:v1.0.0
# image: linuxkit/getty:bae9e3d4861173bacf78f14a4fe44997a430d13b
# env:
# - INSECURE=true
# Enable acpi to shutdown on power events
- name: acpid
image: linuxkit/acpid:v1.0.0
image: linuxkit/acpid:6379700e2f3341250432e37a4cac36e35c7caac8
- name: rngd
image: linuxkit/rngd:v1.0.0
image: linuxkit/rngd:814d1a3a76e84eae01a94575c038fd22652f94e3
- name: dhcpcd
image: linuxkit/dhcpcd:v1.0.0
image: linuxkit/dhcpcd:330839488cd122db3c44738e265c035c9729a963
net: /run/netns/nginx
binds:
- /etc/dhcpcd.conf:/dhcpcd.conf:ro
@ -257,7 +266,6 @@ services:
- /var/lib/fic/files
- /var/lib/fic/pki
- /var/lib/fic/settingsdist
- /var/lib/fic/ssh
- /var/lib/fic/submissions
- /var/lib/fic/teams

@ -7,9 +7,9 @@ kernel:
init:
- nemunaire/mdadm:04814350d71ba9417e1f861be1685de26adf7a67
- nemunaire/syslinux:086f221f281d577d300949aa1094fb20c5cd90dc
- linuxkit/format:v1.0.0
- linuxkit/dm-crypt:d49723bc9d10c5ada9e03b0670f4e57416d5d084
- linuxkit/metadata:v1.0.0
- linuxkit/format:8f487d728959192289e0783784fc2b185eadbc82
- linuxkit/dm-crypt:ad2a05dcffa28ef809a61aa27ba230c82f02f603
- linuxkit/metadata:83cda7b43112b201613084ea8b7fab585b6e5549
- alpine:latest
files:

@ -6,7 +6,7 @@ kernel:
init:
- nemunaire/mdadm:04814350d71ba9417e1f861be1685de26adf7a67
- linuxkit/metadata:v1.0.0
- linuxkit/metadata:f35b5aafc7d19bb6a44a900840727902dad78e44
- alpine:latest

File diff suppressed because it is too large Load Diff

@ -12,9 +12,9 @@
"@sveltejs/adapter-static": "^3.0.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"@sveltestrap/sveltestrap": "^6.2.1",
"@sveltestrap/sveltestrap": "^7.0.0",
"eslint": "^9.0.0",
"eslint-config-prettier": "^9.0.0",
"eslint-config-prettier": "^10.0.0",
"eslint-plugin-svelte": "^2.35.1",
"prettier": "^3.0.0",
"prettier-plugin-svelte": "^3.1.2",

@ -27,15 +27,15 @@
</CardBody>
<ListGroup flush class="border-secondary">
{#each files as file, index}
<ListGroupItem tag="a" href={file.path} target={(file.name.endsWith(".txt") || file.name.endsWith(".jpg") || file.name.endsWith(".png") || file.name.endsWith(".pdf"))?"_blank":"_self"} class="d-flex">
<ListGroupItem tag="a" href={file.path} target={(file.name.endsWith(".txt") || file.name.endsWith(".xml") || file.name.endsWith(".jpg") || file.name.endsWith(".png") || file.name.endsWith(".pdf"))?"_blank":"_self"} class="d-flex">
<h1 class="me-3">
<Icon name="arrow-down-circle" />
</h1>
<div style="min-width: 0">
<h4 class="fw-bold"><samp>{file.name}</samp></h4>
{#if file.disclamer}
<div class="file-disclamer text-warning">
{file.disclamer}
{#if file.disclaimer}
<div class="file-disclaimer text-warning">
{file.disclaimer}
</div>
{/if}
<nobr>
@ -61,10 +61,10 @@
{/if}
<style>
.file-disclamer {
.file-disclaimer {
display: none;
}
:global(.list-group-item:hover .file-disclamer) {
:global(.list-group-item:hover .file-disclaimer) {
display: block;
}
</style>

@ -2,6 +2,12 @@ import { sveltekit } from '@sveltejs/kit/vite';
/** @type {import('vite').UserConfig} */
const config = {
server: {
hmr: {
port: 10000
}
},
plugins: [sveltekit()]
};

36
go.mod

@ -1,24 +1,26 @@
module srs.epita.fr/fic-server
go 1.18
go 1.21
toolchain go1.23.4
require (
github.com/BurntSushi/toml v1.4.0
github.com/asticode/go-astisub v0.30.0
github.com/asticode/go-astisub v0.32.0
github.com/cenkalti/dominantcolor v1.0.3
github.com/gin-contrib/sessions v1.0.1
github.com/gin-contrib/sessions v1.0.2
github.com/gin-gonic/gin v1.10.0
github.com/go-git/go-git/v5 v5.12.0
github.com/go-git/go-git/v5 v5.13.2
github.com/go-sql-driver/mysql v1.8.1
github.com/google/gopacket v1.1.19
github.com/studio-b12/gowebdav v0.9.0
github.com/studio-b12/gowebdav v0.10.0
github.com/u2takey/ffmpeg-go v0.5.0
github.com/yuin/goldmark v1.7.8
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b
go.uber.org/multierr v1.11.0
golang.org/x/crypto v0.29.0
golang.org/x/image v0.22.0
golang.org/x/oauth2 v0.24.0
golang.org/x/crypto v0.33.0
golang.org/x/image v0.24.0
golang.org/x/oauth2 v0.26.0
gopkg.in/fsnotify.v1 v1.4.7
)
@ -26,7 +28,7 @@ require (
dario.cat/mergo v1.0.0 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/ProtonMail/go-crypto v1.0.0 // indirect
github.com/ProtonMail/go-crypto v1.1.5 // indirect
github.com/acomagu/bufpipe v1.0.4 // indirect
github.com/asticode/go-astikit v0.20.0 // indirect
github.com/asticode/go-astits v1.8.0 // indirect
@ -38,14 +40,14 @@ require (
github.com/cloudflare/circl v1.3.7 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/cyphar/filepath-securejoin v0.3.6 // indirect
github.com/disintegration/imaging v1.6.2 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.5.0 // indirect
github.com/go-git/go-billy/v5 v5.6.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
@ -67,20 +69,20 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pjbgf/sha1cd v0.3.2 // indirect
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/skeema/knownhosts v1.2.2 // indirect
github.com/skeema/knownhosts v1.3.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/u2takey/go-utils v0.3.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sync v0.9.0 // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/text v0.20.0 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.34.1 // indirect

58
go.sum

@ -31,6 +31,10 @@ github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCv
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk=
github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4=
github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ=
@ -64,6 +68,8 @@ github.com/asticode/go-astisub v0.29.0 h1:1Pjz+TkaAwjPPoH88bQ6nnoXjin+WO2zZV95C/
github.com/asticode/go-astisub v0.29.0/go.mod h1:WTkuSzFB+Bp7wezuSf2Oxulj5A8zu2zLRVFf6bIFQK8=
github.com/asticode/go-astisub v0.30.0 h1:z4k2Y+V+rlCE8qk3uw/nie56KXxJaL1/GwTP+9F2GMM=
github.com/asticode/go-astisub v0.30.0/go.mod h1:WTkuSzFB+Bp7wezuSf2Oxulj5A8zu2zLRVFf6bIFQK8=
github.com/asticode/go-astisub v0.32.0 h1:i1RHVQyTxSAuX0X3YC5zIyWruVZorS3cDXxqxYa0qss=
github.com/asticode/go-astisub v0.32.0/go.mod h1:WTkuSzFB+Bp7wezuSf2Oxulj5A8zu2zLRVFf6bIFQK8=
github.com/asticode/go-astits v1.8.0 h1:rf6aiiGn/QhlFjNON1n5plqF3Fs025XLUwiQ0NB6oZg=
github.com/asticode/go-astits v1.8.0/go.mod h1:DkOWmBNQpnr9mv24KfZjq4JawCFX1FCqjLVGvO0DygQ=
github.com/aws/aws-sdk-go v1.38.20 h1:QbzNx/tdfATbdKfubBpkt84OM6oBkxQZRw6+bW2GyeA=
@ -107,6 +113,8 @@ github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQ
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM=
github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -130,6 +138,8 @@ github.com/gin-contrib/sessions v1.0.0 h1:r5GLta4Oy5xo9rAwMHx8B4wLpeRGHMdz9NafzJ
github.com/gin-contrib/sessions v1.0.0/go.mod h1:DN0f4bvpqMQElDdi+gNGScrP2QEI04IErRyMFyorUOI=
github.com/gin-contrib/sessions v1.0.1 h1:3hsJyNs7v7N8OtelFmYXFrulAf6zSR7nW/putcPEHxI=
github.com/gin-contrib/sessions v1.0.1/go.mod h1:ouxSFM24/OgIud5MJYQJLpy6AwxQ5EYO9yLhbtObGkM=
github.com/gin-contrib/sessions v1.0.2 h1:UaIjUvTH1cMeOdj3in6dl+Xb6It8RiKRF9Z1anbUyCA=
github.com/gin-contrib/sessions v1.0.2/go.mod h1:KxKxWqWP5LJVDCInulOl4WbLzK2KSPlLesfZ66wRvMs=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
@ -158,6 +168,10 @@ github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8
github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg=
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
github.com/go-git/go-billy/v5 v5.6.1 h1:u+dcrgaguSSkbjzHwelEjc0Yj300NUevrrPphk/SoRA=
github.com/go-git/go-billy/v5 v5.6.1/go.mod h1:0AsLr1z2+Uksi4NlElmMblP5rPcDZNRCD8ujZCRR2BE=
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8=
github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0=
github.com/go-git/go-git-fixtures/v4 v4.3.1/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo=
@ -187,6 +201,10 @@ github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3c
github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY=
github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
github.com/go-git/go-git/v5 v5.13.1 h1:DAQ9APonnlvSWpvolXWIuV6Q6zXy2wHbN4cVlNR5Q+M=
github.com/go-git/go-git/v5 v5.13.1/go.mod h1:qryJB4cSBoq3FRoBRf5A77joojuBcmPJ0qu3XXXVixc=
github.com/go-git/go-git/v5 v5.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0=
github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
@ -340,6 +358,8 @@ github.com/pjbgf/sha1cd v0.2.3 h1:uKQP/7QOzNtKYH7UTohZLcjF5/55EnTw0jO/Ru4jZwI=
github.com/pjbgf/sha1cd v0.2.3/go.mod h1:HOK9QrgzdHpbc2Kzip0Q1yi3M2MFGPADtR6HjG65m5M=
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@ -368,6 +388,8 @@ github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2
github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=
github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -398,6 +420,8 @@ github.com/studio-b12/gowebdav v0.0.0-20230203202212-3282f94193f2 h1:VsBj3UD2xyA
github.com/studio-b12/gowebdav v0.0.0-20230203202212-3282f94193f2/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
github.com/studio-b12/gowebdav v0.9.0 h1:1j1sc9gQnNxbXXM4M/CebPOX4aXYtr7MojAVcN4dHjU=
github.com/studio-b12/gowebdav v0.9.0/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
github.com/studio-b12/gowebdav v0.10.0 h1:Yewz8FFiadcGEu4hxS/AAJQlHelndqln1bns3hcJIYc=
github.com/studio-b12/gowebdav v0.10.0/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/u2takey/ffmpeg-go v0.4.1 h1:l5ClIwL3N2LaH1zF3xivb3kP2HW95eyG5xhHE1JdZ9Y=
@ -522,6 +546,14 @@ golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY=
golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.1.0 h1:r8Oj8ZA2Xy12/b5KZYj3tuv7NG/fBz3TwQVvpJ9l8Rk=
golang.org/x/image v0.1.0/go.mod h1:iyPr49SD/G/TBxYVB/9RRtGUT5eNbo2u4NamWeQcD5c=
@ -567,6 +599,10 @@ golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s=
golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78=
golang.org/x/image v0.22.0 h1:UtK5yLUzilVrkjMAZAZ34DXGpASN8i8pj8g+O+yd10g=
golang.org/x/image v0.22.0/go.mod h1:9hPFhljd4zZ1GNSIZJ49sqbp45GKK9t6w+iXvGqZUz4=
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@ -633,6 +669,10 @@ golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/oauth2 v0.1.0 h1:isLCZuhj4v+tYv7eskaN4v/TM+A1begWWgyVJDdl1+Y=
golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A=
golang.org/x/oauth2 v0.2.0 h1:GtQkldQ9m7yvzCL1V+LrYow3Khe0eJH0w7RbX/VbaIU=
@ -681,6 +721,10 @@ golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE=
golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -690,6 +734,10 @@ golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -768,6 +816,12 @@ golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -815,6 +869,10 @@ golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

@ -414,6 +414,7 @@ CREATE TABLE IF NOT EXISTS exercice_solved(
}
if _, err := db.Exec(`
CREATE TABLE IF NOT EXISTS exercice_tries(
id_try INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
id_exercice INTEGER NOT NULL,
id_team INTEGER NOT NULL,
time TIMESTAMP NOT NULL,
@ -423,6 +424,26 @@ CREATE TABLE IF NOT EXISTS exercice_tries(
FOREIGN KEY(id_exercice) REFERENCES exercices(id_exercice),
FOREIGN KEY(id_team) REFERENCES teams(id_team)
) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
`); err != nil {
return err
}
if _, err := db.Exec(`
CREATE TABLE IF NOT EXISTS exercice_tries_flags(
id_try INTEGER NOT NULL,
id_flag INTEGER NOT NULL,
FOREIGN KEY(id_try) REFERENCES exercice_tries(id_try) ON DELETE CASCADE,
FOREIGN KEY(id_flag) REFERENCES exercice_flags(id_flag) ON DELETE CASCADE
) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
`); err != nil {
return err
}
if _, err := db.Exec(`
CREATE TABLE IF NOT EXISTS exercice_tries_mcq(
id_try INTEGER NOT NULL,
id_mcq INTEGER NOT NULL,
FOREIGN KEY(id_try) REFERENCES exercice_tries(id_try) ON DELETE CASCADE,
FOREIGN KEY(id_mcq) REFERENCES exercice_mcq(id_mcq) ON DELETE CASCADE
) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
`); err != nil {
return err
}

@ -3,6 +3,7 @@ package fic
import (
"errors"
"fmt"
"log"
"math"
"time"
)
@ -444,11 +445,25 @@ func (e *Exercice) GetOrdinal() (int, error) {
}
// NewTry registers a solving attempt for the given Team.
func (e *Exercice) NewTry(t *Team, cksum []byte) error {
if _, err := DBExec("INSERT INTO exercice_tries (id_exercice, id_team, time, cksum) VALUES (?, ?, ?, ?)", e.Id, t.Id, time.Now(), cksum); err != nil {
return err
func (e *Exercice) NewTry(t *Team, cksum []byte, flags ...Flag) (int64, error) {
if res, err := DBExec("INSERT INTO exercice_tries (id_exercice, id_team, time, cksum) VALUES (?, ?, ?, ?)", e.Id, t.Id, time.Now(), cksum); err != nil {
return 0, err
} else {
return nil
return res.LastInsertId()
}
}
func (e *Exercice) NewTryFlag(tryid int64, flags ...Flag) {
for _, flag := range flags {
if fk, ok := flag.(*FlagKey); ok {
if _, err := DBExec("INSERT INTO exercice_tries_flags (id_try, id_flag) VALUES (?, ?)", tryid, fk.Id); err != nil {
log.Println("Unable to add detailed try: ", err.Error())
}
} else if fm, ok := flag.(*MCQ); ok {
if _, err := DBExec("INSERT INTO exercice_tries_mcq (id_try, id_mcq) VALUES (?, ?)", tryid, fm.Id); err != nil {
log.Println("Unable to add detailed try: ", err.Error())
}
}
}
}
@ -550,7 +565,7 @@ func (e *Exercice) MCQSolved() (res []int64) {
// CheckResponse, given both flags and MCQ responses, figures out if thoses are correct (or if they are previously solved).
// In the meanwhile, CheckResponse registers good answers given (but it does not mark the challenge as solved at the end).
func (e *Exercice) CheckResponse(cksum []byte, respflags map[int]string, respmcq map[int]bool, t *Team) (bool, error) {
if err := e.NewTry(t, cksum); err != nil {
if tryId, err := e.NewTry(t, cksum); err != nil {
return false, err
} else if flags, err := e.GetFlagKeys(); err != nil {
return false, err
@ -565,6 +580,10 @@ func (e *Exercice) CheckResponse(cksum []byte, respflags map[int]string, respmcq
// Check MCQs
for _, mcq := range mcqs {
if mcq.HasOneEntry(respmcq) {
e.NewTryFlag(tryId, mcq)
}
if d := mcq.Check(respmcq); d > 0 {
if !PartialValidation || t.HasPartiallySolved(mcq) == nil {
valid = false
@ -589,15 +608,21 @@ func (e *Exercice) CheckResponse(cksum []byte, respflags map[int]string, respmcq
for _, flag := range flags {
if res, ok := respflags[flag.Id]; !ok && (!PartialValidation || t.HasPartiallySolved(flag) == nil) {
valid = valid && flag.IsOptionnal()
} else if flag.Check([]byte(res)) != 0 {
if !PartialValidation || t.HasPartiallySolved(flag) == nil {
valid = valid && flag.IsOptionnal()
} else if ok {
if len(res) > 0 {
e.NewTryFlag(tryId, flag)
}
} else {
err := flag.FoundBy(t)
if err == nil {
// err is unicity issue, probably flag already found
goodResponses += 1
if flag.Check([]byte(res)) != 0 {
if !PartialValidation || t.HasPartiallySolved(flag) == nil {
valid = valid && flag.IsOptionnal()
}
} else {
err := flag.FoundBy(t)
if err == nil {
// err is unicity issue, probably flag already found
goodResponses += 1
}
}
}
}

@ -68,7 +68,8 @@ func (e *Exercice) AppendHistoryItem(tId int64, kind string, secondary *int64) e
if kind == "tries" {
bid := make([]byte, 5)
binary.LittleEndian.PutUint32(bid, rand.Uint32())
return (&Exercice{Id: e.Id}).NewTry(team, bid)
_, err = (&Exercice{Id: e.Id}).NewTry(team, bid)
return err
} else if kind == "hint" && secondary != nil {
return team.OpenHint(&EHint{Id: *secondary})
} else if kind == "wchoices" && secondary != nil {

@ -392,7 +392,12 @@ func (f *EFile) GetDepends() ([]Flag, error) {
// CheckFileOnDisk recalculates the hash of the file on disk.
func (f *EFile) CheckFileOnDisk() error {
if _, size, err := checkFileHash(path.Join(FilesDir, f.Path), f.Checksum); err != nil {
firstChecksum := f.Checksum
if len(f.ChecksumShown) > 0 {
firstChecksum = f.ChecksumShown
}
if _, size, err := checkFileHash(path.Join(FilesDir, f.Path), firstChecksum); size > 0 && err != nil {
return err
} else if size == 0 {
if _, _, err := checkFileHash(path.Join(FilesDir, f.Path+".gz"), f.Checksum); err != nil {
@ -400,9 +405,17 @@ func (f *EFile) CheckFileOnDisk() error {
} else {
return nil
}
} else {
return nil
} else if err != nil {
return err
}
if _, err := os.Stat(path.Join(FilesDir, f.Path+".gz")); !os.IsNotExist(err) {
if _, _, err = checkFileHash(path.Join(FilesDir, f.Path+".gz"), f.Checksum); err != nil {
return err
}
}
return nil
}
// GunzipFileOnDisk gunzip a compressed file.

@ -14,6 +14,9 @@ type Flag interface {
Check(val interface{}) int
IsOptionnal() bool
FoundBy(t *Team) error
NbTries() (int64, error)
TeamsOnIt() ([]int64, error)
DeleteTries() error
}
// GetFlag returns a list of flags comming with the challenge.

@ -230,6 +230,54 @@ func (k *FlagKey) RecoverId() (Flag, error) {
}
}
// NbTries returns the flag resolution statistics.
func (k *FlagKey) NbTries() (tries int64, err error) {
err = DBQueryRow("SELECT COUNT(*) AS tries FROM exercice_tries_flags WHERE id_flag = ?", k.Id).Scan(&tries)
return
}
func (k *FlagKey) TeamsOnIt() ([]int64, error) {
if rows, err := DBQuery("SELECT DISTINCT M.id_team FROM exercice_tries_flags F INNER JOIN exercice_tries T ON T.id_try = F.id_try INNER JOIN teams M ON M.id_team = T.id_team WHERE id_flag = ?", k.Id); err != nil {
return nil, err
} else {
defer rows.Close()
teams := []int64{}
for rows.Next() {
var idteam int64
if err := rows.Scan(&idteam); err != nil {
return nil, err
}
teams = append(teams, idteam)
}
return teams, nil
}
}
func (k *FlagKey) DeleteTries() error {
if rows, err := DBQuery("SELECT id_try FROM exercice_tries_flags WHERE id_flag = ?", k.Id); err != nil {
return err
} else {
defer rows.Close()
for rows.Next() {
var idtry int64
err = rows.Scan(&idtry)
if err != nil {
return err
}
_, err = DBExec("DELETE FROM exercice_tries WHERE id_try = ?", idtry)
if err != nil {
return err
}
}
return nil
}
}
// AddFlagKey creates and fills a new struct Flag, from a hashed flag, and registers it into the database.
func (k *FlagKey) Create(e *Exercice) (Flag, error) {
// Check the regexp compile

@ -71,6 +71,18 @@ func (k *FlagLabel) RecoverId() (Flag, error) {
}
}
func (k *FlagLabel) NbTries() (int64, error) {
return 0, nil
}
func (k *FlagLabel) TeamsOnIt() ([]int64, error) {
return nil, nil
}
func (k *FlagLabel) DeleteTries() error {
return nil
}
// AddFlagLabel creates and fills a new struct Flag and registers it into the database.
func (k *FlagLabel) Create(e *Exercice) (Flag, error) {
if res, err := DBExec("INSERT INTO exercice_flag_labels (id_exercice, ordre, label, variant) VALUES (?, ?, ?, ?)", e.Id, k.Order, k.Label, k.Variant); err != nil {

@ -136,6 +136,54 @@ func (m *MCQ) RecoverId() (Flag, error) {
}
}
// NbTries returns the MCQ resolution statistics.
func (m *MCQ) NbTries() (tries int64, err error) {
err = DBQueryRow("SELECT COUNT(*) AS tries FROM exercice_tries_mcq WHERE id_mcq = ?", m.Id).Scan(&tries)
return
}
func (m *MCQ) TeamsOnIt() ([]int64, error) {
if rows, err := DBQuery("SELECT DISTINCT M.id_team FROM exercice_tries_mcq F INNER JOIN exercice_tries T ON T.id_try = F.id_try INNER JOIN teams M ON M.id_team = T.id_team WHERE id_mcq = ?", m.Id); err != nil {
return nil, err
} else {
defer rows.Close()
teams := []int64{}
for rows.Next() {
var idteam int64
if err := rows.Scan(&idteam); err != nil {
return nil, err
}
teams = append(teams, idteam)
}
return teams, nil
}
}
func (m *MCQ) DeleteTries() error {
if rows, err := DBQuery("SELECT id_try FROM exercice_tries_mcq WHERE id_mcq = ?", m.Id); err != nil {
return err
} else {
defer rows.Close()
for rows.Next() {
var idtry int64
err = rows.Scan(&idtry)
if err != nil {
return err
}
_, err = DBExec("DELETE FROM exercice_tries WHERE id_try = ?", idtry)
if err != nil {
return err
}
}
return nil
}
}
// Create registers a MCQ into the database and recursively add its entries.
func (m *MCQ) Create(e *Exercice) (Flag, error) {
if res, err := DBExec("INSERT INTO exercice_mcq (id_exercice, ordre, title) VALUES (?, ?, ?)", e.Id, m.Order, m.Title); err != nil {
@ -207,7 +255,7 @@ func (m *MCQ) AddEntry(e *MCQ_entry) (*MCQ_entry, error) {
// Update applies modifications back to the database.
func (n *MCQ_entry) Update() (int64, error) {
if res, err := DBExec("UPDATE mcq_entries SET label = ?, response = ? WHERE id_mcq = ?", n.Label, n.Response, n.Id); err != nil {
if res, err := DBExec("UPDATE mcq_entries SET label = ?, response = ? WHERE id_mcq_entry = ?", n.Label, n.Response, n.Id); err != nil {
return 0, err
} else if nb, err := res.RowsAffected(); err != nil {
return 0, err
@ -319,6 +367,24 @@ func (m *MCQ) IsOptionnal() bool {
return false
}
// Check if the given vals contains at least a response for the given MCQ.
func (m *MCQ) HasOneEntry(v interface{}) bool {
var vals map[int]bool
if va, ok := v.(map[int]bool); !ok {
return false
} else {
vals = va
}
for _, n := range m.Entries {
if _, ok := vals[n.Id]; ok {
return true
}
}
return false
}
// Check if the given vals are the expected ones to validate this flag.
func (m *MCQ) Check(v interface{}) int {
var vals map[int]bool

@ -37,6 +37,7 @@ type myTeamFile struct {
Checksum string `json:"checksum"`
Compressed bool `json:"compressed,omitempty"`
Size int64 `json:"size"`
Disclaimer string `json:"disclaimer,omitempty"`
}
type myTeamHint struct {
HintId int64 `json:"id"`
@ -213,7 +214,7 @@ func MyJSONTeam(t *Team, started bool) (interface{}, error) {
cksum = f.ChecksumShown
compressed = true
}
exercice.Files = append(exercice.Files, myTeamFile{path.Join(FilesDir, f.Path), f.Name, hex.EncodeToString(cksum), compressed, f.Size})
exercice.Files = append(exercice.Files, myTeamFile{path.Join(FilesDir, f.Path), f.Name, hex.EncodeToString(cksum), compressed, f.Size, f.Disclaimer})
}
}
}

@ -269,6 +269,7 @@ func GitLab_ExportQA(c *gin.Context) {
gitlabid, err := GitLab_getExerciceId(exercice)
if err != nil {
log.Println("Unable to retrieve Gitlab exercice id:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
@ -301,6 +302,7 @@ func GitLab_ExportQA(c *gin.Context) {
oauth2Token := c.MustGet("gitlab-token").(*oauth2.Token)
iid, err := gitlab_newIssue(c.Request.Context(), oauth2Token, gitlabid, &issue)
if err != nil {
log.Println("Unable to create Gitlab issue:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}

@ -21,6 +21,7 @@ func declareTodoRoutes(router *gin.RouterGroup) {
func declareTodoManagerRoutes(router *gin.RouterGroup) {
router.POST("/qa_assign_work", assignWork)
router.DELETE("/qa_assign_work", deleteAssignedWork)
router.POST("/qa_my_exercices.json", addQAView)
router.POST("/qa_work.json", createQATodo)
@ -241,10 +242,13 @@ func deleteQATodo(c *gin.Context) {
}
type QAAssignWork struct {
Turns int `json:"turns"`
Start int `json:"start"`
TeamPrefix string `json:"team_prefix"`
TeamAssistants string `json:"team_assistants"`
Turns int `json:"turns"`
Start int `json:"start"`
TeamPrefix string `json:"team_prefix"`
TeamAssistants string `json:"team_assistants"`
OnlyExercices bool `json:"only_exercices"`
WithoutStandaloneExercices bool `json:"without_standalone_exercices"`
WithoutThemes bool `json:"without_themes"`
}
func assignWork(c *gin.Context) {
@ -278,19 +282,80 @@ func assignWork(c *gin.Context) {
}
}
exercices, err := fic.GetExercices()
if err != nil {
log.Println("Unable to GetExercices: ", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list exercices: %s", err.Error())})
return
var themes []*fic.Theme
if !uaw.OnlyExercices && !uaw.WithoutThemes {
themes, err = fic.GetThemes()
if err != nil {
log.Println("Unable to GetThemes: ", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list themes: %s", err.Error())})
return
}
}
var exercices []*fic.Exercice
if uaw.OnlyExercices {
exercices, err = fic.GetExercices()
if err != nil {
log.Println("Unable to GetExercices: ", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list exercices: %s", err.Error())})
return
}
if uaw.WithoutStandaloneExercices || uaw.WithoutThemes {
for i := len(exercices) - 1; i >= 0; i-- {
if (exercices[i].IdTheme == nil && uaw.WithoutStandaloneExercices) || (exercices[i].IdTheme != nil && uaw.WithoutThemes) {
exercices = append(exercices[:i], exercices[i+1:]...)
}
}
}
} else if !uaw.WithoutStandaloneExercices {
exercices, err = (&fic.Theme{URLId: "_", Path: "exercices"}).GetExercices()
if err != nil {
log.Println("Unable to GetStandaloneExercices: ", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list exercices: %s", err.Error())})
return
}
}
// Struct to store reported team (due to owned exercice)
var teamIdStack []int64
for i := 0; i < uaw.Turns; i++ {
lasttid := 0
for tid, theme := range themes {
lasttid = tid
team := teams[(uaw.Start+tid+uaw.Turns*i)%len(teams)]
if len(teamIdStack) > 0 {
teamIdStack = append(teamIdStack, team.Id)
team, _ = fic.GetTeam(teamIdStack[0])
teamIdStack = append([]int64{}, teamIdStack[1:]...)
}
j := 0
// Find a team not responsible for this exercice
for (strings.Contains(theme.Path, "grp") && strings.Contains(theme.Path, "-grp"+strings.TrimPrefix(team.Name, uaw.TeamPrefix)+"-")) || (!strings.Contains(theme.Path, "grp") && strings.HasPrefix(theme.Path, strings.TrimPrefix(team.Name, uaw.TeamPrefix)+"-")) {
j += 1
teamIdStack = append(teamIdStack, team.Id)
team = teams[(uaw.Start+tid+uaw.Turns*i+j)%len(teams)]
}
exercices, err := theme.GetExercices()
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
for _, ex := range exercices {
_, err := team.NewQATodo(ex.Id)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
}
}
for eid, ex := range exercices {
team := teams[(uaw.Start+eid+uaw.Turns*i)%len(teams)]
team := teams[(uaw.Start+lasttid+eid+uaw.Turns*i)%len(teams)]
if len(teamIdStack) > 0 {
teamIdStack = append(teamIdStack, team.Id)
@ -316,3 +381,24 @@ func assignWork(c *gin.Context) {
c.JSON(http.StatusOK, "true")
}
func deleteAssignedWork(c *gin.Context) {
teams, err := fic.GetTeams()
if err != nil {
log.Println("Unable to GetTeams: ", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list teams: %s", err.Error())})
return
}
for _, team := range teams {
todos, err := team.GetQATodo()
if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Todo not found."})
return
}
for _, t := range todos {
t.Delete()
}
}
}

@ -24,7 +24,7 @@ func showVersion(c *gin.Context) {
}
c.JSON(http.StatusOK, gin.H{
"version": 0.3,
"version": 0.4,
"auth": map[string]interface{}{
"name": ficteam,
"id_team": teamid,

@ -42,6 +42,14 @@ func NewApp(baseURL string) App {
api.DeclareRoutes(baserouter)
declareStaticRoutes(baserouter, baseURL)
// In case of /baseURL/baseURL, redirect to /baseURL
baserouter.GET(baseURL, func(c *gin.Context) {
c.Redirect(http.StatusFound, baseURL)
})
baserouter.GET(baseURL+"/*path", func(c *gin.Context) {
c.Redirect(http.StatusFound, baseURL+c.Param("path"))
})
} else {
declareStaticRoutes(router.Group(""), "")
}

@ -55,7 +55,7 @@ func serveOrReverse(forced_url string, baseURL string) func(c *gin.Context) {
if u, err := url.Parse(DevProxy); err != nil {
http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
} else {
if forced_url != "" {
if forced_url != "" && forced_url != "/" {
u.Path = path.Join(u.Path, forced_url)
} else {
u.Path = path.Join(u.Path, strings.TrimPrefix(c.Request.URL.Path, baseURL))

431
qa/ui/package-lock.json generated

@ -8,7 +8,7 @@
"name": "qa",
"version": "0.0.1",
"dependencies": {
"@sveltestrap/sveltestrap": "^6.2.1",
"@sveltestrap/sveltestrap": "^7.0.0",
"bootstrap": "^5.2.2",
"bootstrap-icons": "^1.9.1"
},
@ -17,7 +17,7 @@
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"eslint": "^9.0.0",
"eslint-config-prettier": "^9.0.0",
"eslint-config-prettier": "^10.0.0",
"eslint-plugin-svelte": "^2.35.1",
"prettier": "^3.0.0",
"prettier-plugin-svelte": "^3.1.2",
@ -430,17 +430,20 @@
}
},
"node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
"integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==",
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz",
"integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==",
"dev": true,
"license": "MIT",
"dependencies": {
"eslint-visitor-keys": "^3.3.0"
"eslint-visitor-keys": "^3.4.3"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
},
"peerDependencies": {
"eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
}
@ -469,13 +472,13 @@
}
},
"node_modules/@eslint/config-array": {
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.0.tgz",
"integrity": "sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ==",
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz",
"integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@eslint/object-schema": "^2.1.4",
"@eslint/object-schema": "^2.1.5",
"debug": "^4.3.1",
"minimatch": "^3.1.2"
},
@ -484,11 +487,14 @@
}
},
"node_modules/@eslint/core": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.0.tgz",
"integrity": "sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==",
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz",
"integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@types/json-schema": "^7.0.15"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
@ -518,9 +524,9 @@
}
},
"node_modules/@eslint/js": {
"version": "9.15.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.15.0.tgz",
"integrity": "sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==",
"version": "9.19.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.19.0.tgz",
"integrity": "sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==",
"dev": true,
"license": "MIT",
"engines": {
@ -528,9 +534,9 @@
}
},
"node_modules/@eslint/object-schema": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz",
"integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==",
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz",
"integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@ -538,12 +544,13 @@
}
},
"node_modules/@eslint/plugin-kit": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz",
"integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==",
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz",
"integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@eslint/core": "^0.10.0",
"levn": "^0.4.1"
},
"engines": {
@ -574,6 +581,20 @@
"node": ">=18.18.0"
}
},
"node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
"integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=18.18"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/nzakas"
}
},
"node_modules/@humanwhocodes/module-importer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
@ -589,9 +610,9 @@
}
},
"node_modules/@humanwhocodes/retry": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
"integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz",
"integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@ -603,9 +624,9 @@
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
"license": "MIT",
"dependencies": {
"@jridgewell/set-array": "^1.2.1",
@ -668,9 +689,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz",
"integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==",
"version": "4.32.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.32.0.tgz",
"integrity": "sha512-G2fUQQANtBPsNwiVFg4zKiPQyjVKZCUdQUol53R8E71J7AsheRMV/Yv/nB8giOcOVqP7//eB5xPqieBYZe9bGg==",
"cpu": [
"arm"
],
@ -682,9 +703,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz",
"integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==",
"version": "4.32.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.32.0.tgz",
"integrity": "sha512-qhFwQ+ljoymC+j5lXRv8DlaJYY/+8vyvYmVx074zrLsu5ZGWYsJNLjPPVJJjhZQpyAKUGPydOq9hRLLNvh1s3A==",
"cpu": [
"arm64"
],
@ -696,9 +717,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz",
"integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==",
"version": "4.32.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.32.0.tgz",
"integrity": "sha512-44n/X3lAlWsEY6vF8CzgCx+LQaoqWGN7TzUfbJDiTIOjJm4+L2Yq+r5a8ytQRGyPqgJDs3Rgyo8eVL7n9iW6AQ==",
"cpu": [
"arm64"
],
@ -710,9 +731,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz",
"integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==",
"version": "4.32.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.32.0.tgz",
"integrity": "sha512-F9ct0+ZX5Np6+ZDztxiGCIvlCaW87HBdHcozUfsHnj1WCUTBUubAoanhHUfnUHZABlElyRikI0mgcw/qdEm2VQ==",
"cpu": [
"x64"
],
@ -723,10 +744,38 @@
"darwin"
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.32.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.32.0.tgz",
"integrity": "sha512-JpsGxLBB2EFXBsTLHfkZDsXSpSmKD3VxXCgBQtlPcuAqB8TlqtLcbeMhxXQkCDv1avgwNjF8uEIbq5p+Cee0PA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.32.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.32.0.tgz",
"integrity": "sha512-wegiyBT6rawdpvnD9lmbOpx5Sph+yVZKHbhnSP9MqUEDX08G4UzMU+D87jrazGE7lRSyTRs6NEYHtzfkJ3FjjQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz",
"integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==",
"version": "4.32.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.32.0.tgz",
"integrity": "sha512-3pA7xecItbgOs1A5H58dDvOUEboG5UfpTq3WzAdF54acBbUM+olDJAPkgj1GRJ4ZqE12DZ9/hNS2QZk166v92A==",
"cpu": [
"arm"
],
@ -738,9 +787,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz",
"integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==",
"version": "4.32.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.32.0.tgz",
"integrity": "sha512-Y7XUZEVISGyge51QbYyYAEHwpGgmRrAxQXO3siyYo2kmaj72USSG8LtlQQgAtlGfxYiOwu+2BdbPjzEpcOpRmQ==",
"cpu": [
"arm"
],
@ -752,9 +801,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz",
"integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==",
"version": "4.32.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.32.0.tgz",
"integrity": "sha512-r7/OTF5MqeBrZo5omPXcTnjvv1GsrdH8a8RerARvDFiDwFpDVDnJyByYM/nX+mvks8XXsgPUxkwe/ltaX2VH7w==",
"cpu": [
"arm64"
],
@ -766,9 +815,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz",
"integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==",
"version": "4.32.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.32.0.tgz",
"integrity": "sha512-HJbifC9vex9NqnlodV2BHVFNuzKL5OnsV2dvTw6e1dpZKkNjPG6WUq+nhEYV6Hv2Bv++BXkwcyoGlXnPrjAKXw==",
"cpu": [
"arm64"
],
@ -779,10 +828,24 @@
"linux"
]
},
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
"version": "4.32.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.32.0.tgz",
"integrity": "sha512-VAEzZTD63YglFlWwRj3taofmkV1V3xhebDXffon7msNz4b14xKsz7utO6F8F4cqt8K/ktTl9rm88yryvDpsfOw==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz",
"integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==",
"version": "4.32.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.32.0.tgz",
"integrity": "sha512-Sts5DST1jXAc9YH/iik1C9QRsLcCoOScf3dfbY5i4kH9RJpKxiTBXqm7qU5O6zTXBTEZry69bGszr3SMgYmMcQ==",
"cpu": [
"ppc64"
],
@ -794,9 +857,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz",
"integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==",
"version": "4.32.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.32.0.tgz",
"integrity": "sha512-qhlXeV9AqxIyY9/R1h1hBD6eMvQCO34ZmdYvry/K+/MBs6d1nRFLm6BOiITLVI+nFAAB9kUB6sdJRKyVHXnqZw==",
"cpu": [
"riscv64"
],
@ -808,9 +871,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz",
"integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==",
"version": "4.32.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.32.0.tgz",
"integrity": "sha512-8ZGN7ExnV0qjXa155Rsfi6H8M4iBBwNLBM9lcVS+4NcSzOFaNqmt7djlox8pN1lWrRPMRRQ8NeDlozIGx3Omsw==",
"cpu": [
"s390x"
],
@ -822,9 +885,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz",
"integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==",
"version": "4.32.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.32.0.tgz",
"integrity": "sha512-VDzNHtLLI5s7xd/VubyS10mq6TxvZBp+4NRWoW+Hi3tgV05RtVm4qK99+dClwTN1McA6PHwob6DEJ6PlXbY83A==",
"cpu": [
"x64"
],
@ -836,9 +899,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz",
"integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==",
"version": "4.32.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.32.0.tgz",
"integrity": "sha512-qcb9qYDlkxz9DxJo7SDhWxTWV1gFuwznjbTiov289pASxlfGbaOD54mgbs9+z94VwrXtKTu+2RqwlSTbiOqxGg==",
"cpu": [
"x64"
],
@ -850,9 +913,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz",
"integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==",
"version": "4.32.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.32.0.tgz",
"integrity": "sha512-pFDdotFDMXW2AXVbfdUEfidPAk/OtwE/Hd4eYMTNVVaCQ6Yl8et0meDaKNL63L44Haxv4UExpv9ydSf3aSayDg==",
"cpu": [
"arm64"
],
@ -864,9 +927,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz",
"integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==",
"version": "4.32.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.32.0.tgz",
"integrity": "sha512-/TG7WfrCAjeRNDvI4+0AAMoHxea/USWhAzf9PVDFHbcqrQ7hMMKp4jZIy4VEjk72AAfN5k4TiSMRXRKf/0akSw==",
"cpu": [
"ia32"
],
@ -878,9 +941,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz",
"integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==",
"version": "4.32.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.32.0.tgz",
"integrity": "sha512-5hqO5S3PTEO2E5VjCePxv40gIgyS2KvO7E7/vvC/NbIW4SIRamkMr1hqj+5Y67fbBWv/bQLB6KelBQmXlyCjWA==",
"cpu": [
"x64"
],
@ -892,9 +955,9 @@
]
},
"node_modules/@sveltejs/adapter-static": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.6.tgz",
"integrity": "sha512-MGJcesnJWj7FxDcB/GbrdYD3q24Uk0PIL4QIX149ku+hlJuj//nxUbb0HxUTpjkecWfHjVveSUnUaQWnPRXlpg==",
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.8.tgz",
"integrity": "sha512-YaDrquRpZwfcXbnlDsSrBQNCChVOT9MGuSg+dMAyfsAa1SmiAhrA5jUYUiIMC59G92kIbY/AaQOWcBdq+lh+zg==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@ -902,25 +965,23 @@
}
},
"node_modules/@sveltejs/kit": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.9.0.tgz",
"integrity": "sha512-W3E7ed3ChB6kPqRs2H7tcHp+Z7oiTFC6m+lLyAQQuyXeqw6LdNuuwEUla+5VM0OGgqQD+cYD6+7Xq80vVm17Vg==",
"version": "2.17.1",
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.17.1.tgz",
"integrity": "sha512-CpoGSLqE2MCmcQwA2CWJvOsZ9vW+p/1H3itrFykdgajUNAEyQPbsaSn7fZb6PLHQwe+07njxje9ss0fjZoCAyw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@types/cookie": "^0.6.0",
"cookie": "^0.6.0",
"devalue": "^5.1.0",
"esm-env": "^1.2.1",
"esm-env": "^1.2.2",
"import-meta-resolve": "^4.1.0",
"kleur": "^4.1.5",
"magic-string": "^0.30.5",
"mrmime": "^2.0.0",
"sade": "^1.8.1",
"set-cookie-parser": "^2.6.0",
"sirv": "^3.0.0",
"tiny-glob": "^0.2.9"
"sirv": "^3.0.0"
},
"bin": {
"svelte-kit": "svelte-kit.js"
@ -976,9 +1037,9 @@
}
},
"node_modules/@sveltestrap/sveltestrap": {
"version": "6.2.7",
"resolved": "https://registry.npmjs.org/@sveltestrap/sveltestrap/-/sveltestrap-6.2.7.tgz",
"integrity": "sha512-WwLLfAFUb42BGuRrf3Vbct30bQMzlEMMipN/MfxhjuLTmLQeW9muVJfPyvjtWS+mY+RjkSCoHvAp/ZobP1NLlQ==",
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/@sveltestrap/sveltestrap/-/sveltestrap-7.0.3.tgz",
"integrity": "sha512-lvZpVlq7pHVxJbjq2d6JAAr/Z1mkSaPOw3pwpZiuQ9FK97/Pr66m5Bf9qZIc1FUkLnbNiDtRAbhVyR8LVdr3FQ==",
"license": "MIT",
"dependencies": {
"@popperjs/core": "^2.11.8"
@ -1218,9 +1279,9 @@
}
},
"node_modules/cross-spawn": {
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz",
"integrity": "sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==",
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -1259,9 +1320,9 @@
}
},
"node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -1353,19 +1414,19 @@
}
},
"node_modules/eslint": {
"version": "9.15.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.15.0.tgz",
"integrity": "sha512-7CrWySmIibCgT1Os28lUU6upBshZ+GxybLOrmRzi08kS8MBuO8QA7pXEgYgY5W8vK3e74xv0lpjo9DbaGU9Rkw==",
"version": "9.19.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.19.0.tgz",
"integrity": "sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.19.0",
"@eslint/core": "^0.9.0",
"@eslint/core": "^0.10.0",
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "9.15.0",
"@eslint/plugin-kit": "^0.2.3",
"@eslint/js": "9.19.0",
"@eslint/plugin-kit": "^0.2.5",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.1",
@ -1373,7 +1434,7 @@
"@types/json-schema": "^7.0.15",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.5",
"cross-spawn": "^7.0.6",
"debug": "^4.3.2",
"escape-string-regexp": "^4.0.0",
"eslint-scope": "^8.2.0",
@ -1429,22 +1490,22 @@
}
},
"node_modules/eslint-config-prettier": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz",
"integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==",
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.0.1.tgz",
"integrity": "sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw==",
"dev": true,
"license": "MIT",
"bin": {
"eslint-config-prettier": "bin/cli.js"
"eslint-config-prettier": "build/bin/cli.js"
},
"peerDependencies": {
"eslint": ">=7.0.0"
}
},
"node_modules/eslint-plugin-svelte": {
"version": "2.46.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.46.0.tgz",
"integrity": "sha512-1A7iEMkzmCZ9/Iz+EAfOGYL8IoIG6zeKEq1SmpxGeM5SXmoQq+ZNnCpXFVJpsxPWYx8jIVGMerQMzX20cqUl0g==",
"version": "2.46.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.46.1.tgz",
"integrity": "sha512-7xYr2o4NID/f9OEYMqxsEQsCsj4KaMy4q5sANaKkAb6/QeCjYFxRmDm2S3YC3A3pl1kyPZ/syOx/i7LcWYSbIw==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -1506,24 +1567,10 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint/node_modules/@humanwhocodes/retry": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz",
"integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=18.18"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/nzakas"
}
},
"node_modules/esm-env": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.1.tgz",
"integrity": "sha512-U9JedYYjCnadUlXk7e1Kr+aENQhtUaoaV9+gZm1T8LC/YBAPJx3NSPIAurFOC0U5vrdSevnUJS2/wUVxGwPhng==",
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
"dev": true,
"license": "MIT"
},
@ -1666,9 +1713,9 @@
}
},
"node_modules/flatted": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
"integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz",
"integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==",
"dev": true,
"license": "ISC"
},
@ -1713,20 +1760,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/globalyzer": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz",
"integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==",
"dev": true,
"license": "MIT"
},
"node_modules/globrex": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz",
"integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==",
"dev": true,
"license": "MIT"
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@ -1809,12 +1842,12 @@
}
},
"node_modules/is-reference": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz",
"integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==",
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
"integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
"license": "MIT",
"dependencies": {
"@types/estree": "*"
"@types/estree": "^1.0.6"
}
},
"node_modules/isexe": {
@ -1939,9 +1972,9 @@
"license": "MIT"
},
"node_modules/magic-string": {
"version": "0.30.12",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz",
"integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==",
"version": "0.30.17",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
"integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0"
@ -1994,9 +2027,9 @@
"license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
"dev": true,
"funding": [
{
@ -2114,16 +2147,16 @@
}
},
"node_modules/picocolors": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
"integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==",
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true,
"license": "ISC"
},
"node_modules/postcss": {
"version": "8.4.47",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
"integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
"version": "8.5.1",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz",
"integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==",
"dev": true,
"funding": [
{
@ -2141,8 +2174,8 @@
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.1.0",
"nanoid": "^3.3.8",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
"engines": {
@ -2248,9 +2281,9 @@
}
},
"node_modules/prettier": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz",
"integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==",
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
"integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
"dev": true,
"license": "MIT",
"bin": {
@ -2264,9 +2297,9 @@
}
},
"node_modules/prettier-plugin-svelte": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.3.2.tgz",
"integrity": "sha512-kRPjH8wSj2iu+dO+XaUv4vD8qr5mdDmlak3IT/7AOgGIMRG86z/EHOLauFcClKEnOUf4A4nOA7sre5KrJD4Raw==",
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.3.3.tgz",
"integrity": "sha512-yViK9zqQ+H2qZD1w/bH7W8i+bVfKrD8GIFjkFe4Thl6kCT9SlAsXVNmt3jCvQOCsnOhcvYgsoVlRV/Eu6x5nNw==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@ -2295,9 +2328,9 @@
}
},
"node_modules/rollup": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz",
"integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==",
"version": "4.32.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.32.0.tgz",
"integrity": "sha512-JmrhfQR31Q4AuNBjjAX4s+a/Pu/Q8Q9iwjWBsjRH1q52SPFE2NqRMK6fUZKKnvKO6id+h7JIRf0oYsph53eATg==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -2311,22 +2344,25 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.24.0",
"@rollup/rollup-android-arm64": "4.24.0",
"@rollup/rollup-darwin-arm64": "4.24.0",
"@rollup/rollup-darwin-x64": "4.24.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.24.0",
"@rollup/rollup-linux-arm-musleabihf": "4.24.0",
"@rollup/rollup-linux-arm64-gnu": "4.24.0",
"@rollup/rollup-linux-arm64-musl": "4.24.0",
"@rollup/rollup-linux-powerpc64le-gnu": "4.24.0",
"@rollup/rollup-linux-riscv64-gnu": "4.24.0",
"@rollup/rollup-linux-s390x-gnu": "4.24.0",
"@rollup/rollup-linux-x64-gnu": "4.24.0",
"@rollup/rollup-linux-x64-musl": "4.24.0",
"@rollup/rollup-win32-arm64-msvc": "4.24.0",
"@rollup/rollup-win32-ia32-msvc": "4.24.0",
"@rollup/rollup-win32-x64-msvc": "4.24.0",
"@rollup/rollup-android-arm-eabi": "4.32.0",
"@rollup/rollup-android-arm64": "4.32.0",
"@rollup/rollup-darwin-arm64": "4.32.0",
"@rollup/rollup-darwin-x64": "4.32.0",
"@rollup/rollup-freebsd-arm64": "4.32.0",
"@rollup/rollup-freebsd-x64": "4.32.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.32.0",
"@rollup/rollup-linux-arm-musleabihf": "4.32.0",
"@rollup/rollup-linux-arm64-gnu": "4.32.0",
"@rollup/rollup-linux-arm64-musl": "4.32.0",
"@rollup/rollup-linux-loongarch64-gnu": "4.32.0",
"@rollup/rollup-linux-powerpc64le-gnu": "4.32.0",
"@rollup/rollup-linux-riscv64-gnu": "4.32.0",
"@rollup/rollup-linux-s390x-gnu": "4.32.0",
"@rollup/rollup-linux-x64-gnu": "4.32.0",
"@rollup/rollup-linux-x64-musl": "4.32.0",
"@rollup/rollup-win32-arm64-msvc": "4.32.0",
"@rollup/rollup-win32-ia32-msvc": "4.32.0",
"@rollup/rollup-win32-x64-msvc": "4.32.0",
"fsevents": "~2.3.2"
}
},
@ -2357,9 +2393,9 @@
}
},
"node_modules/set-cookie-parser": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.0.tgz",
"integrity": "sha512-lXLOiqpkUumhRdFF3k1osNXCy9akgx/dyPZ5p8qAg9seJzXr5ZrlqZuWIMuY6ejOsVLE6flJ5/h3lsn57fQ/PQ==",
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
"dev": true,
"license": "MIT"
},
@ -2550,17 +2586,6 @@
"svelte": "^3.19.0 || ^4.0.0"
}
},
"node_modules/tiny-glob": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz",
"integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==",
"dev": true,
"license": "MIT",
"dependencies": {
"globalyzer": "0.1.0",
"globrex": "^0.1.2"
}
},
"node_modules/totalist": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
@ -2602,9 +2627,9 @@
"license": "MIT"
},
"node_modules/vite": {
"version": "5.4.11",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",
"integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==",
"version": "5.4.14",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz",
"integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==",
"dev": true,
"license": "MIT",
"dependencies": {

@ -14,7 +14,7 @@
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"eslint": "^9.0.0",
"eslint-config-prettier": "^9.0.0",
"eslint-config-prettier": "^10.0.0",
"eslint-plugin-svelte": "^2.35.1",
"prettier": "^3.0.0",
"prettier-plugin-svelte": "^3.1.2",
@ -25,6 +25,6 @@
"dependencies": {
"bootstrap": "^5.2.2",
"bootstrap-icons": "^1.9.1",
"@sveltestrap/sveltestrap": "^6.2.1"
"@sveltestrap/sveltestrap": "^7.0.0"
}
}

@ -11,66 +11,81 @@
let themesP = fetch('api/qa/export.json').then((res) => res.json())
function state2Color(state) {
function state2Color(report) {
const subtle = report.closed ? "-subtle" : "";
const state = report.state;
if (state === "timer") {
return "info";
return "info" + subtle;
} else if (state === "ok") {
return "success";
return "success" + subtle;
} else if (state.startsWith("issue")) {
return "danger";
return "danger" + subtle;
} else {
return "warning";
return "warning" + subtle;
}
}
</script>
<div
class="d-flex flex-fill"
class="d-flex flex-column flex-fill"
style="overflow-y: auto"
>
{#await themesP then themes}
{#if Object.keys(themes).length > 1}
<Row
style={'min-width:'+15*Object.keys(themes).length + 'vw'}
>
{#if Object.keys(themes).length > 1}
{#each Object.keys(themes) as tname}
<Col style="border-right: 1px solid lightgray">
<h3
class="text-center py-3 mb-3"
style="border-bottom: 2px solid black"
<div
class="d-flex gap-2 mb-2 align-items-start py-1"
style="border-bottom: 1px solid lightgray; min-height: 4rem"
>
{tname}
</h3>
{#if themes[tname].exercices}
{#each themes[tname].exercices as exercice}
{#if exercice.reports}
{#each exercice.reports as report}
<Card
class="mb-3"
color={state2Color(report.report.state)}
style="cursor: pointer"
on:click={() => goto(`exercices/${exercice.exercice.id}/${report.report.id}`)}
<h4
class="d-flex align-items-center pe-2 mb-0 h-100 text-truncate"
style="border-right: 2px solid black; max-width: 150px"
title={tname}
on:click={() => goto("/themes/" + themes[tname].theme.id)}
>
{tname}
</h4>
{#if themes[tname].exercices}
<div
class="flex-fill d-flex flex-column gap-1"
>
{#each themes[tname].exercices as exercice, eid}
{#if exercice.reports}
<div
class="flex-fill d-flex gap-2 align-items-center"
>
<CardBody class="p-2">
{report.report.subject}
{#if report.comments}
<Badge
class="float-end"
>
{report.comments.length}
</Badge>
{/if}
</CardBody>
</Card>
{/each}
<hr />
{/if}
{/each}
{/if}
</Col>
<span
title={exercice.exercice.title}
on:click={() => goto("/themes/" + themes[tname].theme.id + "/" + exercice.exercice.id)}
>
{eid+1}.
</span>
{#each exercice.reports as report}
<Card
color={state2Color(report.report)}
style="cursor: pointer; min-width: 100px; max-width: 250px"
on:click={() => goto(`exercices/${exercice.exercice.id}/${report.report.id}`)}
>
<CardBody class="p-2 text-truncate" title={report.report.subject}>
{#if report.comments}
<Badge
class="float-end"
>
{report.comments.length}
</Badge>
{/if}
{report.report.subject}
</CardBody>
</Card>
{/each}
</div>
{/if}
{/each}
</div>
{/if}
</div>
{/each}
</Row>
{:else}
{:else}
{#each Object.keys(themes) as tname}
{#if themes[tname].exercices}
{#each themes[tname].exercices as exercice}
@ -112,6 +127,6 @@
{/each}
{/if}
{/each}
{/if}
{/if}
{/await}
</div>

@ -22,10 +22,12 @@
goto("teams/" + id)
}
let start = 0;
let turns = 3;
let team_prefix = "";
let team_assistants = "";
let assignForm = {
start: 0,
turns: 3,
team_prefix: "",
team_assistants: "",
}
let assignInProgress = false;
async function assignExercices() {
@ -33,12 +35,7 @@
const res = await fetch(`api/qa_assign_work`, {
method: 'POST',
headers: {'Accept': 'application/json'},
body: JSON.stringify({
start,
turns,
team_prefix,
team_assistants,
}),
body: JSON.stringify(assignForm),
})
if (res.status == 200) {
teams.refresh();
@ -111,23 +108,26 @@
<form on:submit|preventDefault={assignExercices}>
<FormGroup>
<Label for="ae-start">Compteur de départ</Label>
<Input type="number" id="ae-start" bind:value={start} />
<Input type="number" id="ae-start" bind:value={assignForm.start} />
<p class="form-text">
Incrémenter de 1 pour chaque nouveau challenge blanc, cela décale l'attribution des exercices.
</p>
</FormGroup>
<FormGroup>
<Label for="ae-turns">Nombre d'itérations</Label>
<Input type="number" id="ae-turns" bind:value={turns} />
<Input type="number" id="ae-turns" bind:value={assignForm.turns} />
</FormGroup>
<FormGroup>
<Label for="ae-prefix">Préfixe des noms d'équipes</Label>
<Input id="ae-prefix" bind:value={team_prefix} placeholder="FIC Groupe" />
<Input id="ae-prefix" bind:value={assignForm.team_prefix} placeholder="FIC Groupe" />
</FormGroup>
<FormGroup>
<Label for="ae-assistants">Nom de l'équipe assistants</Label>
<Input id="ae-assistants" bind:value={team_assistants} placeholder="Assistants" />
<Input id="ae-assistants" bind:value={assignForm.team_assistants} placeholder="Assistants" />
</FormGroup>
<Input class="mb-3" type="switch" label="Ne pas répartir les exercices indépendants" bind:checked={assignForm.without_standalone_exercices} />
<Input class="mb-3" type="switch" label="Ne pas répartir les scénarios" bind:checked={assignForm.without_themes} />
<Input class="mb-3" type="switch" label="Répartir tous les exercices existant sans considération des scénarios dans lesquels ils se trouvent" bind:checked={assignForm.only_exercices} />
<Button
type="submit"
disabled={assignInProgress}

@ -2,6 +2,12 @@ import { sveltekit } from '@sveltejs/kit/vite';
/** @type {import('vite').UserConfig} */
const config = {
server: {
hmr: {
port: 10001
}
},
plugins: [sveltekit()]
};