Compare commits
8 Commits
Author | SHA1 | Date |
---|---|---|
nemunaire | 1713db351e | |
nemunaire | 8c2aa18495 | |
nemunaire | 9fd786cb04 | |
nemunaire | fd8778f533 | |
nemunaire | b2051ac3fe | |
nemunaire | 83b3600e60 | |
nemunaire | 6cc54635a5 | |
nemunaire | 047e8f63a7 |
|
@ -85,6 +85,14 @@ func listTeam(args []string, body []byte) (interface{}, error) {
|
|||
return fic.MyJSONTeam(team, true)
|
||||
} else if args[1] == "wait.json" {
|
||||
return fic.MyJSONTeam(team, false)
|
||||
} else if args[1] == "stats.json" {
|
||||
if team != nil {
|
||||
return team.GetStats()
|
||||
} else {
|
||||
return fic.GetTeamsStats(nil)
|
||||
}
|
||||
} else if args[1] == "tries" {
|
||||
return fic.GetTries(team, nil)
|
||||
} else if team != nil && args[1] == "members" {
|
||||
return team.GetMembers()
|
||||
} else if args[1] == "certificate" && team != nil {
|
||||
|
@ -95,6 +103,8 @@ func listTeam(args []string, body []byte) (interface{}, error) {
|
|||
} else if len(args) == 1 {
|
||||
if args[0] == "teams.json" {
|
||||
return fic.ExportTeams()
|
||||
} else if args[0] == "tries" {
|
||||
return fic.GetTries(nil, nil)
|
||||
} else if args[0] == "nginx" {
|
||||
return nginxGenTeam()
|
||||
} else if args[0] == "binding" {
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
|
@ -2,15 +2,40 @@
|
|||
<html ng-app="FICApp">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Challenge Forensic FIC 2016 - Administration</title>
|
||||
<title>Challenge Forensic - Administration</title>
|
||||
<link href="/css/bootstrap.min.css" rel="stylesheet">
|
||||
<base href="/">
|
||||
<script src="//d3js.org/d3.v3.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="page-header">
|
||||
<h1>Challenge FIC Epita!</h1>
|
||||
<nav class="navbar navbar-inverse navbar-static-top">
|
||||
<div class="container">
|
||||
|
||||
<div class="navbar-header">
|
||||
<a class="navbar-brand" href="/">
|
||||
<img alt="FIC" src="img/fic.png" style="height: 100%">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="/teams">Équipes</a></li>
|
||||
<li><a href="/themes">Thèmes</a></li>
|
||||
<li><a href="/exercices">Exercices</a></li>
|
||||
<li><a href="/events">Événements</a></li>
|
||||
</ul>
|
||||
|
||||
<p id="clock" class="navbar-text navbar-right" ng-controller="CountdownController">
|
||||
<span id="hours">{{ time.hours | time }}</span>
|
||||
<span class="point">:</span>
|
||||
<span id="min">{{ time.minutes | time }}</span>
|
||||
<span class="point">:</span>
|
||||
<span id="sec">{{ time.seconds | time }}</span>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-sm-12" ng-view></div>
|
||||
</div>
|
||||
|
|
|
@ -17,34 +17,89 @@ angular.module("FICApp", ["ngRoute", "ngResource"])
|
|||
controller: "TeamsListController",
|
||||
templateUrl: "views/team-list.html"
|
||||
})
|
||||
.when("/teams/:teamId", {
|
||||
controller: "TeamController",
|
||||
templateUrl: "views/team.html"
|
||||
})
|
||||
.when("/teams/new", {
|
||||
controller: "TeamNewController",
|
||||
templateUrl: "views/team-new.html"
|
||||
})
|
||||
.when("/", {
|
||||
templateUrl: "views/home.html"
|
||||
});
|
||||
$locationProvider.html5Mode(true);
|
||||
});
|
||||
|
||||
angular.module("FICApp")
|
||||
.factory("Version", function($resource) {
|
||||
return $resource("/api/version")
|
||||
})
|
||||
.factory("Team", function($resource) {
|
||||
return $resource("/api/teams/:teamId", { teamId: '@id' }, {
|
||||
'save': {method: 'PATCH'},
|
||||
})
|
||||
});
|
||||
angular.module("FICApp")
|
||||
})
|
||||
.factory("TeamMember", function($resource) {
|
||||
return $resource("/api/teams/:teamId/members", { teamId: '@id' })
|
||||
})
|
||||
.factory("TeamMy", function($resource) {
|
||||
return $resource("/api/teams/:teamId/my.json", { teamId: '@id' })
|
||||
})
|
||||
.factory("Teams", function($resource) {
|
||||
return $resource("/api/teams/teams.json")
|
||||
})
|
||||
.factory("TeamStats", function($resource) {
|
||||
return $resource("/api/teams/:teamId/stats.json", { teamId: '@id' })
|
||||
})
|
||||
.factory("TeamPresence", function($resource) {
|
||||
return $resource("/api/teams/:teamId/tries", { teamId: '@id' })
|
||||
})
|
||||
.factory("Theme", function($resource) {
|
||||
return $resource("/api/themes/:themeId", null, {
|
||||
'save': {method: 'PATCH'},
|
||||
})
|
||||
});
|
||||
angular.module("FICApp")
|
||||
.factory("Exercice", function($resource) {
|
||||
return $resource("/api/themes/:themeId/:exerciceId", null, {
|
||||
'query': {method: "GET", url: "/api/themes/:themeId/exercices", isArray: true},
|
||||
'save': {method: 'PATCH'},
|
||||
})
|
||||
.factory("Themes", function($resource) {
|
||||
return $resource("/api/themes/themes.json", null, {
|
||||
'get': {method: 'GET'},
|
||||
})
|
||||
})
|
||||
.factory("Exercice", function($resource) {
|
||||
return $resource("/api/exercices/:exerciceId")
|
||||
});
|
||||
|
||||
String.prototype.capitalize = function() {
|
||||
return this
|
||||
.toLowerCase()
|
||||
.replace(
|
||||
/(^|\s)([a-z])/g,
|
||||
function(m,p1,p2) { return p1+p2.toUpperCase(); }
|
||||
);
|
||||
}
|
||||
|
||||
angular.module("FICApp")
|
||||
.filter("capitalize", function() {
|
||||
return function(input) {
|
||||
return input.capitalize();
|
||||
}
|
||||
})
|
||||
.filter("time", function() {
|
||||
return function(input) {
|
||||
if (input == undefined) {
|
||||
return "--";
|
||||
} else if (input >= 10) {
|
||||
return input;
|
||||
} else {
|
||||
return "0" + input;
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
.controller("VersionController", function($scope, Version) {
|
||||
$scope.v = Version.get();
|
||||
})
|
||||
|
||||
.controller("ThemesListController", function($scope, Theme, $location) {
|
||||
$scope.themes = Theme.query();
|
||||
$scope.fields = ["id", "name"];
|
||||
|
@ -81,14 +136,278 @@ angular.module("FICApp")
|
|||
|
||||
.controller("TeamsListController", function($scope, Team, $location) {
|
||||
$scope.teams = Team.query();
|
||||
$scope.fields = ["id", "name"];
|
||||
$scope.fields = ["id", "name", "initialName"];
|
||||
|
||||
$scope.show = function(id) {
|
||||
$location.url("/teams/" + id);
|
||||
};
|
||||
})
|
||||
.controller("TeamController", function($scope, Team, TeamMember, $routeParams) {
|
||||
$scope.team = Team.get({ teamId: $routeParams.teamId });
|
||||
$scope.members = TeamMember.query({ teamId: $routeParams.teamId });
|
||||
})
|
||||
.controller("TeamStatsController", function($scope, TeamStats, $routeParams) {
|
||||
$scope.teamstats = TeamStats.get({ teamId: $routeParams.teamId });
|
||||
$scope.teamstats.$promise.then(function(res) {
|
||||
solvedByLevelPie("#pieLevels", res.levels);
|
||||
solvedByThemesPie("#pieThemes", res.themes);
|
||||
});
|
||||
})
|
||||
.controller("TeamExercicesController", function($scope, Teams, Themes, TeamMy, Exercice, $routeParams) {
|
||||
$scope.teams = Teams.get();
|
||||
$scope.themes = Themes.get();
|
||||
$scope.exercices = Exercice.query();
|
||||
$scope.my = TeamMy.get({ teamId: $routeParams.teamId });
|
||||
|
||||
$scope.teams.$promise.then(function(res){
|
||||
$scope.nb_teams = 0;
|
||||
$scope.nb_reg_teams = Object.keys(res).length;
|
||||
angular.forEach(res, function(team, tid) {
|
||||
if (team.rank)
|
||||
$scope.nb_teams += 1;
|
||||
}, 0);
|
||||
});
|
||||
|
||||
$scope.my.$promise.then(function(res){
|
||||
$scope.solved_exercices = 0;
|
||||
angular.forEach(res.exercices, function(exercice, eid) {
|
||||
if (exercice.solved) {
|
||||
$scope.solved_exercices += 1;
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
|
||||
})
|
||||
.controller("TeamNewController", function($scope, Team, $location) {
|
||||
$scope.contact = new Team({
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
.controller("PresenceController", function($scope, TeamPresence, $routeParams) {
|
||||
$scope.presence = TeamPresence.query({ teamId: $routeParams.teamId });
|
||||
$scope.presence.$promise.then(function(res) {
|
||||
presenceCal("#presenceCal", res);
|
||||
});
|
||||
})
|
||||
.controller("CountdownController", function($scope, $http, $timeout) {
|
||||
$scope.time = {};
|
||||
function updTime() {
|
||||
$timeout.cancel($scope.cbm);
|
||||
$scope.cbm = $timeout(updTime, 1000);
|
||||
if (sessionStorage.userService) {
|
||||
var time = angular.fromJson(sessionStorage.userService);
|
||||
var srv_cur = (Date.now() + (time.cu * 1000 - time.he)) / 1000;
|
||||
var remain = time.du;
|
||||
if (time.st == Math.floor(srv_cur)) {
|
||||
$scope.refresh(true);
|
||||
}
|
||||
if (time.st > 0 && time.st <= srv_cur) {
|
||||
remain = time.st + time.du - srv_cur;
|
||||
}
|
||||
if (remain < 0) {
|
||||
remain = 0;
|
||||
$scope.time.end = true;
|
||||
$scope.time.expired = true;
|
||||
} else if (remain < 60) {
|
||||
$scope.time.end = false;
|
||||
$scope.time.expired = true;
|
||||
} else {
|
||||
$scope.time.end = false;
|
||||
$scope.time.expired = false;
|
||||
}
|
||||
$scope.time.start = time.st * 1000;
|
||||
$scope.time.duration = time.du;
|
||||
$scope.time.remaining = remain;
|
||||
$scope.time.hours = Math.floor(remain / 3600);
|
||||
$scope.time.minutes = Math.floor((remain % 3600) / 60);
|
||||
$scope.time.seconds = Math.floor(remain % 60);
|
||||
}
|
||||
}
|
||||
|
||||
$http.get("/time.json").success(function(time) {
|
||||
time.he = (new Date()).getTime();
|
||||
sessionStorage.userService = angular.toJson(time);
|
||||
updTime();
|
||||
});
|
||||
});
|
||||
|
||||
function solvedByLevelPie(location, data) {
|
||||
var width = d3.select(location).node().getBoundingClientRect().width - 10,
|
||||
height = d3.select(location).node().getBoundingClientRect().width - 10,
|
||||
radius = Math.min(width, height) / 2,
|
||||
innerRadius = 0.1 * radius;
|
||||
|
||||
var color = d3.scale.ordinal()
|
||||
.range(["#9E0041", "#C32F4B", "#E1514B", "#F47245", "#FB9F59", "#FEC574", "#FAE38C", "#EAD195", "#C7E89E", "#9CD6A4", "#6CC4A4", "#4D9DB4", "#4776B4", "#5E4EA1"]);
|
||||
|
||||
var pie = d3.layout.pie()
|
||||
.sort(null)
|
||||
.value(function(d) { return d.width; });
|
||||
|
||||
var arc = d3.svg.arc()
|
||||
.innerRadius(innerRadius)
|
||||
.outerRadius(function (d) {
|
||||
return (radius - innerRadius) * (d.data.score / 100.0) + innerRadius;
|
||||
});
|
||||
|
||||
var outlineArc = d3.svg.arc()
|
||||
.innerRadius(innerRadius)
|
||||
.outerRadius(radius);
|
||||
|
||||
var svg = d3.select(location).append("svg")
|
||||
.attr("width", width)
|
||||
.attr("height", height)
|
||||
.append("g")
|
||||
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
|
||||
|
||||
data.forEach(function(d) {
|
||||
d.score = d.solved * 100 / d.total;
|
||||
d.width = d.tries + 1;
|
||||
});
|
||||
|
||||
var path = svg.selectAll(".solidArc")
|
||||
.data(pie(data))
|
||||
.enter().append("path")
|
||||
.attr("fill", function(d) { return color(d.data.tip); })
|
||||
.attr("class", "solidArc")
|
||||
.attr("stroke", "gray")
|
||||
.attr("d", arc);
|
||||
|
||||
var outerPath = svg.selectAll(".outlineArc")
|
||||
.data(pie(data))
|
||||
.enter().append("path")
|
||||
.attr("fill", "none")
|
||||
.attr("stroke", "gray")
|
||||
.attr("class", "outlineArc")
|
||||
.attr("d", outlineArc);
|
||||
|
||||
var labelArc = d3.svg.arc()
|
||||
.outerRadius(0.8 * radius)
|
||||
.innerRadius(0.8 * radius);
|
||||
|
||||
svg.selectAll(".labelArc")
|
||||
.data(pie(data))
|
||||
.enter().append("text")
|
||||
.attr("transform", function(d) { return "translate(" + labelArc.centroid(d) + ")"; })
|
||||
.attr("dy", ".35em")
|
||||
.attr("text-anchor", "middle")
|
||||
.text(function(d) { return d.data.tip + ": " + d.data.solved + "/" + d.data.total; });
|
||||
|
||||
svg.selectAll(".label2Arc")
|
||||
.data(pie(data))
|
||||
.enter().append("text")
|
||||
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
|
||||
.attr("dy", ".35em")
|
||||
.attr("text-anchor", "middle")
|
||||
.text(function(d) { return d.data.tries; });
|
||||
}
|
||||
|
||||
function solvedByThemesPie(location, data) {
|
||||
var width = d3.select(location).node().getBoundingClientRect().width,
|
||||
height = d3.select(location).node().getBoundingClientRect().width,
|
||||
radius = Math.min(width, height) / 2,
|
||||
innerRadius = 0.1 * radius;
|
||||
|
||||
var color = d3.scale.ordinal()
|
||||
.range(["#9E0041", "#C32F4B", "#E1514B", "#F47245", "#FB9F59", "#FEC574", "#FAE38C", "#EAD195", "#C7E89E", "#9CD6A4", "#6CC4A4", "#4D9DB4", "#4776B4", "#5E4EA1"]);
|
||||
|
||||
var pie = d3.layout.pie()
|
||||
.sort(null)
|
||||
.value(function(d) { return d.width; });
|
||||
|
||||
var arc = d3.svg.arc()
|
||||
.innerRadius(innerRadius)
|
||||
.outerRadius(function (d) {
|
||||
return (radius - innerRadius) * (d.data.score / 100.0) + innerRadius;
|
||||
});
|
||||
|
||||
var outlineArc = d3.svg.arc()
|
||||
.innerRadius(innerRadius)
|
||||
.outerRadius(radius);
|
||||
|
||||
var svg = d3.select(location).append("svg")
|
||||
.attr("width", width)
|
||||
.attr("height", height)
|
||||
.append("g")
|
||||
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
|
||||
|
||||
data.forEach(function(d) {
|
||||
d.score = d.solved * 100 / d.total;
|
||||
d.width = d.tries + 0.5;
|
||||
});
|
||||
|
||||
var path = svg.selectAll(".solidArc")
|
||||
.data(pie(data))
|
||||
.enter().append("path")
|
||||
.attr("fill", function(d) { return color(d.data.tip); })
|
||||
.attr("class", "solidArc")
|
||||
.attr("stroke", "gray")
|
||||
.attr("d", arc);
|
||||
|
||||
var outerPath = svg.selectAll(".outlineArc")
|
||||
.data(pie(data))
|
||||
.enter().append("path")
|
||||
.attr("fill", "none")
|
||||
.attr("stroke", "gray")
|
||||
.attr("class", "outlineArc")
|
||||
.attr("d", outlineArc);
|
||||
|
||||
svg.selectAll(".label2Arc")
|
||||
.data(pie(data))
|
||||
.enter().append("text")
|
||||
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
|
||||
.attr("dy", ".35em")
|
||||
.attr("text-anchor", "middle")
|
||||
.text(function(d) { return d.data.solved; });
|
||||
|
||||
var labelArc = d3.svg.arc()
|
||||
.outerRadius(0.8 * radius)
|
||||
.innerRadius(0.8 * radius);
|
||||
|
||||
svg.selectAll(".labelArc")
|
||||
.data(pie(data))
|
||||
.enter().append("text")
|
||||
.attr("transform", function(d) { return "translate(" + labelArc.centroid(d) + ")"; })
|
||||
.attr("dy", ".35em")
|
||||
.attr("text-anchor", "middle")
|
||||
.text(function(d) { return d.data.tip + ": " + d.data.tries; });
|
||||
}
|
||||
|
||||
function presenceCal(location, data) {
|
||||
var width = d3.select(location).node().getBoundingClientRect().width,
|
||||
height = 80,
|
||||
cellSize = 17; // cell size
|
||||
|
||||
var percent = d3.format(".1%"),
|
||||
format = d3.time.format("%H:%M");
|
||||
|
||||
var color = d3.scale.quantize()
|
||||
.domain([0, 16])
|
||||
.range(d3.range(8).map(function(d) { return "q" + d + "-8"; }));
|
||||
|
||||
var svg = d3.select(location).selectAll("svg")
|
||||
.data(d3.range(26, 29))
|
||||
.enter().append("svg")
|
||||
.attr("width", width)
|
||||
.attr("height", height)
|
||||
.attr("class", "RdYlGn")
|
||||
.append("g")
|
||||
.attr("transform", "translate(" + ((width - cellSize * 24) / 2) + "," + (height - cellSize * 4 - 1) + ")");
|
||||
|
||||
svg.append("text")
|
||||
.attr("transform", "translate(-6," + cellSize * 2.6 + ")rotate(-90)")
|
||||
.style("text-anchor", "middle")
|
||||
.text(function(d) { return d + "-02"; });
|
||||
|
||||
var rect = svg.selectAll(".quarter")
|
||||
.data(function(d) { return d3.time.minutes(new Date(2016, 1, d, 0), new Date(2016, 1, d, 24), 15); })
|
||||
.enter().append("rect")
|
||||
.attr("width", cellSize)
|
||||
.attr("height", cellSize)
|
||||
.attr("class", function(d) { return color(data.reduce(function(prev, cur){
|
||||
cur = new Date(cur).getTime();
|
||||
dv = d.getTime();
|
||||
return prev + ((dv <= cur && cur < dv+15*60000)?1:0);
|
||||
}, 0));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<div class="well well-lg">
|
||||
<h3>Interface d'administration du challenge</h3>
|
||||
<p>
|
||||
Sélectionnez une action dans le menu ci-dessus.
|
||||
</p>
|
||||
<p ng-controller="VersionController">
|
||||
Version de l'API : {{ v.version }}
|
||||
</p>
|
||||
</div>
|
|
@ -0,0 +1,54 @@
|
|||
<style>
|
||||
.RdYlGn .q0-8{fill:rgb(245,250,250)}
|
||||
.RdYlGn .q1-8{fill:rgb(190,200,200)}
|
||||
.RdYlGn .q2-8{fill:rgb(170,180,180)}
|
||||
.RdYlGn .q3-8{fill:rgb(150,160,160)}
|
||||
.RdYlGn .q4-8{fill:rgb(130,140,140)}
|
||||
.RdYlGn .q5-8{fill:rgb(110,120,120)}
|
||||
.RdYlGn .q6-8{fill:rgb(90,100,100)}
|
||||
.RdYlGn .q7-8{fill:rgb(70,80,80)}
|
||||
</style>
|
||||
|
||||
<h1>{{ team.name }}<span ng-show="team.name != team.initialName"> ({{ team.initialName}})</span> <small><span ng-repeat="member in members"><span ng-show="$last && !$first"> et </span><span ng-show="$middle">, </span>{{ member.firstname | capitalize }} <em ng-show="member.nickname">{{ member.nickname }}</em> {{ member.lastname | capitalize }}</span></small></h1>
|
||||
|
||||
<div ng-controller="TeamExercicesController">
|
||||
|
||||
<dl class="dl-horizontal">
|
||||
<dt>Points</dt>
|
||||
<dd>{{ my.score }}</dd>
|
||||
<dt>Classement</dt>
|
||||
<dd>{{ teams[my.team_id].rank }}/{{ nb_teams }} ({{ nb_reg_teams }} registered teams)</dd>
|
||||
</dl>
|
||||
|
||||
<h2>Présence</h2>
|
||||
|
||||
<div id="presenceCal" ng-controller="PresenceController">
|
||||
</div>
|
||||
|
||||
<h2>Exercices résolus : {{ solved_exercices }}/{{ exercices.length }} {{ solved_exercices * 100 / exercices.length | number:0 }}%</h2>
|
||||
|
||||
<dl>
|
||||
<div style="float: left;padding: 0 5px; margin: 5px; border: 1px solid #ccc; border-radius: 3px; min-width: 5vw" ng-repeat="(tid,theme) in themes" class="text-center">
|
||||
<dt>{{ theme.name }}</dt>
|
||||
<dd>
|
||||
<ul class="list-unstyled">
|
||||
<li ng-repeat="(eid,exercice) in theme.exercices" ng-show="my.exercices[eid] && my.exercices[eid].solved"><a href="https://fic.srs.epita.fr/{{ my.exercices[eid].theme_id }}/{{ eid }}" target="_blank"><abbr title="{{ my.exercices[eid].statement }}">{{ exercice.title }}</abbr></a> (<abbr title="{{ my.exercices[eid].solved_time | date:'mediumDate' }} à {{ my.exercices[eid].solved_time | date:'mediumTime' }}">{{ my.exercices[eid].solved_number }}<sup>e</sup></abbr>)</li>
|
||||
</ul>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div class="container" ng-controller="TeamStatsController">
|
||||
<div class="row">
|
||||
<div class="col-sm-6" id="pieLevels">
|
||||
<h4 class="text-center">Tentatives par niveaux</h4>
|
||||
</div>
|
||||
<div class="col-sm-6" id="pieThemes">
|
||||
<h4 class="text-center">Tentatives par thèmes</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
|
@ -32,7 +32,12 @@
|
|||
<div class="navbar navbar-default">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="navbar-header col-sm-3">
|
||||
<div class="navbar-header col-sm-3" ng-show="!(time.start || my.team_id)">
|
||||
<a href="https://www.forum-fic.com/">
|
||||
<img src="/img/fic.png" alt="Forum International de la Cybersécurité" class="center-block">
|
||||
</a>
|
||||
</div>
|
||||
<div class="navbar-header col-sm-3" ng-show="(time.start || my.team_id)">
|
||||
<a href="/">
|
||||
<img src="/img/fic.png" alt="Forum International de la Cybersécurité" class="center-block">
|
||||
</a>
|
||||
|
@ -49,8 +54,18 @@
|
|||
<span class="point">:</span>
|
||||
<span id="sec">{{ time.seconds | time }}</span>
|
||||
</div>
|
||||
<div id="clock" class="col-sm-7" ng-show="!(!time.start || my.team_id)">
|
||||
{{ time.start | date:"shortDate" }}
|
||||
<div id="clock" class="col-sm-7" ng-show="!(time.start || my.team_id)" style="padding: 25px">
|
||||
<div class="btn-group btn-group-justified btn-group-lg">
|
||||
<a class="btn btn-default" href="/">
|
||||
<span class="glyphicon glyphicon-home"></span> Accueil
|
||||
</a>
|
||||
<a class="btn btn-default" href="/rank">
|
||||
<span class="glyphicon glyphicon-list"></span> Classement
|
||||
</a>
|
||||
<a class="btn btn-default" href="https://www.youtube.com/playlist?list=PLSJ8QLhKMtQv7jRhdAn9wXSMYTsvqfieX">
|
||||
<span class="glyphicon glyphicon-blackboard"></span> Vidéos
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -101,6 +116,7 @@
|
|||
<script src="/js/bootstrap.min.js"></script>
|
||||
<script src="/js/angular-route.min.js"></script>
|
||||
<script src="/js/angular-sanitize.min.js"></script>
|
||||
<script src="/js/i18n/angular-locale_fr-fr.js"></script>>
|
||||
<script src="/js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -92,7 +92,7 @@ angular.module("FICApp")
|
|||
}
|
||||
}
|
||||
})
|
||||
.controller("DataController", function($scope, $http, $rootScope, $timeout) {
|
||||
.controller("DataController", function($sce, $scope, $http, $rootScope, $timeout) {
|
||||
var actMenu = function() {
|
||||
if ($scope.my && $scope.themes) {
|
||||
angular.forEach($scope.themes, function(theme, key) {
|
||||
|
@ -139,6 +139,11 @@ angular.module("FICApp")
|
|||
}
|
||||
$http.get("/my.json").success(function(my) {
|
||||
$scope.my = my;
|
||||
angular.forEach($scope.my.exercices, function(exercice, eid) {
|
||||
if (exercice.video_uri) {
|
||||
exercice.video_uri = $sce.trustAsResourceUrl(exercice.video_uri);
|
||||
}
|
||||
});
|
||||
actMenu();
|
||||
});
|
||||
console.log("refresh!");
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
'use strict';
|
||||
angular.module("ngLocale", [], ["$provide", function($provide) {
|
||||
var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"};
|
||||
$provide.value("$locale", {
|
||||
"DATETIME_FORMATS": {
|
||||
"AMPMS": [
|
||||
"AM",
|
||||
"PM"
|
||||
],
|
||||
"DAY": [
|
||||
"dimanche",
|
||||
"lundi",
|
||||
"mardi",
|
||||
"mercredi",
|
||||
"jeudi",
|
||||
"vendredi",
|
||||
"samedi"
|
||||
],
|
||||
"ERANAMES": [
|
||||
"avant J\u00e9sus-Christ",
|
||||
"apr\u00e8s J\u00e9sus-Christ"
|
||||
],
|
||||
"ERAS": [
|
||||
"av. J.-C.",
|
||||
"ap. J.-C."
|
||||
],
|
||||
"FIRSTDAYOFWEEK": 0,
|
||||
"MONTH": [
|
||||
"janvier",
|
||||
"f\u00e9vrier",
|
||||
"mars",
|
||||
"avril",
|
||||
"mai",
|
||||
"juin",
|
||||
"juillet",
|
||||
"ao\u00fbt",
|
||||
"septembre",
|
||||
"octobre",
|
||||
"novembre",
|
||||
"d\u00e9cembre"
|
||||
],
|
||||
"SHORTDAY": [
|
||||
"dim.",
|
||||
"lun.",
|
||||
"mar.",
|
||||
"mer.",
|
||||
"jeu.",
|
||||
"ven.",
|
||||
"sam."
|
||||
],
|
||||
"SHORTMONTH": [
|
||||
"janv.",
|
||||
"f\u00e9vr.",
|
||||
"mars",
|
||||
"avr.",
|
||||
"mai",
|
||||
"juin",
|
||||
"juil.",
|
||||
"ao\u00fbt",
|
||||
"sept.",
|
||||
"oct.",
|
||||
"nov.",
|
||||
"d\u00e9c."
|
||||
],
|
||||
"STANDALONEMONTH": [
|
||||
"Janvier",
|
||||
"F\u00e9vrier",
|
||||
"Mars",
|
||||
"Avril",
|
||||
"Mai",
|
||||
"Juin",
|
||||
"Juillet",
|
||||
"Ao\u00fbt",
|
||||
"Septembre",
|
||||
"Octobre",
|
||||
"Novembre",
|
||||
"D\u00e9cembre"
|
||||
],
|
||||
"WEEKENDRANGE": [
|
||||
5,
|
||||
6
|
||||
],
|
||||
"fullDate": "EEEE d MMMM y",
|
||||
"longDate": "d MMMM y",
|
||||
"medium": "d MMM y HH:mm:ss",
|
||||
"mediumDate": "d MMM y",
|
||||
"mediumTime": "HH:mm:ss",
|
||||
"short": "dd/MM/y HH:mm",
|
||||
"shortDate": "dd/MM/y",
|
||||
"shortTime": "HH:mm"
|
||||
},
|
||||
"NUMBER_FORMATS": {
|
||||
"CURRENCY_SYM": "\u20ac",
|
||||
"DECIMAL_SEP": ",",
|
||||
"GROUP_SEP": "\u00a0",
|
||||
"PATTERNS": [
|
||||
{
|
||||
"gSize": 3,
|
||||
"lgSize": 3,
|
||||
"maxFrac": 3,
|
||||
"minFrac": 0,
|
||||
"minInt": 1,
|
||||
"negPre": "-",
|
||||
"negSuf": "",
|
||||
"posPre": "",
|
||||
"posSuf": ""
|
||||
},
|
||||
{
|
||||
"gSize": 3,
|
||||
"lgSize": 3,
|
||||
"maxFrac": 2,
|
||||
"minFrac": 2,
|
||||
"minInt": 1,
|
||||
"negPre": "-",
|
||||
"negSuf": "\u00a0\u00a4",
|
||||
"posPre": "",
|
||||
"posSuf": "\u00a0\u00a4"
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": "fr-fr",
|
||||
"pluralCat": function(n, opt_precision) { var i = n | 0; if (i == 0 || i == 1) { return PLURAL_CATEGORY.ONE; } return PLURAL_CATEGORY.OTHER;}
|
||||
});
|
||||
}]);
|
|
@ -199,6 +199,7 @@
|
|||
<script src="/js/angular-animate.min.js"></script>
|
||||
<script src="/js/angular-route.min.js"></script>
|
||||
<script src="/js/angular-sanitize.min.js"></script>
|
||||
<script src="/js/i18n/angular-locale_fr-fr.js"></script>>
|
||||
<script src="/js/public.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -11,19 +11,7 @@
|
|||
Passez voir l'équipe serveur pour corriger cela.
|
||||
</p>
|
||||
<p>
|
||||
Compromissions, défauts de configuration, utilisations malveillantes,
|
||||
contournements des règles de sécurité, … tous les jours nous mettons
|
||||
en danger nos données.
|
||||
</p>
|
||||
<p>
|
||||
Saurez-vous identifier les différents vecteurs de fuites de données avec
|
||||
lesquels nos systèmes d'informations et nos utilisateurs font faces ?
|
||||
</p>
|
||||
<p>
|
||||
<strong>Attention :</strong> puisqu'il s'agit de captures effectuées dans
|
||||
le but de découvrir si des actes malveillants ont été commis sur différents
|
||||
systèmes d'information, les contenus qui sont
|
||||
téléchargeables <em>peuvent</em> contenir du contenu malveillant !
|
||||
Le sujet du rush est disponible <a href="2017-RUSH-SRS-DEFNET-FIC.pdf">ici</a>.
|
||||
</p>
|
||||
<p>
|
||||
Bon courage !
|
||||
|
@ -74,10 +62,3 @@
|
|||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-controller="RankController" ng-show="!(my.team_id)">
|
||||
<ng-include src="'views/rank.html'">
|
||||
</div>
|
||||
|
||||
<!-- Avoid title rewrite... -->
|
||||
<div ng-controller="HomeController"></div>
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
<div class="panel-title">Soumettre une solution</div>
|
||||
</div>
|
||||
<ul class="list-group" ng-show="(my.exercices[current_exercice].solved_number || my.exercices[current_exercice].submitted || sberr)">
|
||||
<li class="list-group-item text-warning" ng-show="my.exercices[current_exercice].solved_number">{{ my.exercices[current_exercice].solved_number }} tentative(s) effectuée(s). Dernière solution envoyée à {{ my.exercices[current_exercice].solved_time | date:"fullDate" }}.</li>
|
||||
<li class="list-group-item text-warning" ng-show="my.exercices[current_exercice].solved_number">{{ my.exercices[current_exercice].solved_number }} tentative(s) effectuée(s). Dernière solution envoyée le {{ my.exercices[current_exercice].solved_time | date:"mediumDate" }} à {{ my.exercices[current_exercice].solved_time | date:"mediumTime" }}.</li>
|
||||
<li class="list-group-item" ng-class="messageClass" ng-show="my.exercices[current_exercice].submitted || sberr"><strong ng-show="!sberr">Votre solution a bien été envoyée !</strong><strong ng-show="sberr">{{ sberr }}</strong> {{ message }}</li>
|
||||
</ul>
|
||||
<div class="panel-body" ng-show="!my.exercices[current_exercice].submitted || sberr">
|
||||
|
@ -65,13 +65,13 @@
|
|||
<div class="panel-title">Challenge réussi !</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
Vous êtes la {{ my.exercices[current_exercice].solved_number }}<sup>e</sup> équipe à avoir résolu ce challenge à {{ my.exercices[current_exercice].solved_time | date:"fullDate" }}. Vous avez marqué {{ themes[current_theme].exercices[current_exercice].gain }} points !
|
||||
Vous êtes la {{ my.exercices[current_exercice].solved_number }}<sup>e</sup> équipe à avoir résolu ce challenge le {{ my.exercices[current_exercice].solved_time | date:"mediumDate" }} à {{ my.exercices[current_exercice].solved_time | date:"mediumTime" }}. Vous avez marqué {{ themes[current_theme].exercices[current_exercice].gain }} points !
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-success" ng-show="(!my.team_id && my.exercices[current_exercice].keys)">
|
||||
<div class="panel-heading">
|
||||
<div class="panel-title">Clefs du challenge</div>
|
||||
<div class="panel-title">Solution du challenge</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<p>
|
||||
|
@ -81,5 +81,8 @@
|
|||
<dt>{{ key.slice(128) }}</dt>
|
||||
<dd class="samp"><code>{{ key.slice(0, 128) }}</code></dd>
|
||||
</dl>
|
||||
<iframe type="text/html" ng-show="my.exercices[current_exercice].video_uri" ng-src="{{ my.exercices[current_exercice].video_uri }}" frameborder="0" style="width: 100%; height: 35vw">
|
||||
Regardez la vidéo de résolution de cet exercice : <a ng-href="{{ my.exercices[current_exercice].video_uri }}">{{ my.exercices[current_exercice].video_uri }}</a>.
|
||||
</iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -47,16 +47,7 @@
|
|||
Vous n'êtes pas encore connecté en tant qu'équipe sur notre serveur.
|
||||
</p>
|
||||
<p>
|
||||
Après avoir suivi le guide présent sur la clef USB que nous vous
|
||||
avons remis et ajouté votre certificat dans votre système ou votre
|
||||
navigateur, ce dernier devrait vous afficher une boîte de dialogue
|
||||
vous demandant de choisir parmi les certificats installés sur votre
|
||||
machine.
|
||||
</p>
|
||||
<p>
|
||||
Si vous avez accédé à cette page avant d'avoir ajouté le certificat,
|
||||
il peut être nécessaire de relancer votre navigateur, afin qu'il
|
||||
démarre une nouvelle session.
|
||||
Le sujet est disponible <a href="/RUSH-SRS-DEFNET-FIC.pdf">ici</a> !
|
||||
</p>
|
||||
<p>
|
||||
Si malgré tout, vous n'arrivez pas à accéder à l'espace de votre
|
||||
|
|
|
@ -3,6 +3,7 @@ package main
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
@ -19,6 +20,8 @@ type timeObject struct {
|
|||
}
|
||||
|
||||
func (t TimeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("Handling %s request from %s: %s [%s]\n", r.Method, r.RemoteAddr, r.URL.Path, r.UserAgent())
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
if j, err := json.Marshal(timeObject{t.StartTime.Unix(), time.Now().Unix(), int(t.Duration.Seconds())}); err != nil {
|
||||
|
|
162
libfic/team.go
162
libfic/team.go
|
@ -1,6 +1,7 @@
|
|||
package fic
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
@ -93,7 +94,7 @@ func (t Team) GetPoints() (int64, error) {
|
|||
}
|
||||
|
||||
func GetRank() (map[int64]int, error) {
|
||||
if rows, err := DBQuery("SELECT id_team, SUM(E.gain) AS score, MAX(S.time) FROM exercice_solved S INNER JOIN exercices E ON E.id_exercice = S.id_exercice GROUP BY id_team HAVING score > 0 ORDER BY score DESC, time ASC"); err != nil {
|
||||
if rows, err := DBQuery("SELECT id_team, SUM(E.gain) AS score, MAX(S.time) FROM exercice_solved S INNER JOIN exercices E ON E.id_exercice = S.id_exercice GROUP BY id_team ORDER BY score DESC, time ASC"); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer rows.Close()
|
||||
|
@ -119,7 +120,7 @@ func GetRank() (map[int64]int, error) {
|
|||
}
|
||||
|
||||
func GetTryRank() ([]int64, error) {
|
||||
if rows, err := DBQuery("SELECT id_team, COUNT(*) AS score FROM exercice_tries GROUP BY id_team HAVING score > 0 ORDER BY score DESC"); err != nil {
|
||||
if rows, err := DBQuery("SELECT id_team, COUNT(*) AS score FROM exercice_tries GROUP BY id_team ORDER BY score DESC"); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer rows.Close()
|
||||
|
@ -152,6 +153,61 @@ func (t Team) HasAccess(e Exercice) bool {
|
|||
}
|
||||
}
|
||||
|
||||
func NbTry(t *Team, e Exercice) int {
|
||||
var cnt *int
|
||||
|
||||
if t != nil {
|
||||
DBQueryRow("SELECT COUNT(*) FROM exercice_tries WHERE id_team = ? AND id_exercice = ?", t.Id, e.Id).Scan(&cnt)
|
||||
} else {
|
||||
DBQueryRow("SELECT COUNT(*) FROM exercice_tries WHERE id_exercice = ?", e.Id).Scan(&cnt)
|
||||
}
|
||||
|
||||
if cnt == nil {
|
||||
return 0
|
||||
} else {
|
||||
return *cnt
|
||||
}
|
||||
}
|
||||
|
||||
func GetTries(t *Team, e *Exercice) ([]time.Time, error) {
|
||||
var rows *sql.Rows
|
||||
var err error
|
||||
|
||||
if t == nil {
|
||||
if e == nil {
|
||||
rows, err = DBQuery("SELECT time FROM exercice_tries ORDER BY time ASC")
|
||||
} else {
|
||||
rows, err = DBQuery("SELECT time FROM exercice_tries WHERE id_exercice = ? ORDER BY time ASC", e.Id)
|
||||
}
|
||||
} else {
|
||||
if e == nil {
|
||||
rows, err = DBQuery("SELECT time FROM exercice_tries WHERE id_team = ? ORDER BY time ASC", t.Id)
|
||||
} else {
|
||||
rows, err = DBQuery("SELECT time FROM exercice_tries WHERE id_team = ? AND id_exercice = ? ORDER BY time ASC", t.Id, e.Id)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer rows.Close()
|
||||
|
||||
times := make([]time.Time, 0)
|
||||
for rows.Next() {
|
||||
var tm time.Time
|
||||
if err := rows.Scan(&tm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
times = append(times, tm)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return times, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t Team) HasSolved(e Exercice) (bool, time.Time, int64) {
|
||||
var nb *int64
|
||||
var tm *time.Time
|
||||
|
@ -170,6 +226,108 @@ func (t Team) HasSolved(e Exercice) (bool, time.Time, int64) {
|
|||
}
|
||||
}
|
||||
|
||||
func IsSolved(e Exercice) (int, time.Time) {
|
||||
var nb *int
|
||||
var tm *time.Time
|
||||
if DBQueryRow("SELECT COUNT(id_exercice), MIN(time) FROM exercice_solved WHERE id_exercice = ?", e.Id).Scan(&nb, &tm); nb == nil || tm == nil {
|
||||
return 0, time.Time{}
|
||||
} else {
|
||||
return *nb, *tm
|
||||
}
|
||||
}
|
||||
|
||||
type statLine struct {
|
||||
Tip string `json:"tip"`
|
||||
Total int `json:"total"`
|
||||
Solved int `json:"solved"`
|
||||
Tried int `json:"tried"`
|
||||
Tries int `json:"tries"`
|
||||
}
|
||||
|
||||
type teamStats struct {
|
||||
Levels []statLine `json:"levels"`
|
||||
Themes []statLine `json:"themes"`
|
||||
}
|
||||
|
||||
func (s *teamStats) GetLevel(level int) *statLine {
|
||||
level -= 1
|
||||
|
||||
for len(s.Levels) <= level {
|
||||
s.Levels = append(s.Levels, statLine{
|
||||
fmt.Sprintf("Level %d", (len(s.Levels) + 1)),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
})
|
||||
}
|
||||
|
||||
return &s.Levels[level]
|
||||
}
|
||||
|
||||
func (t Team) GetStats() (interface{}, error) {
|
||||
return GetTeamsStats(&t)
|
||||
}
|
||||
|
||||
func GetTeamsStats(t *Team) (interface{}, error) {
|
||||
stat := teamStats{}
|
||||
|
||||
if themes, err := GetThemes(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
for _, theme := range themes {
|
||||
total := 0
|
||||
solved := 0
|
||||
tried := 0
|
||||
tries := 0
|
||||
|
||||
if exercices, err := theme.GetExercices(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
for _, exercice := range exercices {
|
||||
var lvl int
|
||||
if lvl, err = exercice.GetLevel(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sLvl := stat.GetLevel(lvl)
|
||||
|
||||
total += 1
|
||||
sLvl.Total += 1
|
||||
|
||||
if t != nil {
|
||||
if b, _, _ := t.HasSolved(exercice); b {
|
||||
solved += 1
|
||||
sLvl.Solved += 1
|
||||
}
|
||||
} else {
|
||||
if n, _ := IsSolved(exercice); n > 0 {
|
||||
solved += 1
|
||||
sLvl.Solved += 1
|
||||
}
|
||||
}
|
||||
|
||||
try := NbTry(t, exercice)
|
||||
if try > 0 {
|
||||
tried += 1
|
||||
tries += try
|
||||
sLvl.Tried += 1
|
||||
sLvl.Tries += try
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stat.Themes = append(stat.Themes, statLine{
|
||||
theme.Name,
|
||||
total,
|
||||
solved,
|
||||
tried,
|
||||
tries,
|
||||
})
|
||||
}
|
||||
|
||||
return stat, nil
|
||||
}
|
||||
}
|
||||
|
||||
type exportedTeam struct {
|
||||
Name string `json:"name"`
|
||||
|
|
Loading…
Reference in New Issue