frontend: add players possibility to report problems with exercices
This commit is contained in:
parent
32dc9c1a8c
commit
9186bbc229
|
@ -179,6 +179,7 @@ func main() {
|
|||
AllowRegistration: false,
|
||||
CanJoinTeam: false,
|
||||
DenyNameChange: false,
|
||||
AcceptNewIssue: true,
|
||||
EnableResolutionRoute: false,
|
||||
PartialValidation: true,
|
||||
UnlockedChallengeDepth: 0,
|
||||
|
|
|
@ -128,6 +128,13 @@
|
|||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<label class="custom-control custom-checkbox">
|
||||
<input class="custom-control-input" type="checkbox" ng-model="config.acceptNewIssue">
|
||||
<span class="custom-control-label">Activer le rapport d'anomalies par les équipes</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<label class="custom-control custom-checkbox">
|
||||
<input class="custom-control-input" type="checkbox" ng-model="config.enableResolutionRoute">
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
)
|
||||
|
||||
type IssueUpload struct {
|
||||
IdExercice int64 `json:"id_exercice"`
|
||||
Subject string `json:"subject"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
func treatIssue(pathname string, team fic.Team) {
|
||||
// Generate a unique identifier to follow the request in logs
|
||||
bid := make([]byte, 5)
|
||||
binary.LittleEndian.PutUint32(bid, rand.Uint32())
|
||||
id := "[" + base64.StdEncoding.EncodeToString(bid) + "]"
|
||||
log.Println(id, "New issue receive", pathname)
|
||||
|
||||
var issue IssueUpload
|
||||
|
||||
if cnt_raw, err := ioutil.ReadFile(pathname); err != nil {
|
||||
log.Printf("%s [ERR] %s\n", id, err)
|
||||
} else if err := json.Unmarshal(cnt_raw, &issue); err != nil {
|
||||
log.Printf("%s [ERR] %s\n", id, err)
|
||||
} else if len(issue.Subject) == 0 && len(issue.Description) == 0 {
|
||||
log.Printf("%s Empty issue: not treated.\n", id)
|
||||
} else if len(issue.Subject) == 0 {
|
||||
log.Printf("%s Issue with no subject: not treated.\n", id)
|
||||
} else {
|
||||
var exercice *fic.Exercice = nil
|
||||
if e, err := fic.GetExercice(issue.IdExercice); err == nil {
|
||||
exercice = &e
|
||||
}
|
||||
|
||||
if claim, err := fic.NewClaim(issue.Subject, &team, exercice, nil, "medium"); err != nil {
|
||||
log.Printf("%s [ERR] Unable to create new issue: %s\n", id, err)
|
||||
} else if len(issue.Description) > 0 {
|
||||
if _, err := claim.AddDescription(issue.Description, fic.ClaimAssignee{Id: 0}); err != nil {
|
||||
log.Printf("%s [WRN] Unable to add description to issue: %s\n", id, err)
|
||||
} else {
|
||||
log.Printf("%s [OOK] New issue created: id=%d\n", id, claim.Id)
|
||||
if err = os.Remove(pathname); err != nil {
|
||||
log.Printf("%s [ERR] %s\n", id, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -186,6 +186,8 @@ func treat(raw_path string) {
|
|||
switch spath[2] {
|
||||
case "name":
|
||||
treatRename(raw_path, team)
|
||||
case "issue":
|
||||
treatIssue(raw_path, team)
|
||||
case "hint":
|
||||
treatOpeningHint(raw_path, team)
|
||||
case "choices":
|
||||
|
|
|
@ -86,6 +86,11 @@ server {
|
|||
|
||||
rewrite ^/.*$ /index.html;
|
||||
}
|
||||
location /issue {
|
||||
include fic-auth.conf;
|
||||
|
||||
rewrite ^/.*$ /index.html;
|
||||
}
|
||||
location /rank {
|
||||
include fic-auth.conf;
|
||||
|
||||
|
@ -167,6 +172,15 @@ server {
|
|||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_redirect off;
|
||||
}
|
||||
location /submit/issue {
|
||||
include fic-auth.conf;
|
||||
|
||||
rewrite ^/submit/.*$ /issue/$team break;
|
||||
|
||||
proxy_pass http://frontend:8080/;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_redirect off;
|
||||
}
|
||||
location /submit/name {
|
||||
include fic-auth.conf;
|
||||
|
||||
|
|
|
@ -78,6 +78,11 @@ server {
|
|||
|
||||
rewrite ^/.*$ /index.html;
|
||||
}
|
||||
location /issue {
|
||||
include fic-auth.conf;
|
||||
|
||||
rewrite ^/.*$ /index.html;
|
||||
}
|
||||
location /rank {
|
||||
include fic-auth.conf;
|
||||
|
||||
|
@ -159,6 +164,15 @@ server {
|
|||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_redirect off;
|
||||
}
|
||||
location /submit/issue {
|
||||
include fic-auth.conf;
|
||||
|
||||
rewrite ^/submit/.*$ /issue/$team break;
|
||||
|
||||
proxy_pass http://frontend:8080/;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_redirect off;
|
||||
}
|
||||
location /submit/name {
|
||||
include fic-auth.conf;
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"path"
|
||||
)
|
||||
|
||||
var acceptNewIssues bool = true
|
||||
|
||||
func IssueHandler(w http.ResponseWriter, r *http.Request, team string, sURL []string) {
|
||||
if !acceptNewIssues {
|
||||
log.Printf("UNHANDELED %s issue request from %s: %s [%s]\n", r.Method, r.RemoteAddr, r.URL.Path, r.UserAgent())
|
||||
http.Error(w, "{\"errmsg\":\"Il n'est pas possible de rapporter d'anomalie.\"}", http.StatusForbidden)
|
||||
} else if len(sURL) != 0 {
|
||||
http.Error(w, "{\"errmsg\":\"Arguments manquants.\"}", http.StatusBadRequest)
|
||||
} else if saveTeamFile(path.Join(team, "issue"), w, r) {
|
||||
// File enqueued for backend treatment
|
||||
http.Error(w, "{\"errmsg\":\"Anomalie signalée avec succès. Merci de votre patience...\"}", http.StatusAccepted)
|
||||
}
|
||||
}
|
|
@ -47,6 +47,7 @@ func main() {
|
|||
|
||||
// Register handlers
|
||||
http.Handle(fmt.Sprintf("%s/chname/", *prefix), http.StripPrefix(fmt.Sprintf("%s/chname/", *prefix), submissionTeamChecker{"name change", ChNameHandler, *teamsDir}))
|
||||
http.Handle(fmt.Sprintf("%s/issue/", *prefix), http.StripPrefix(fmt.Sprintf("%s/issue/", *prefix), submissionTeamChecker{"issue", IssueHandler, *teamsDir}))
|
||||
http.Handle(fmt.Sprintf("%s/openhint/", *prefix), http.StripPrefix(fmt.Sprintf("%s/openhint/", *prefix), submissionTeamChecker{"opening hint", HintHandler, *teamsDir}))
|
||||
http.Handle(fmt.Sprintf("%s/wantchoices/", *prefix), http.StripPrefix(fmt.Sprintf("%s/wantchoices/", *prefix), submissionTeamChecker{"wantint choices", WantChoicesHandler, *teamsDir}))
|
||||
http.Handle(fmt.Sprintf("%s/registration/", *prefix), http.StripPrefix(fmt.Sprintf("%s/registration/", *prefix), submissionChecker{"registration", RegistrationHandler}))
|
||||
|
|
|
@ -57,6 +57,14 @@ angular.module("FICApp", ["ngRoute", "ngSanitize"])
|
|||
controller: "MyTeamController",
|
||||
templateUrl: "views/team-edit.html"
|
||||
})
|
||||
.when("/issue", {
|
||||
controller: "IssueController",
|
||||
templateUrl: "views/issue.html"
|
||||
})
|
||||
.when("/issue/:eid", {
|
||||
controller: "IssueController",
|
||||
templateUrl: "views/issue.html"
|
||||
})
|
||||
.when("/rank", {
|
||||
controller: "RankController",
|
||||
templateUrl: "views/rank.html"
|
||||
|
@ -636,6 +644,42 @@ angular.module("FICApp", ["ngRoute", "ngSanitize"])
|
|||
});
|
||||
}
|
||||
})
|
||||
.controller("IssueController", function($scope, $http, $rootScope, $routeParams) {
|
||||
$rootScope.current_tag = undefined;
|
||||
$rootScope.current_exercice = $routeParams.eid;
|
||||
|
||||
$scope.issue = {
|
||||
id_exercice: parseInt($routeParams.eid, 10),
|
||||
subject: "",
|
||||
description: "",
|
||||
}
|
||||
|
||||
$scope.isubmit = function() {
|
||||
$rootScope.sberr = "";
|
||||
if ($scope.issue.subject.length < 3) {
|
||||
$rootScope.messageClass = {"text-danger": true};
|
||||
$rootScope.sberr = "L'object de votre rapport d'anomalie est trop court !";
|
||||
return false;
|
||||
}
|
||||
|
||||
$http({
|
||||
url: "submit/issue",
|
||||
method: "POST",
|
||||
data: $scope.issue
|
||||
}).then(function(response) {
|
||||
$rootScope.messageClass = {"text-success": true};
|
||||
$rootScope.message = response.data.errmsg;
|
||||
$scope.issue.subject = "";
|
||||
$scope.issue.description = "";
|
||||
}, function(response) {
|
||||
$rootScope.messageClass = {"text-danger": true};
|
||||
$rootScope.message = response.data.errmsg;
|
||||
if (response.status != 402) {
|
||||
$rootScope.sberr = "Une erreur est survenue lors de l'envoi. Veuillez réessayer dans quelques instants.";
|
||||
}
|
||||
});
|
||||
};
|
||||
})
|
||||
.controller("MyTeamController", function($scope, $http, $rootScope, $timeout, $location) {
|
||||
$rootScope.current_theme = 0;
|
||||
$rootScope.current_exercice = 0;
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
<p class="text-justify" ng-bind-html="themes[current_theme].intro"></p>
|
||||
</div>
|
||||
<div class="jumbotron niceborder text-indent mt-3" ng-if="(my.exercices[current_exercice])">
|
||||
<a ng-if="settings.acceptNewIssue" class="float-right btn btn-sm btn-warning" ng-href="issue/{{ current_exercice }}"><span class="glyphicon glyphicon-warning-sign" aria-hidden="true"></span> Rapporter une anomalie sur cet exercice</a>
|
||||
<h3 class="display-4">{{ themes[current_theme].exercices[current_exercice].title }}</h3>
|
||||
<a ng-href="tags/{{tag}}" class="badge badge-pill badge-secondary mr-2 mb-2" ng-repeat="tag in themes[current_theme].exercices[current_exercice].tags">#{{ tag }}</a>
|
||||
<p class="lead text-justify" ng-bind-html="my.exercices[current_exercice].statement"></p>
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
<p class="lead text-justify" ng-bind-html="my.exercices[current_exercice].statement"></p>
|
||||
<div class="alert alert-{{my.exercices[current_exercice].issuekind}}" ng-if="my.exercices[current_exercice].issue" ng-bind-html="my.exercices[current_exercice].issue"></div>
|
||||
<hr class="my-3">
|
||||
<a ng-if="settings.acceptNewIssue" class="float-right btn btn-sm btn-warning" ng-href="issue/{{ current_exercice }}"><span class="glyphicon glyphicon-warning-sign" aria-hidden="true"></span> Rapporter une anomalie sur cet exercice</a>
|
||||
<ul>
|
||||
<li><strong>Gain :</strong> <ng-pluralize count="themes[current_theme].exercices[current_exercice].gain" when="{'one': '{} point', 'other': '{} points'}"></ng-pluralize> <em ng-if="settings.firstBlood && themes[current_theme].exercices[current_exercice].solved < 1">{{ 1 + settings.firstBlood | coeff }} prem's</em> <em ng-if="themes[current_theme].exercices[current_exercice].curcoeff != 1.0 || settings.exerciceCurrentCoefficient != 1.0">{{ themes[current_theme].exercices[current_exercice].curcoeff * settings.exerciceCurrentCoefficient | coeff }} bonus</em></li>
|
||||
<li ng-if="themes[current_theme].exercices[current_exercice].tried"><strong>Tenté par :</strong> <ng-pluralize count="themes[current_theme].exercices[current_exercice].tried" when="{'0': 'aucune équipe', 'one': '{} équipe', 'other': '{} équipes'}"></ng-pluralize> <span ng-if="my.exercices[current_exercice].total_tries">(cumulant <ng-pluralize count="my.exercices[current_exercice].total_tries" when="{'one': '{} tentative', 'other': '{} tentatives'}"></ng-pluralize>)</span></li>
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
<div class="card border-warning mt-3" ng-if="!settings.acceptNewIssue">
|
||||
<div class="card-header bg-warning text-light">Rapporter une anomalie sur un exercice</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text">Rapprochez-vous d'un membre de l'équipe serveur afin d'obtenir de l'aide.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card border-warning mt-3 mb-4" ng-if="settings.acceptNewIssue">
|
||||
<div class="card-header bg-warning text-light">Rapporter une anomalie <span ng-if="issue.id_exercice">sur un exercice</span></div>
|
||||
<div class="card-body">
|
||||
<p ng-class="messageClass" ng-if="message || sberr"><strong ng-if="!sberr">Votre rapport a bien été envoyé !</strong><strong ng-if="sberr">{{ sberr }}</strong> {{ message }}</p>
|
||||
<form ng-submit="isubmit()">
|
||||
|
||||
<div class="form-group row" ng-if="issue.id_exercice">
|
||||
<label for="idExercice" class="col-sm-2 col-form-label">Exercice</label>
|
||||
<div class="col-sm-10" ng-if="current_theme">
|
||||
<input type="text" readonly class="form-control-plaintext" id="idExercice" value="{{ themes[current_theme].exercices[issue.id_exercice].title }}">
|
||||
</div>
|
||||
<div class="col-sm-10" ng-if="!current_theme">
|
||||
<input type="text" readonly class="form-control-plaintext" id="idExercice" value="{{ issue.id_exercice }}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="subject" class="col col-form-label">Objet</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" id="subject" ng-model="issue.subject" placeholder="Intitulé succinct">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="description" class="col col-form-label">Description</label>
|
||||
<div class="col-sm-10">
|
||||
<textarea class="form-control" id="description" ng-model="issue.description" placeholder="Décrivez en détail votre problème ici. Si nécessaire, incluez un lien vers une capture d'écran montrant votre problème."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="float-right btn btn-warning">Envoyer le rapport</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
|
@ -55,6 +55,8 @@ type FICSettings struct {
|
|||
CanJoinTeam bool `json:"canJoinTeam"`
|
||||
// DenyNameChange disallow Team to change their name.
|
||||
DenyNameChange bool `json:"denyNameChange"`
|
||||
// AcceptNewIssue enables the reporting system.
|
||||
AcceptNewIssue bool `json:"acceptNewIssue"`
|
||||
// EnableResolutionRoute activates the route displaying resolution movies.
|
||||
EnableResolutionRoute bool `json:"enableResolutionRoute"`
|
||||
// PartialValidation validates each correct given answers, don't expect Team to give all correct answer in a try.
|
||||
|
|
Loading…
Reference in New Issue