Compare commits
9 commits
4dcf1218d8
...
99c436ba9a
Author | SHA1 | Date | |
---|---|---|---|
99c436ba9a | |||
a7d521fbdd | |||
0c53372618 | |||
84be750ce6 | |||
3881385c9e | |||
e44cac32ac | |||
485e6b0173 | |||
f1ada8ce99 | |||
7e301b8ecb |
12 changed files with 278 additions and 61 deletions
|
@ -27,7 +27,7 @@ ADD https://web.archive.org/web/20240926154729if_/https://grammalecte.net/zip/Gr
|
|||
|
||||
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
|
||||
|
||||
FROM alpine:3.21
|
||||
FROM alpine:3.19
|
||||
|
||||
ENTRYPOINT ["/usr/bin/repochecker", "--rules-plugins=/usr/lib/epita-rules.so", "--rules-plugins=/usr/lib/file-inspector.so", "--rules-plugins=/usr/lib/grammalecte-rules.so", "--rules-plugins=/usr/lib/pcap-inspector.so", "--rules-plugins=/usr/lib/videos-rules.so"]
|
||||
|
||||
|
|
|
@ -953,6 +953,23 @@ func updateExerciceFlag(c *gin.Context) {
|
|||
flag.Help = uk.Help
|
||||
flag.IgnoreCase = uk.IgnoreCase
|
||||
flag.Multiline = uk.Multiline
|
||||
flag.ChoicesCost = uk.ChoicesCost
|
||||
flag.BonusGain = uk.BonusGain
|
||||
|
||||
if uk.CaptureRe != nil && len(*uk.CaptureRe) > 0 {
|
||||
if flag.CaptureRegexp != uk.CaptureRe && uk.Flag == "" {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Pour changer la capture_regexp, vous devez rentrer la réponse attendue à nouveau, car le flag doit être recalculé."})
|
||||
return
|
||||
}
|
||||
flag.CaptureRegexp = uk.CaptureRe
|
||||
} else {
|
||||
if flag.CaptureRegexp != nil && uk.Flag == "" {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Pour changer la capture_regexp, vous devez rentrer la réponse attendue à nouveau, car le flag doit être recalculé."})
|
||||
return
|
||||
}
|
||||
flag.CaptureRegexp = nil
|
||||
}
|
||||
|
||||
if len(uk.Flag) > 0 {
|
||||
var err error
|
||||
flag.Checksum, err = flag.ComputeChecksum([]byte(uk.Flag))
|
||||
|
@ -964,14 +981,6 @@ func updateExerciceFlag(c *gin.Context) {
|
|||
} else {
|
||||
flag.Checksum = uk.Value
|
||||
}
|
||||
flag.ChoicesCost = uk.ChoicesCost
|
||||
flag.BonusGain = uk.BonusGain
|
||||
|
||||
if uk.CaptureRe != nil && len(*uk.CaptureRe) > 0 {
|
||||
flag.CaptureRegexp = uk.CaptureRe
|
||||
} else {
|
||||
flag.CaptureRegexp = nil
|
||||
}
|
||||
|
||||
if _, err := flag.Update(); err != nil {
|
||||
log.Println("Unable to updateExerciceFlag:", err.Error())
|
||||
|
|
|
@ -34,6 +34,11 @@ func declarePasswordRoutes(router *gin.RouterGroup) {
|
|||
|
||||
c.JSON(http.StatusOK, gin.H{"password": passwd})
|
||||
})
|
||||
router.GET("/oauth-status", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"secret_defined": OidcSecret != "",
|
||||
})
|
||||
})
|
||||
router.GET("/dex.yaml", func(c *gin.Context) {
|
||||
cfg, err := genDexConfig()
|
||||
if err != nil {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
@ -294,6 +295,11 @@ func bindingTeams(c *gin.Context) {
|
|||
c.String(http.StatusOK, ret)
|
||||
}
|
||||
|
||||
type teamAssociation struct {
|
||||
Association string `json:"association"`
|
||||
TeamId int64 `json:"team_id"`
|
||||
}
|
||||
|
||||
func allAssociations(c *gin.Context) {
|
||||
teams, err := fic.GetTeams()
|
||||
if err != nil {
|
||||
|
@ -302,7 +308,7 @@ func allAssociations(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
var ret []string
|
||||
var ret []teamAssociation
|
||||
|
||||
for _, team := range teams {
|
||||
assocs, err := pki.GetTeamAssociations(TeamsDir, team.Id)
|
||||
|
@ -312,7 +318,7 @@ func allAssociations(c *gin.Context) {
|
|||
}
|
||||
|
||||
for _, a := range assocs {
|
||||
ret = append(ret, a)
|
||||
ret = append(ret, teamAssociation{a, team.Id})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -320,8 +326,21 @@ func allAssociations(c *gin.Context) {
|
|||
}
|
||||
|
||||
func importTeamsFromCyberrange(c *gin.Context) {
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"errmsg": "Failed to get file: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errmsg": "Failed to open file: " + err.Error()})
|
||||
return
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
var ut []fic.CyberrangeTeam
|
||||
err := c.ShouldBindJSON(&fic.CyberrangeAPIResponse{Data: &ut})
|
||||
err = json.NewDecoder(src).Decode(&fic.CyberrangeAPIResponse{Data: &ut})
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
|
|
|
@ -4,7 +4,7 @@ const indextpl = `<!DOCTYPE html>
|
|||
<html ng-app="FICApp">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Challenge Forensic - Administration</title>
|
||||
<title>{{ .title }} - Administration</title>
|
||||
<link href="{{.urlbase}}css/bootstrap.min.css" type="text/css" rel="stylesheet">
|
||||
<link href="{{.urlbase}}css/glyphicon.css" type="text/css" rel="stylesheet" media="screen">
|
||||
<style>
|
||||
|
@ -86,7 +86,7 @@ const indextpl = `<!DOCTYPE html>
|
|||
<body class="bg-light text-dark">
|
||||
<nav class="navbar sticky-top navbar-expand-lg navbar-dark text-light" ng-class="{'bg-dark': settings.wip, 'bg-danger': !settings.wip}">
|
||||
<a class="navbar-brand" href=".">
|
||||
<img alt="FIC" src="img/fic.png" style="height: 30px">
|
||||
<img alt="{{ .title }}" src="{{ .logo }}" style="height: 30px">
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#adminMenu" aria-controls="adminMenu" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
|
@ -95,7 +95,7 @@ const indextpl = `<!DOCTYPE html>
|
|||
<div class="collapse navbar-collapse" id="adminMenu">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item" ng-class="{'active': $location.path().startsWith('/teams')}"><a class="nav-link" href="teams">Équipes</a></li>
|
||||
<li class="nav-item" ng-class="{'active': $location.path().startsWith('/pki')}"><a class="nav-link" href="pki">PKI</a></li>
|
||||
<li class="nav-item" ng-class="{'active': $location.path().startsWith('/auth')}"><a class="nav-link" href="auth">Authentification</a></li>
|
||||
<li class="nav-item" ng-class="{'active': $location.path().startsWith('/themes')}"><a class="nav-link" href="themes">Thèmes</a></li>
|
||||
<li class="nav-item" ng-class="{'active': $location.path().startsWith('/exercices')}"><a class="nav-link" href="exercices">Exercices</a></li>
|
||||
<li class="nav-item" ng-class="{'active': $location.path().startsWith('/public')}"><a class="nav-link" href="public/0">Public</a></li>
|
||||
|
@ -124,7 +124,7 @@ const indextpl = `<!DOCTYPE html>
|
|||
</div>
|
||||
|
||||
<span id="clock" class="navbar-text" ng-controller="CountdownController" ng-cloak>
|
||||
<div style="position: absolute;">
|
||||
<div style="pointer-events: none; position: absolute;">
|
||||
<div style="position: absolute;" id="circle1" class="circle-anim border-danger"></div>
|
||||
<div style="position: absolute;" id="circle2" class="circle-anim border-info"></div>
|
||||
</div>
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
@ -25,10 +26,24 @@ var assets embed.FS
|
|||
var indexPage []byte
|
||||
|
||||
func genIndex(baseURL string) {
|
||||
tplcfg := map[string]string{
|
||||
"logo": "img/logo.png",
|
||||
"title": "Challenge",
|
||||
"urlbase": path.Clean(path.Join(baseURL+"/", "nuke"))[:len(path.Clean(path.Join(baseURL+"/", "nuke")))-4],
|
||||
}
|
||||
|
||||
ci, err := api.GetChallengeInfo()
|
||||
if err == nil && ci != nil {
|
||||
tplcfg["title"] = ci.Title
|
||||
if len(ci.MainLogo) > 0 {
|
||||
tplcfg["logo"] = "/files/logo/" + path.Base(ci.MainLogo[0])
|
||||
}
|
||||
}
|
||||
|
||||
b := bytes.NewBufferString("")
|
||||
if indexTmpl, err := template.New("index").Parse(indextpl); err != nil {
|
||||
log.Fatal("Cannot create template:", err)
|
||||
} else if err = indexTmpl.Execute(b, map[string]string{"urlbase": path.Clean(path.Join(baseURL+"/", "nuke"))[:len(path.Clean(path.Join(baseURL+"/", "nuke")))-4]}); err != nil {
|
||||
} else if err = indexTmpl.Execute(b, tplcfg); err != nil {
|
||||
log.Fatal("An error occurs during template execution:", err)
|
||||
} else {
|
||||
indexPage = b.Bytes()
|
||||
|
@ -50,6 +65,9 @@ func declareStaticRoutes(router *gin.RouterGroup, cfg *settings.Settings, baseUR
|
|||
router.GET("/", func(c *gin.Context) {
|
||||
serveIndex(c)
|
||||
})
|
||||
router.GET("/auth/*_", func(c *gin.Context) {
|
||||
serveIndex(c)
|
||||
})
|
||||
router.GET("/claims/*_", func(c *gin.Context) {
|
||||
serveIndex(c)
|
||||
})
|
||||
|
@ -104,8 +122,22 @@ func declareStaticRoutes(router *gin.RouterGroup, cfg *settings.Settings, baseUR
|
|||
})
|
||||
|
||||
router.GET("/files/*_", func(c *gin.Context) {
|
||||
// TODO: handle .gz file here
|
||||
http.ServeFile(c.Writer, c.Request, path.Join(fic.FilesDir, strings.TrimPrefix(c.Request.URL.Path, path.Join(baseURL, "files"))))
|
||||
filepath := path.Join(fic.FilesDir, strings.TrimPrefix(strings.TrimPrefix(c.Request.URL.Path, baseURL), "/files"))
|
||||
|
||||
if st, err := os.Stat(filepath); os.IsNotExist(err) || st.Size() == 0 {
|
||||
if st, err := os.Stat(filepath + ".gz"); err == nil {
|
||||
if fd, err := os.Open(filepath + ".gz"); err == nil {
|
||||
log.Println(filepath + ".gz")
|
||||
c.DataFromReader(http.StatusOK, st.Size(), "application/octet-stream", fd, map[string]string{
|
||||
"Content-Encoding": "gzip",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Println(filepath)
|
||||
c.File(filepath)
|
||||
})
|
||||
router.GET("/submissions/*_", func(c *gin.Context) {
|
||||
http.ServeFile(c.Writer, c.Request, path.Join(api.TimestampCheck, strings.TrimPrefix(c.Request.URL.Path, path.Join(baseURL, "submissions"))))
|
||||
|
|
|
@ -25,6 +25,10 @@ angular.module("FICApp", ["ngRoute", "ngResource", "ngSanitize"])
|
|||
controller: "SettingsController",
|
||||
templateUrl: "views/settings.html"
|
||||
})
|
||||
.when("/auth", {
|
||||
controller: "AuthController",
|
||||
templateUrl: "views/auth.html"
|
||||
})
|
||||
.when("/pki", {
|
||||
controller: "PKIController",
|
||||
templateUrl: "views/pki.html"
|
||||
|
@ -921,6 +925,49 @@ angular.module("FICApp")
|
|||
};
|
||||
})
|
||||
|
||||
.controller("AuthController", function ($scope, $http) {
|
||||
$scope.generateHtpasswd = function () {
|
||||
$http.post("api/htpasswd").then(function () {
|
||||
$scope.addToast('success', 'Fichier htpasswd généré avec succès');
|
||||
}, function (response) {
|
||||
$scope.addToast('danger', 'An error occurs when generating htpasswd file:', response.data.errmsg);
|
||||
});
|
||||
};
|
||||
$scope.removeHtpasswd = function () {
|
||||
$http.delete("api/htpasswd").then(function () {
|
||||
$scope.addToast('success', 'Fichier htpasswd supprimé avec succès');
|
||||
}, function (response) {
|
||||
$scope.addToast('danger', 'An error occurs when deleting htpasswd file:', response.data.errmsg);
|
||||
});
|
||||
};
|
||||
})
|
||||
|
||||
.controller("OAuthController", function ($scope, $http) {
|
||||
$scope.refreshOAuthStatus = function () {
|
||||
$http.get("api/oauth-status").then(function (res) {
|
||||
$scope.oauth_status = response.data;
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
$scope.genDexCfg = function () {
|
||||
$http.post("api/dex.yaml").then(function () {
|
||||
$http.post("api/dex-password.tpl").then(function () {
|
||||
$scope.addToast('success', 'Dex config refreshed.', "Don't forget to reload/reboot frontend host.");
|
||||
}, function (response) {
|
||||
$scope.addToast('danger', 'An error occurs when generating dex password tpl:', response.data.errmsg);
|
||||
});
|
||||
}, function (response) {
|
||||
$scope.addToast('danger', 'An error occurs when generating dex config:', response.data.errmsg);
|
||||
});
|
||||
$http.post("api/vouch-proxy.yaml").then(function () {
|
||||
$scope.addToast('success', 'VouchProxy config refreshed.', "Don't forget to reload/reboot frontend host.");
|
||||
}, function (response) {
|
||||
$scope.addToast('danger', 'An error occurs when generating VouchProxy config:', response.data.errmsg);
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
.controller("PKIController", function ($scope, $rootScope, Certificate, CACertificate, Team, $location, $http) {
|
||||
var ts = Date.now() - Date.now() % 86400000;
|
||||
var d = new Date(ts);
|
||||
|
@ -1005,20 +1052,6 @@ angular.module("FICApp")
|
|||
$scope.addToast('danger', 'An error occurs when generating certificate:', response.data.errmsg);
|
||||
});
|
||||
};
|
||||
$scope.generateHtpasswd = function () {
|
||||
$http.post("api/htpasswd").then(function () {
|
||||
$scope.addToast('success', 'Fichier htpasswd généré avec succès');
|
||||
}, function (response) {
|
||||
$scope.addToast('danger', 'An error occurs when generating htpasswd file:', response.data.errmsg);
|
||||
});
|
||||
};
|
||||
$scope.removeHtpasswd = function () {
|
||||
$http.delete("api/htpasswd").then(function () {
|
||||
$scope.addToast('success', 'Fichier htpasswd supprimé avec succès');
|
||||
}, function (response) {
|
||||
$scope.addToast('danger', 'An error occurs when deleting htpasswd file:', response.data.errmsg);
|
||||
});
|
||||
};
|
||||
})
|
||||
|
||||
.controller("PublicController", function ($scope, $rootScope, $routeParams, $location, Scene, Theme, Teams, Exercice) {
|
||||
|
@ -2284,9 +2317,9 @@ angular.module("FICApp")
|
|||
}
|
||||
$scope.saveFlag = function () {
|
||||
if (this.flag.id) {
|
||||
this.flag.$update();
|
||||
this.flag.$update().then(function() {}, function(error) { $scope.addToast('danger', 'Impossible de mettre à jour le flag :', error.data.errmsg); });
|
||||
} else {
|
||||
this.flag.$save({ exerciceId: $routeParams.exerciceId });
|
||||
this.flag.$save({ exerciceId: $routeParams.exerciceId }).then(function() {}, function(error) { $scope.addToast('danger', 'Impossible de créer le flag :', error.data.errmsg); });
|
||||
}
|
||||
$rootScope.staticFilesNeedUpdate++;
|
||||
}
|
||||
|
@ -2461,22 +2494,6 @@ angular.module("FICApp")
|
|||
}
|
||||
};
|
||||
|
||||
$scope.genDexCfg = function () {
|
||||
$http.post("api/dex.yaml").then(function () {
|
||||
$http.post("api/dex-password.tpl").then(function () {
|
||||
$scope.addToast('success', 'Dex config refreshed.', "Don't forget to reload/reboot frontend host.");
|
||||
}, function (response) {
|
||||
$scope.addToast('danger', 'An error occurs when generating dex password tpl:', response.data.errmsg);
|
||||
});
|
||||
}, function (response) {
|
||||
$scope.addToast('danger', 'An error occurs when generating dex config:', response.data.errmsg);
|
||||
});
|
||||
$http.post("api/vouch-proxy.yaml").then(function () {
|
||||
$scope.addToast('success', 'VouchProxy config refreshed.', "Don't forget to reload/reboot frontend host.");
|
||||
}, function (response) {
|
||||
$scope.addToast('danger', 'An error occurs when generating VouchProxy config:', response.data.errmsg);
|
||||
});
|
||||
}
|
||||
$scope.desactiveTeams = function () {
|
||||
$http.post("api/disableinactiveteams").then(function () {
|
||||
$scope.teams = Team.query();
|
||||
|
@ -2484,9 +2501,34 @@ angular.module("FICApp")
|
|||
$scope.addToast('danger', 'An error occurs when disabling inactive teams:', response.data.errmsg);
|
||||
});
|
||||
}
|
||||
$scope.refineTeamsColors = function () {
|
||||
$http.post("api/refine_colors").then(function () {
|
||||
$scope.teams = Team.query();
|
||||
}, function (response) {
|
||||
$scope.addToast('danger', 'An error occurs when updating teams:', response.data.errmsg);
|
||||
});
|
||||
}
|
||||
$scope.show = function (id) {
|
||||
$location.url("/teams/" + id);
|
||||
};
|
||||
$scope.triggerTeamsImport = function() {
|
||||
document.getElementById('crTeamsInput').click();
|
||||
};
|
||||
$scope.uploadFile = function() {
|
||||
var formData = new FormData();
|
||||
formData.append('file', $scope.selectedFile);
|
||||
|
||||
$http.post('api/cyberrange-teams.json', formData, {
|
||||
transformRequest: angular.identity,
|
||||
headers: {'Content-Type': undefined},
|
||||
}).then(function(response) {
|
||||
$scope.teams = response.data;
|
||||
$scope.addToast('success', 'Import des équipes', "L'import a été réalisé avec succès !");
|
||||
}, function(error) {
|
||||
console.log(error);
|
||||
$scope.addToast('danger', 'Import des équipes', error.data.errmsg);
|
||||
});
|
||||
};
|
||||
})
|
||||
.controller("TeamMembersController", function ($scope, TeamMember) {
|
||||
$scope.fields = ["firstname", "lastname", "nickname", "company"];
|
||||
|
|
|
@ -81,6 +81,22 @@ angular.module("FICApp")
|
|||
});
|
||||
}
|
||||
}
|
||||
}])
|
||||
.directive('fileModel', ['$parse', function ($parse) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function($scope, element, attrs) {
|
||||
var model = $parse(attrs.fileModel);
|
||||
var modelSetter = model.assign;
|
||||
|
||||
element.bind('change', function(){
|
||||
$scope.$apply(function(){
|
||||
modelSetter($scope, element[0].files[0]);
|
||||
$scope.uploadFile();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
angular.module("FICApp")
|
||||
|
|
86
admin/static/views/auth.html
Normal file
86
admin/static/views/auth.html
Normal file
|
@ -0,0 +1,86 @@
|
|||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h2>
|
||||
Authentification
|
||||
</h2>
|
||||
<div>
|
||||
<div class="btn-group mr-1" role="group">
|
||||
<button type="button" ng-click="generateHtpasswd()" class="btn btn-sm btn-secondary"><span class="glyphicon glyphicon-save-file" aria-hidden="true"></span> Générer <code>fichtpasswd</code></button>
|
||||
<button type="button" ng-click="removeHtpasswd()" class="btn btn-sm btn-danger"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-controller="OAuthController">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h3>
|
||||
OAuth 2
|
||||
<span class="badge badge-success" ng-if="oauth_status.secret_defined">Actif</span>
|
||||
<span class="badge badge-danger" ng-if="!oauth_status.secret_defined">Non configuré</span>
|
||||
</h3>
|
||||
<div>
|
||||
<button type="button" ng-click="genDexCfg()" class="btn btn-success mr-2"><span class="glyphicon glyphicon-refresh" aria-hidden="true"></span> DexIdP</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div ng-controller="PKIController">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h3>
|
||||
Autorité de certification
|
||||
<span class="badge badge-success" ng-if="ca.version">Générée</span>
|
||||
<span class="badge badge-danger" ng-if="!ca.version">Introuvable</span>
|
||||
</h3>
|
||||
<div>
|
||||
<a
|
||||
class="btn btn-primary"
|
||||
href="/pki"
|
||||
>
|
||||
<span class="glyphicon glyphicon-certificate" aria-hidden="true"></span>
|
||||
Gérer la PKI
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info" ng-if="!ca.version">
|
||||
<strong>Aucune CA n'a été générée pour le moment.</strong>
|
||||
</div>
|
||||
|
||||
<dl ng-if="ca.version">
|
||||
<ng-repeat ng-repeat="(k, v) in ca" class="row">
|
||||
<dt class="col-3 text-right">{{ k }}</dt>
|
||||
<dd class="col-9" ng-if="v.CommonName">/CN={{ v.CommonName }}/OU={{ v.OrganizationalUnit }}/O={{ v.Organization }}/L={{ v.Locality }}/P={{ v.Province }}/C={{ v.Country }}/</dd>
|
||||
<dd class="col-9" ng-if="!v.CommonName">{{ v }}</dd>
|
||||
</ng-repeat>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="mb-4" ng-controller="AllTeamAssociationsController">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h3>
|
||||
Association utilisateurs et équipes
|
||||
</h3>
|
||||
<div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table table-sm table-hover" ng-controller="TeamsListController" >
|
||||
<tr>
|
||||
<th class="text-right">Utilisateur</th>
|
||||
<th></th>
|
||||
<th>Équipe</th>
|
||||
</tr>
|
||||
<tr ng-repeat="association in allAssociations">
|
||||
<td class="text-right">{{ association.association }}</td>
|
||||
<td class="text-center">⬌</td>
|
||||
<td ng-repeat="team in teams" ng-if="team.id == association.team_id">
|
||||
<a ng-href="teams/{{ team.id }}">
|
||||
{{ team.name }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
|
@ -100,7 +100,7 @@
|
|||
<form ng-submit="saveFile()" class="list-group-item bg-light text-dark" ng-repeat="file in files">
|
||||
<div class="row form-group">
|
||||
<input type="text" ng-model="file.name" class="col form-control form-control-sm" placeholder="Nom de fichier">
|
||||
<a href="../files{{file.path}}" class="btn btn-sm btn-secondary col-auto"><span class="glyphicon glyphicon-download" aria-hidden="true"></span></a>
|
||||
<a href="../files{{file.path}}" target="_self" class="btn btn-sm btn-secondary col-auto"><span class="glyphicon glyphicon-download" aria-hidden="true"></span></a>
|
||||
<button type="submit" class="btn btn-sm btn-success col-auto"><span class="glyphicon glyphicon-ok" aria-hidden="true"></span></button><br>
|
||||
<button type="button" ng-click="deleteFile()" class="btn btn-sm btn-danger col-auto"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button>
|
||||
</div>
|
||||
|
|
|
@ -323,7 +323,7 @@
|
|||
<form class="row" ng-controller="AllTeamAssociationsController" ng-submit="addDelegatedQA()">
|
||||
<div class="col">
|
||||
<select class="form-control form-control-sm" ng-model="newdqa">
|
||||
<option ng-selected="newdqa == m" ng-repeat="(i,m) in allAssociations" ng-value="m">{{ m }}</option>
|
||||
<option ng-selected="newdqa == m.association" ng-repeat="(i,m) in allAssociations" ng-value="m.association">{{ m.association }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col input-group">
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
<h2>
|
||||
Équipes
|
||||
<button type="button" ng-click="show('new')" class="float-right btn btn-sm btn-primary"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Ajouter une équipe</button>
|
||||
<button type="button" ng-click="show('print')" class="float-right btn btn-sm btn-secondary mr-2"><span class="glyphicon glyphicon-print" aria-hidden="true"></span> Imprimer les équipes</button>
|
||||
<button type="button" ng-click="show('export')" class="float-right btn btn-sm btn-secondary mr-2"><span class="glyphicon glyphicon-export" aria-hidden="true"></span> Statistiques générales</button>
|
||||
<button type="button" ng-click="desactiveTeams()" class="float-right btn btn-sm btn-danger mr-2" title="Cliquer pour marquer les équipes sans certificat comme inactives (et ainsi éviter que ses fichiers ne soient générés)"><span class="glyphicon glyphicon-leaf" aria-hidden="true"></span> Désactiver les équipes inactives</button>
|
||||
<button type="button" ng-click="genDexCfg()" class="float-right btn btn-sm btn-success mr-2"><span class="glyphicon glyphicon-refresh" aria-hidden="true"></span> DexIdP</button>
|
||||
</h2>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h2>
|
||||
Équipes
|
||||
</h2>
|
||||
<div>
|
||||
<button type="button" ng-click="show('new')" class="btn btn-sm btn-primary ml-1"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Ajouter une équipe</button>
|
||||
<form class="d-inline">
|
||||
<input id="crTeamsInput" type="file" file-model="selectedFile" class="d-none" />
|
||||
<button type="button" ng-click="triggerTeamsImport()" class="btn btn-sm btn-secondary ml-1"><span class="glyphicon glyphicon-import" aria-hidden="true"></span> Import Cyberrange</button>
|
||||
</form>
|
||||
<button type="button" ng-click="show('print')" class="btn btn-sm btn-secondary ml-1" title="Imprimer les équipes et leurs membres"><span class="glyphicon glyphicon-print" aria-hidden="true"></span></button>
|
||||
<button type="button" ng-click="refineTeamsColors()" class="btn btn-sm btn-secondary ml-1" title="Réarranger automatiquement les couleurs des équipes pour maximiser le spectre utilisé"><span class="glyphicon glyphicon-adjust" aria-hidden="true"></span></button>
|
||||
<button type="button" ng-click="show('export')" class="btn btn-sm btn-secondary ml-1"><span class="glyphicon glyphicon-export" aria-hidden="true"></span> Statistiques générales</button>
|
||||
<button type="button" ng-click="desactiveTeams()" class="btn btn-sm btn-danger ml-1" title="Cliquer pour marquer les équipes sans certificat comme inactives (et ainsi éviter que ses fichiers ne soient générés)"><span class="glyphicon glyphicon-leaf" aria-hidden="true"></span> Désactiver les équipes inactives</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><input type="search" class="form-control" placeholder="Search" ng-model="query" ng-keypress="validateSearch($event)" autofocus></p>
|
||||
<table class="table table-hover table-bordered table-striped table-sm">
|
||||
|
|
Reference in a new issue