Compare commits
11 Commits
f82152a462
...
9e75386038
Author | SHA1 | Date | |
---|---|---|---|
9e75386038 | |||
a1b0e6a79b | |||
092d2256f7 | |||
28b4e7e529 | |||
32632322d4 | |||
4473166ee7 | |||
12feb91d48 | |||
03d02669ea | |||
c1924c0e92 | |||
7692f92aa4 | |||
724f985770 |
@ -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))
|
||||
})
|
||||
|
||||
|
@ -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' },
|
||||
@ -755,6 +752,12 @@ 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('repositoryUptodate', {
|
||||
bindings: {
|
||||
@ -781,9 +784,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,30 +796,28 @@ 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 + ' ?'
|
||||
|
@ -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>
|
||||
|
@ -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 }} %</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">
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -2,6 +2,12 @@ import { sveltekit } from '@sveltejs/kit/vite';
|
||||
|
||||
/** @type {import('vite').UserConfig} */
|
||||
const config = {
|
||||
server: {
|
||||
hmr: {
|
||||
port: 10000
|
||||
}
|
||||
},
|
||||
|
||||
plugins: [sveltekit()]
|
||||
};
|
||||
|
||||
|
2
go.mod
2
go.mod
@ -20,7 +20,7 @@ require (
|
||||
go.uber.org/multierr v1.11.0
|
||||
golang.org/x/crypto v0.31.0
|
||||
golang.org/x/image v0.23.0
|
||||
golang.org/x/oauth2 v0.24.0
|
||||
golang.org/x/oauth2 v0.25.0
|
||||
gopkg.in/fsnotify.v1 v1.4.7
|
||||
)
|
||||
|
||||
|
2
go.sum
2
go.sum
@ -705,6 +705,8 @@ 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/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=
|
||||
|
@ -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)
|
||||
|
||||
@ -316,3 +317,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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
|
207
qa/ui/package-lock.json
generated
207
qa/ui/package-lock.json
generated
@ -487,9 +487,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/core": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.1.tgz",
|
||||
"integrity": "sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==",
|
||||
"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": {
|
||||
@ -524,9 +524,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/js": {
|
||||
"version": "9.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz",
|
||||
"integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==",
|
||||
"version": "9.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz",
|
||||
"integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@ -544,12 +544,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/plugin-kit": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.4.tgz",
|
||||
"integrity": "sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==",
|
||||
"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": {
|
||||
@ -688,9 +689,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.1.tgz",
|
||||
"integrity": "sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==",
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.30.1.tgz",
|
||||
"integrity": "sha512-pSWY+EVt3rJ9fQ3IqlrEUtXh3cGqGtPDH1FQlNZehO2yYxCHEX1SPsz1M//NXwYfbTlcKr9WObLnJX9FsS9K1Q==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@ -702,9 +703,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.1.tgz",
|
||||
"integrity": "sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==",
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.30.1.tgz",
|
||||
"integrity": "sha512-/NA2qXxE3D/BRjOJM8wQblmArQq1YoBVJjrjoTSBS09jgUisq7bqxNHJ8kjCHeV21W/9WDGwJEWSN0KQ2mtD/w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -716,9 +717,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.1.tgz",
|
||||
"integrity": "sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==",
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.30.1.tgz",
|
||||
"integrity": "sha512-r7FQIXD7gB0WJ5mokTUgUWPl0eYIH0wnxqeSAhuIwvnnpjdVB8cRRClyKLQr7lgzjctkbp5KmswWszlwYln03Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -730,9 +731,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.1.tgz",
|
||||
"integrity": "sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==",
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.30.1.tgz",
|
||||
"integrity": "sha512-x78BavIwSH6sqfP2xeI1hd1GpHL8J4W2BXcVM/5KYKoAD3nNsfitQhvWSw+TFtQTLZ9OmlF+FEInEHyubut2OA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -744,9 +745,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.1.tgz",
|
||||
"integrity": "sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==",
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.30.1.tgz",
|
||||
"integrity": "sha512-HYTlUAjbO1z8ywxsDFWADfTRfTIIy/oUlfIDmlHYmjUP2QRDTzBuWXc9O4CXM+bo9qfiCclmHk1x4ogBjOUpUQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -758,9 +759,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.1.tgz",
|
||||
"integrity": "sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==",
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.30.1.tgz",
|
||||
"integrity": "sha512-1MEdGqogQLccphhX5myCJqeGNYTNcmTyaic9S7CG3JhwuIByJ7J05vGbZxsizQthP1xpVx7kd3o31eOogfEirw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -772,9 +773,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.1.tgz",
|
||||
"integrity": "sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==",
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.30.1.tgz",
|
||||
"integrity": "sha512-PaMRNBSqCx7K3Wc9QZkFx5+CX27WFpAMxJNiYGAXfmMIKC7jstlr32UhTgK6T07OtqR+wYlWm9IxzennjnvdJg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@ -786,9 +787,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.1.tgz",
|
||||
"integrity": "sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==",
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.30.1.tgz",
|
||||
"integrity": "sha512-B8Rcyj9AV7ZlEFqvB5BubG5iO6ANDsRKlhIxySXcF1axXYUyqwBok+XZPgIYGBgs7LDXfWfifxhw0Ik57T0Yug==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@ -800,9 +801,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.1.tgz",
|
||||
"integrity": "sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==",
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.30.1.tgz",
|
||||
"integrity": "sha512-hqVyueGxAj3cBKrAI4aFHLV+h0Lv5VgWZs9CUGqr1z0fZtlADVV1YPOij6AhcK5An33EXaxnDLmJdQikcn5NEw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -814,9 +815,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.1.tgz",
|
||||
"integrity": "sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==",
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.30.1.tgz",
|
||||
"integrity": "sha512-i4Ab2vnvS1AE1PyOIGp2kXni69gU2DAUVt6FSXeIqUCPIR3ZlheMW3oP2JkukDfu3PsexYRbOiJrY+yVNSk9oA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -828,9 +829,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.28.1.tgz",
|
||||
"integrity": "sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==",
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.30.1.tgz",
|
||||
"integrity": "sha512-fARcF5g296snX0oLGkVxPmysetwUk2zmHcca+e9ObOovBR++9ZPOhqFUM61UUZ2EYpXVPN1redgqVoBB34nTpQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
@ -842,9 +843,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.1.tgz",
|
||||
"integrity": "sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==",
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.30.1.tgz",
|
||||
"integrity": "sha512-GLrZraoO3wVT4uFXh67ElpwQY0DIygxdv0BNW9Hkm3X34wu+BkqrDrkcsIapAY+N2ATEbvak0XQ9gxZtCIA5Rw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@ -856,9 +857,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.1.tgz",
|
||||
"integrity": "sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==",
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.30.1.tgz",
|
||||
"integrity": "sha512-0WKLaAUUHKBtll0wvOmh6yh3S0wSU9+yas923JIChfxOaaBarmb/lBKPF0w/+jTVozFnOXJeRGZ8NvOxvk/jcw==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@ -870,9 +871,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.1.tgz",
|
||||
"integrity": "sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==",
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.30.1.tgz",
|
||||
"integrity": "sha512-GWFs97Ruxo5Bt+cvVTQkOJ6TIx0xJDD/bMAOXWJg8TCSTEK8RnFeOeiFTxKniTc4vMIaWvCplMAFBt9miGxgkA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@ -884,9 +885,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.1.tgz",
|
||||
"integrity": "sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==",
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.30.1.tgz",
|
||||
"integrity": "sha512-UtgGb7QGgXDIO+tqqJ5oZRGHsDLO8SlpE4MhqpY9Llpzi5rJMvrK6ZGhsRCST2abZdBqIBeXW6WPD5fGK5SDwg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -898,9 +899,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.1.tgz",
|
||||
"integrity": "sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==",
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.30.1.tgz",
|
||||
"integrity": "sha512-V9U8Ey2UqmQsBT+xTOeMzPzwDzyXmnAoO4edZhL7INkwQcaW1Ckv3WJX3qrrp/VHaDkEWIBWhRwP47r8cdrOow==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -912,9 +913,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.1.tgz",
|
||||
"integrity": "sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==",
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.30.1.tgz",
|
||||
"integrity": "sha512-WabtHWiPaFF47W3PkHnjbmWawnX/aE57K47ZDT1BXTS5GgrBUEpvOzq0FI0V/UYzQJgdb8XlhVNH8/fwV8xDjw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -926,9 +927,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.1.tgz",
|
||||
"integrity": "sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==",
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.30.1.tgz",
|
||||
"integrity": "sha512-pxHAU+Zv39hLUTdQQHUVHf4P+0C47y/ZloorHpzs2SXMRqeAWmGghzAhfOlzFHHwjvgokdFAhC4V+6kC1lRRfw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@ -940,9 +941,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.1.tgz",
|
||||
"integrity": "sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==",
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.30.1.tgz",
|
||||
"integrity": "sha512-D6qjsXGcvhTjv0kI4fU8tUuBDF/Ueee4SVX79VfNDXZa64TfCW1Slkb6Z7O1p7vflqZjcmOVdZlqf8gvJxc6og==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -964,9 +965,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sveltejs/kit": {
|
||||
"version": "2.15.1",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.15.1.tgz",
|
||||
"integrity": "sha512-8t7D3hQHbUDMiaQ2RVnjJJ/+Ur4Fn/tkeySJCsHtX346Q9cp3LAnav8xXdfuqYNJwpUGX0x3BqF1uvbmXQw93A==",
|
||||
"version": "2.15.2",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.15.2.tgz",
|
||||
"integrity": "sha512-p208T1kdM6zd8k4YXIUM60pLWQ8dZqehXSiqn4NulXHyHibX53uIAL2xtNL8GjxX2IVPqPRT978MwVYhCKExdQ==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
@ -1415,19 +1416,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "9.17.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz",
|
||||
"integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==",
|
||||
"version": "9.18.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz",
|
||||
"integrity": "sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==",
|
||||
"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.17.0",
|
||||
"@eslint/plugin-kit": "^0.2.3",
|
||||
"@eslint/js": "9.18.0",
|
||||
"@eslint/plugin-kit": "^0.2.5",
|
||||
"@humanfs/node": "^0.16.6",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
"@humanwhocodes/retry": "^0.4.1",
|
||||
@ -1569,9 +1570,9 @@
|
||||
}
|
||||
},
|
||||
"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"
|
||||
},
|
||||
@ -2312,9 +2313,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": {
|
||||
@ -2343,9 +2344,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.28.1.tgz",
|
||||
"integrity": "sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==",
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.30.1.tgz",
|
||||
"integrity": "sha512-mlJ4glW020fPuLi7DkM/lN97mYEZGWeqBnrljzN0gs7GLctqX3lNWxKQ7Gl712UAX+6fog/L3jh4gb7R6aVi3w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -2359,25 +2360,25 @@
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.28.1",
|
||||
"@rollup/rollup-android-arm64": "4.28.1",
|
||||
"@rollup/rollup-darwin-arm64": "4.28.1",
|
||||
"@rollup/rollup-darwin-x64": "4.28.1",
|
||||
"@rollup/rollup-freebsd-arm64": "4.28.1",
|
||||
"@rollup/rollup-freebsd-x64": "4.28.1",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.28.1",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.28.1",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.28.1",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.28.1",
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "4.28.1",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.28.1",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.28.1",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.28.1",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.28.1",
|
||||
"@rollup/rollup-linux-x64-musl": "4.28.1",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.28.1",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.28.1",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.28.1",
|
||||
"@rollup/rollup-android-arm-eabi": "4.30.1",
|
||||
"@rollup/rollup-android-arm64": "4.30.1",
|
||||
"@rollup/rollup-darwin-arm64": "4.30.1",
|
||||
"@rollup/rollup-darwin-x64": "4.30.1",
|
||||
"@rollup/rollup-freebsd-arm64": "4.30.1",
|
||||
"@rollup/rollup-freebsd-x64": "4.30.1",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.30.1",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.30.1",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.30.1",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.30.1",
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "4.30.1",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.30.1",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.30.1",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.30.1",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.30.1",
|
||||
"@rollup/rollup-linux-x64-musl": "4.30.1",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.30.1",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.30.1",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.30.1",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
|
@ -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>
|
||||
|
@ -2,6 +2,12 @@ import { sveltekit } from '@sveltejs/kit/vite';
|
||||
|
||||
/** @type {import('vite').UserConfig} */
|
||||
const config = {
|
||||
server: {
|
||||
hmr: {
|
||||
port: 10001
|
||||
}
|
||||
},
|
||||
|
||||
plugins: [sveltekit()]
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user