Display main page with all students and all projects
This commit is contained in:
parent
f1934f14ef
commit
e29990a29a
70
README.md
70
README.md
@ -0,0 +1,70 @@
|
|||||||
|
Shemu
|
||||||
|
=====
|
||||||
|
|
||||||
|
When harvest comes, Shemu tells you if you have correctly submitted your work.
|
||||||
|
|
||||||
|
Shemu is a HTML interface looking for an extracted tarball for each students in
|
||||||
|
each projects.
|
||||||
|
|
||||||
|
|
||||||
|
Compilation
|
||||||
|
-----------
|
||||||
|
|
||||||
|
This program is written in Go. It has no dependancy, you can compile it with
|
||||||
|
the following command:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
go build
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Projects
|
||||||
|
--------
|
||||||
|
|
||||||
|
Shemu takes as argument a path to a directory containing projects.
|
||||||
|
|
||||||
|
The tree exptected looks like:
|
||||||
|
|
||||||
|
rendu/
|
||||||
|
TP1/
|
||||||
|
login_x/
|
||||||
|
...
|
||||||
|
mercie_d/
|
||||||
|
...
|
||||||
|
TP2/
|
||||||
|
login_x/
|
||||||
|
...
|
||||||
|
mercie_d/
|
||||||
|
...
|
||||||
|
|
||||||
|
In the above tree, 2 projects are defined: named `TP1` and `TP2`, from their
|
||||||
|
directory name.
|
||||||
|
|
||||||
|
|
||||||
|
Students
|
||||||
|
--------
|
||||||
|
|
||||||
|
A file with students list is expected, in CSV format.
|
||||||
|
|
||||||
|
A line looks like:
|
||||||
|
|
||||||
|
```csv
|
||||||
|
Mercier,Pierre-Olivier,mercie_d,64170,github@nemunai.re,0123456789
|
||||||
|
```
|
||||||
|
|
||||||
|
The third column is used has student identifier. It is this identifier that is
|
||||||
|
search in each project directory.
|
||||||
|
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
```shell
|
||||||
|
./shemu -students ~/projects/students.csv -path ~/projects/
|
||||||
|
```
|
||||||
|
|
||||||
|
Another convinient way to use it is to launch and forget:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
nohup ./shemu -bind :8079 -title "Submissions title" -students ~/projects/students.csv -path ~/projects/ &
|
||||||
|
```
|
112
main.go
Normal file
112
main.go
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/csv"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"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">
|
||||||
|
</head>
|
||||||
|
<body class="container">
|
||||||
|
<h1>` + title + `</h1>
|
||||||
|
<table class="table table-striped table-hover table-condensed">`))
|
||||||
|
if dirs, err := ioutil.ReadDir(rendusDir); err == nil {
|
||||||
|
w.Write([]byte(`<thead><tr><td></td>`))
|
||||||
|
for _, dir := range dirs {
|
||||||
|
if dir.IsDir() {
|
||||||
|
w.Write([]byte(`<th>` + dir.Name() + `</th>`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Write([]byte(`</tr></thead><tbody>`))
|
||||||
|
for _, student := range students {
|
||||||
|
login := student[2]
|
||||||
|
w.Write([]byte(`<tr><th>` + login + `</th>`))
|
||||||
|
for _, dir := range dirs {
|
||||||
|
if dir.IsDir() {
|
||||||
|
if fi, err := os.Stat(path.Join(rendusDir, dir.Name(), login)); err == nil {
|
||||||
|
w.Write([]byte(`<td class="success">` + fi.ModTime().Format(time.UnixDate) + `</td>`))
|
||||||
|
} else {
|
||||||
|
w.Write([]byte(`<td class="danger"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></td>`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Write([]byte(`</tr>`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Write([]byte(` </tbody></table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`))
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
17
nginx-sample.conf
Normal file
17
nginx-sample.conf
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=STATIC:10m inactive=24h max_size=1g;
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
|
||||||
|
location /
|
||||||
|
{
|
||||||
|
proxy_pass http://localhost:8080;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_cache STATIC;
|
||||||
|
proxy_cache_valid 200 10s;
|
||||||
|
proxy_redirect off;
|
||||||
|
proxy_cache_use_stale error timeout invalid_header updating
|
||||||
|
http_500 http_502 http_503 http_504;
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user