diff --git a/html/leaderboard.html b/html/leaderboard.html index cb18d17c4dd7b9463534dfbb7a1408ba45a03c7e..1a50e3666e47803e404c317566253d4ee0f5f748 100644 --- a/html/leaderboard.html +++ b/html/leaderboard.html @@ -1,89 +1,15 @@ {{ template "header" . }} -<main class="flexcell gridcontainer-leader"> - <div id="table" class="col-0 row-0" style="width: 20%;"> +<main class="leaderboard-main"> + <div id="table" class="flexcell"> <table> <tbody> - <tr> - <th>Name</th> - <th>Points</th> - </tr> - {{ range .AllUsers }} - <tr> - <td>{{ .DisplayName }}</td> - <td>{{ .Points }}</td> - </tr> - {{ end }} </tbody> </table> </div> - <div class="col-1 row-0"> + <div style="min-width: 0; flex-basis: 80%; margin-right: 24px;" class="flexcell"> <canvas id="graph"></canvas> </div> <script src="static/Chart.bundle.min.js" type="text/javascript"></script> - <script> - var score = {name: [{{ range .AllUsers }}{{ .DisplayName }},{{end}}], points: [ { label: "Points", data: [{{ range .AllUsers }}{{ .Points }},{{end}}]}] }; - var chart; - - (function(){ - var c = document.getElementById("graph").getContext('2d'); - c.height = 800; - - - chart = new Chart(c, { - type: 'horizontalBar', - data: { - labels: score.name, - datasets: score.points - }, - options: { - scales: { - xAxes: [{ - ticks: { - min: 0, - stepSize: 1 - } - }] - }, - legend: { - display: false, - }, - mantainAspectRatio: false, - responsive: true, - }, - }); - - ws = new WebSocket("ws://"+window.location.host+"/ws"); -ws.onopen = function() { - - // Web Socket is connected, send data uting send() - console.log("ws connected"); -}; -ws.onclose = function() { - alert("WS Disconnected, reload the page") -}; - -ws.onmessage = (evt)=>{ - var rec = evt.data; - console.log(evt.data); - score = JSON.parse(rec); - chart.data = { - labels: score.name, - datasets: [{data: score.points, label: "Solved Challenges" }] - }; - chart.update(); - table = document.getElementsByTagName("tbody")[0]; - table.innerHTML = "<tr><th>Name</th><th>Points</th></tr>"; - for(i=0; i < score.name.length; i++){ - row = table.insertRow(); - namecell = row.insertCell(); - namecell.innerHTML = score.name[i]; - pointcell = row.insertCell(); - pointcell.innerHTML = score.points[i]; - - } - -}; - })(); - </script> + <script src="static/leaderboard.js" type="text/javascript"></script> </main> {{ template "footer" . }} diff --git a/html/static/leaderboard.js b/html/static/leaderboard.js new file mode 100644 index 0000000000000000000000000000000000000000..b2ff9f6ae16f7ef85ebca0b66213d5ba12307b96 --- /dev/null +++ b/html/static/leaderboard.js @@ -0,0 +1,73 @@ +var chart; +(function() { + var c = document.getElementById("graph").getContext("2d"); + + chart = new Chart(c, { + type: "line", + options: { + scales: { + xAxes: [ + { + type: "time", + time: { + unit: "minute", + max: Date.now() + } + } + ], + yAxes: [ + { + ticks: { + beginAtZero: true + } + } + ] + }, + elements: { + line: { + tension: 0, + fill: false + } + }, + legend: { + display: false + }, + mantainAspectRatio: false, + responsive: true, + showScale: false + } + }); + + ws = new WebSocket("ws://" + window.location.host + "/ws"); + ws.onopen = function() { + // Web Socket is connected, send data uting send() + console.log("ws connected"); + }; + ws.onclose = function() { + alert("WS Disconnected, reload the page"); + }; + + ws.onmessage = evt => { + var rec = evt.data; + console.log(evt.data); + score = JSON.parse(rec); + + chart.data = { + datasets: score.chart + }; + chart.update(); + + table = document.getElementsByTagName("tbody")[0]; + table.innerHTML = "<tr><th>Name</th><th>Points</th></tr>"; + for (i = 0; i < score.table.name.length; i++) { + row = table.insertRow(); + namecell = row.insertCell(); + namecell.innerHTML = score.table.name[i]; + pointcell = row.insertCell(); + pointcell.innerHTML = score.table.points[i]; + } + }; + window.addEventListener("resize", function() { + chart.update(); + }); +})(); diff --git a/html/static/style.css b/html/static/style.css index c41a4559bca7a41f74c4756b826080979ddd16b8..4ee89b6a5d8bcdd52a40f66c162eb4c42ae2852b 100644 --- a/html/static/style.css +++ b/html/static/style.css @@ -4,6 +4,20 @@ border-style: solid; }*/ +.leaderboard-table { + flex-grow: 1; + flex-shrink: 1; + flex-basis: 20%; + margin-right: 24px; +} + +.leaderboard-main { + margin-top: 96px; + justify-content: space-between; + align-items: strech; + display: flex; +} + .button { border: none; color: white; @@ -210,7 +224,7 @@ img { .gridcontainer-leader { display: grid; - width: auto; + width: 100%; height: 100%; } diff --git a/internal/liveScoreboard.go b/internal/liveScoreboard.go index c281441bbb9f3d5b8c7ea245fa7991552c09e9c6..535808d9ac8a2bfc9577138edc3bd516f3c71c30 100644 --- a/internal/liveScoreboard.go +++ b/internal/liveScoreboard.go @@ -2,11 +2,19 @@ package wtfd import ( "encoding/json" + "fmt" "github.com/gorilla/websocket" + "hash/crc32" "log" "net/http" + "time" ) +type tableData struct { + Names []string `json:"name"` + Points []int `json:"points"` +} + var ( serverChan = make(chan chan string, 4) messageChan = make(chan string, 1) @@ -52,6 +60,7 @@ func leaderboardWS(w http.ResponseWriter, r *http.Request) { } client := make(chan string, 1) serverChan <- client // i have no idea what this go magic is + updateScoreboard() for { select { @@ -64,31 +73,72 @@ func leaderboardWS(w http.ResponseWriter, r *http.Request) { } -func updateScoreboard() error { - log.Printf("Scoreboard Update\n") - type userNamePoints struct { - Name []string `json:"name"` - Points []int `json:"points"` - } - var name []string - var points []int +func generateTableData() (tableData, error) { allu, err := ormAllUsersSortedByPoints() if err != nil { - log.Printf("Scoreboard Update Error: %v\n", err) - return err + return tableData{}, err } + var name []string + var points []int + for _, u := range allu { name = append(name, u.DisplayName) points = append(points, u.Points) } + return tableData{Names: name, Points: points}, nil +} + +func updateScoreboard() error { + type chartDataPoint struct { + T string `json:"t"` + // Label string `json:"label"` + Y int `json:"y"` + } + type chartData struct { + Label string `json:"label"` + Data []chartDataPoint `json:"data"` + Color string `json:"backgroundColor"` + Pcolor string `json:"borderColor"` + } + type leaderboardData struct { + TableData tableData `json:"table"` + ChartData []chartData `json:"chart"` + } + + log.Printf("Scoreboard Update\n") + users, err := ormAllUsersSortedByPoints() + datas := make([]chartData, len(users)) + for _, u := range users { + solves := ormGetSolvesWithTime(u.Name) + data := make([]chartDataPoint, len(solves)+1) + sum := 0 + data[0] = chartDataPoint{T: u.Created.Format(time.RFC3339), Y: sum} //, Label: s.ChallengeName} + for i, s := range solves { + chall, err := challs.Find(s.ChallengeName) + if err != nil { + log.Printf("Scoreboard Update Error: %v, %v\n", err, s.ChallengeName) + return err + } + sum += chall.Points + data[i+1] = chartDataPoint{T: s.Created.Format(time.RFC3339), Y: sum} //, Label: s.ChallengeName} + } + a := fmt.Sprintf("#%X", crc32.ChecksumIEEE([]byte(u.DisplayName)))[0:7] + datas = append(datas, chartData{Pcolor: a, Color: a, Label: u.DisplayName, Data: data}) + + } - json, err := json.Marshal(&userNamePoints{Name: name, Points: points}) + td, err := generateTableData() + if err != nil { + log.Printf("Scoreboard Update Error: %v\n", err) + return err + } + ld := leaderboardData{TableData: td, ChartData: datas} + jsona, err := json.Marshal(&ld) if err != nil { log.Printf("Scoreboard Update Error: %v\n", err) return err } - messageChan <- string(json) - log.Printf("Scoreboard Update String: %s\n", string(json)) + messageChan <- string(jsona) return nil } diff --git a/internal/orm.go b/internal/orm.go index 2a3b36bd9fde8b661b908266b4d11013edf12285..c8727d0d424b0066aa840863fa2b1136b4091ffa 100644 --- a/internal/orm.go +++ b/internal/orm.go @@ -3,10 +3,11 @@ package wtfd import ( "errors" "fmt" - "golang.org/x/crypto/bcrypt" "github.com/go-xorm/xorm" _ "github.com/mattn/go-sqlite3" // needed for xorm + "golang.org/x/crypto/bcrypt" "os" + "time" "xorm.io/core" ) @@ -25,13 +26,15 @@ var ( type _ORMUser struct { Name string `xorm:"unique"` DisplayName string `xorm:"unique"` + Created time.Time `xorm:"created" json:"time"` Hash []byte Points int } type _ORMChallengesByUser struct { - UserName string // Foregin keys don't exist - ChallengeName string + UserName string `json:"username"` // Foregin keys don't exist + Created time.Time `xorm:"created" json:"time"` + ChallengeName string `json:"name"` } func (u _ORMUser) TableName() string { @@ -77,6 +80,7 @@ func NewUser(name, password, displayname string) (User, error) { return User{Name: name, Hash: hash, DisplayName: displayname}, nil } + // Contains looks if a username is in the datenbank func Contains(username, displayname string) bool { count, _ := ormUserExists(User{Name: username, DisplayName: displayname}) @@ -146,6 +150,18 @@ func ormNewUser(user User) error { return err } +// ormGetSolveCount returns the number of solves for the Challenge chall +func ormGetSolvesWithTime(u string) []_ORMChallengesByUser { + + var a []_ORMChallengesByUser + if err := engine.Where("UserName = ?", u).Find(&a); err != nil { + fmt.Printf("ORM Error: %v\n", err) + return []_ORMChallengesByUser{} + } + return a + +} + // ormGetSolveCount returns the number of solves for the Challenge chall func ormGetSolveCount(chall Challenge) int64 {