2016-10-11 22:18:36 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"encoding/csv"
|
2017-10-27 21:40:33 +00:00
|
|
|
"encoding/json"
|
2016-10-11 22:18:36 +00:00
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"path/filepath"
|
2017-10-27 21:40:33 +00:00
|
|
|
"strings"
|
2016-10-11 22:18:36 +00:00
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
var students [][]string
|
|
|
|
var rendusDir string
|
|
|
|
var title string
|
|
|
|
|
|
|
|
func Serve(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", "text/html")
|
|
|
|
|
|
|
|
w.Write([]byte(`<!DOCTYPE html>
|
|
|
|
<html>
|
|
|
|
<head>
|
|
|
|
<title>` + title + `</title>
|
|
|
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
|
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
|
2017-10-27 21:40:33 +00:00
|
|
|
<style>.table > tbody > tr > th, .table > tbody > tr > td { vertical-align: middle; }
|
|
|
|
td.danger { text-align: center; }
|
|
|
|
</style>
|
|
|
|
<script type="text/javascript">
|
|
|
|
function disp(rendus) {
|
|
|
|
Object.keys(rendus).map(function(login) {
|
|
|
|
student = rendus[login];
|
|
|
|
|
|
|
|
var row = document.createElement("tr");
|
|
|
|
|
|
|
|
var col = document.createElement("th");
|
|
|
|
col.innerHTML = login;
|
|
|
|
row.appendChild(col);
|
|
|
|
|
|
|
|
Object.keys(student).map(function(wn) {
|
|
|
|
work = student[wn];
|
|
|
|
if (work) {
|
|
|
|
var col = document.createElement("td");
|
|
|
|
col.className = "success";
|
|
|
|
col.innerHTML = work["date"] + "<br>" + work["hash"];
|
|
|
|
row.appendChild(col);
|
|
|
|
} else {
|
|
|
|
var col = document.createElement("td");
|
|
|
|
col.className = "danger";
|
|
|
|
col.innerHTML = "<span class=\"glyphicon glyphicon-remove\" aria-hidden=\"true\"></span>";
|
|
|
|
row.appendChild(col);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
document.getElementById("students").appendChild(row);
|
|
|
|
});
|
|
|
|
Object.keys(student).map(function(wn) {
|
|
|
|
var col = document.createElement("th");
|
|
|
|
col.innerHTML = wn;
|
|
|
|
document.getElementById("head").appendChild(col);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
</script>
|
2016-10-11 22:18:36 +00:00
|
|
|
</head>
|
|
|
|
<body class="container">
|
|
|
|
<h1>` + title + `</h1>
|
2017-10-27 21:40:33 +00:00
|
|
|
<table class="table table-striped table-hover table-condensed">
|
|
|
|
<thead>
|
|
|
|
<tr id="head"><th></th></tr>
|
|
|
|
</thead>
|
|
|
|
<tbody id="students">
|
|
|
|
</tbody>
|
|
|
|
</table>
|
2018-10-17 14:27:37 +00:00
|
|
|
<script type="text/javascript">
|
|
|
|
fetch('rendus.json') // You can also fetch login_x.json
|
|
|
|
.then(function(response) {
|
|
|
|
return response.json();
|
|
|
|
})
|
|
|
|
.then(function(submissions) {
|
|
|
|
disp(submissions);
|
|
|
|
});
|
|
|
|
</script>
|
2017-10-27 21:40:33 +00:00
|
|
|
</body>
|
|
|
|
</html>
|
|
|
|
`))
|
|
|
|
}
|
|
|
|
|
|
|
|
func genStudents() map[string]map[string]*Submission {
|
|
|
|
ret := map[string]map[string]*Submission{}
|
|
|
|
|
|
|
|
for _, student := range students {
|
|
|
|
login := student[2]
|
|
|
|
ret[login] = genStudent(login)
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
func ServeJSON(w http.ResponseWriter, r *http.Request) {
|
2018-10-17 14:30:34 +00:00
|
|
|
if addr := r.Header.Get("X-Forwarded-For"); addr != "" {
|
|
|
|
r.RemoteAddr = addr
|
|
|
|
}
|
2017-10-27 21:40:33 +00:00
|
|
|
log.Printf("Handling %s request from %s: %s [%s]\n", r.Method, r.RemoteAddr, r.URL.Path, r.UserAgent())
|
|
|
|
|
2018-10-17 14:27:37 +00:00
|
|
|
w.Header().Set("Content-Type", "application/json")
|
2017-10-27 21:40:33 +00:00
|
|
|
|
|
|
|
if j, err := json.Marshal(genStudents()); err != nil {
|
|
|
|
http.Error(w, fmt.Sprintf("{errmsg:%q}", err), http.StatusInternalServerError)
|
|
|
|
} else {
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
w.Write(j)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type Submission struct {
|
|
|
|
Date time.Time `json:"date"`
|
|
|
|
Hash string `json:"hash"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func genStudent(login string) map[string]*Submission {
|
|
|
|
ret := map[string]*Submission{}
|
|
|
|
|
2016-10-11 22:18:36 +00:00
|
|
|
if dirs, err := ioutil.ReadDir(rendusDir); err == nil {
|
|
|
|
for _, dir := range dirs {
|
|
|
|
if dir.IsDir() {
|
2017-10-27 21:40:33 +00:00
|
|
|
p := path.Join(rendusDir, dir.Name(), login)
|
|
|
|
if fi, err := os.Stat(p); err == nil {
|
|
|
|
ret[dir.Name()] = new(Submission)
|
|
|
|
ret[dir.Name()].Date = fi.ModTime()
|
|
|
|
if lnk, err := os.Readlink(p); err == nil {
|
2019-11-02 15:30:41 +00:00
|
|
|
ret[dir.Name()].Hash = strings.TrimPrefix(lnk, login+".")
|
2016-10-11 22:18:36 +00:00
|
|
|
}
|
2017-10-27 21:40:33 +00:00
|
|
|
} else {
|
|
|
|
ret[dir.Name()] = nil
|
2016-10-11 22:18:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-10-27 21:40:33 +00:00
|
|
|
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
func ServeJSONStudent(w http.ResponseWriter, r *http.Request) {
|
2018-10-17 14:30:34 +00:00
|
|
|
if addr := r.Header.Get("X-Forwarded-For"); addr != "" {
|
|
|
|
r.RemoteAddr = addr
|
|
|
|
}
|
2017-10-27 21:40:33 +00:00
|
|
|
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")
|
|
|
|
|
|
|
|
login := strings.TrimSuffix(strings.TrimPrefix(r.URL.Path, "/"), ".json")
|
|
|
|
|
|
|
|
if j, err := json.Marshal(genStudent(login)); err != nil {
|
|
|
|
http.Error(w, fmt.Sprintf("{errmsg:%q}", err), http.StatusInternalServerError)
|
|
|
|
} else {
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
w.Write(j)
|
|
|
|
}
|
2016-10-11 22:18:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
var studentsFile string
|
|
|
|
|
|
|
|
var bind = flag.String("bind", "0.0.0.0:8081", "Bind port/socket")
|
|
|
|
flag.StringVar(&studentsFile, "students", "./students.csv", "Path to a CSV file containing in the third column the name of the directory to look for")
|
|
|
|
flag.StringVar(&rendusDir, "path", "./rendu/", "Path to the submissions directory (each subdirectory is a project)")
|
|
|
|
flag.StringVar(&title, "title", "Rendus VIRLI", "Title of the page")
|
|
|
|
flag.Parse()
|
|
|
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
if rendusDir, err = filepath.Abs(rendusDir); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
} else if dirs, err := ioutil.ReadDir(rendusDir); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
} else {
|
|
|
|
var projects []string
|
|
|
|
nbProj := 0
|
|
|
|
for _, dir := range dirs {
|
|
|
|
if dir.IsDir() {
|
|
|
|
projects = append(projects, dir.Name())
|
|
|
|
nbProj++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
log.Println(nbProj, "projects found:", projects)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read and parse students list
|
|
|
|
log.Println("Reading students files...")
|
|
|
|
if studentsFile, err = filepath.Abs(studentsFile); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
} else if fi, err := os.Open(studentsFile); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
} else {
|
|
|
|
r := csv.NewReader(bufio.NewReader(fi))
|
|
|
|
if students, err = r.ReadAll(); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
log.Println(len(students), "students loaded.")
|
|
|
|
|
|
|
|
log.Println("Registering handlers...")
|
|
|
|
http.HandleFunc("/", Serve)
|
2017-10-27 21:40:33 +00:00
|
|
|
http.HandleFunc("/rendus.json", ServeJSON)
|
|
|
|
for _, student := range students {
|
2019-11-02 15:30:41 +00:00
|
|
|
http.HandleFunc("/"+student[2]+".json", ServeJSONStudent)
|
2017-10-27 21:40:33 +00:00
|
|
|
}
|
2016-10-11 22:18:36 +00:00
|
|
|
log.Println(fmt.Sprintf("Ready, listening on %s", *bind))
|
|
|
|
if err := http.ListenAndServe(*bind, nil); err != nil {
|
|
|
|
log.Fatal("Unable to listen and serve: ", err)
|
|
|
|
}
|
|
|
|
}
|