diff --git a/assets/svgload.svg b/assets/svgload.svg new file mode 100644 index 0000000..22ebe15 --- /dev/null +++ b/assets/svgload.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/assets/websocket.js b/assets/websocket.js new file mode 100644 index 0000000..30d26b0 --- /dev/null +++ b/assets/websocket.js @@ -0,0 +1,16 @@ +window.addEventListener("load", function(evt) { + var ws; + var loc = window.location, new_uri; + if (loc.protocol === "https:") { + new_uri = "wss:"; + } else { + new_uri = "ws:"; + } + new_uri += "//" + loc.host + "/ws"; + ws = new WebSocket(new_uri); + ws.onmessage = function(evt) { + // there should be a check for the message + // content, but it is not needed for now + location.reload(); + } +}); diff --git a/cmd/ginvalid/main.go b/cmd/ginvalid/main.go index 2fce12e..b31db66 100644 --- a/cmd/ginvalid/main.go +++ b/cmd/ginvalid/main.go @@ -46,6 +46,7 @@ func registerRoutes(r *mux.Router) { r.HandleFunc("/results/{validator}/{user}/{repo}", web.Results).Methods("GET") r.HandleFunc("/results/{validator}/{user}/{repo}/{id}", web.Results).Methods("GET") r.HandleFunc("/login", web.LoginGet).Methods("GET") + r.HandleFunc("/ws", web.Socket).Methods("GET") r.HandleFunc("/login", web.LoginPost).Methods("POST") r.HandleFunc("/logout", web.Logout).Methods("GET") r.HandleFunc("/repos", web.ListRepos).Methods("GET") diff --git a/go.mod b/go.mod index 0cedd60..9ce8abc 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/google/uuid v1.1.1 github.com/gorilla/handlers v1.4.2 github.com/gorilla/mux v1.7.3 + github.com/gorilla/websocket v1.5.0 // indirect github.com/magiconair/properties v1.8.1 // indirect github.com/mattn/go-colorable v0.1.4 // indirect github.com/mattn/go-isatty v0.0.10 // indirect diff --git a/go.sum b/go.sum index ce3b556..4ac9826 100644 --- a/go.sum +++ b/go.sum @@ -60,6 +60,8 @@ github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/ github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= diff --git a/internal/resources/templates/generic_results.go b/internal/resources/templates/generic_results.go index fefaefd..fe8ccbe 100644 --- a/internal/resources/templates/generic_results.go +++ b/internal/resources/templates/generic_results.go @@ -37,6 +37,11 @@ const GenericResults = ` {{ end }}
+ {{ if not (eq .LoadingSVG "") }} +
+ {{ .LoadingSVG }} +
+ {{ end }}
{{.Content}}
diff --git a/internal/resources/templates/layout.go b/internal/resources/templates/layout.go index 86a0d91..66a128b 100644 --- a/internal/resources/templates/layout.go +++ b/internal/resources/templates/layout.go @@ -15,6 +15,7 @@ var Layout = ` + GIN Valid diff --git a/internal/web/pkg.go b/internal/web/pkg.go index 17daa56..ac1d0b6 100644 --- a/internal/web/pkg.go +++ b/internal/web/pkg.go @@ -7,6 +7,6 @@ package web const ( serveralias = "gin" - progressmsg = "A validation job for this repository is currently in progress, please do not leave this page and refresh the page after a while." + progressmsg = "A validation job for this repository is currently in progress. Please do not leave this page. When it is done, it will refresh automatically. You can also refresh this page manually as many times as you wish" notvalidatedyet = "This repository has not been validated yet. To see the results, update the repository." ) diff --git a/internal/web/results.go b/internal/web/results.go index 34f5b6e..befa0a1 100644 --- a/internal/web/results.go +++ b/internal/web/results.go @@ -241,6 +241,10 @@ func renderInProgress(w http.ResponseWriter, r *http.Request, badge []byte, vali resHistory := resultsHistory(validator, user, repo) loggedUsername := getLoggedUserName(r) year, _, _ := time.Now().Date() + svgload, err := ioutil.ReadFile("/assets/svgload.svg") + if err != nil { + svgload = []byte("") + } info := struct { Badge template.HTML Header string @@ -249,7 +253,8 @@ func renderInProgress(w http.ResponseWriter, r *http.Request, badge []byte, vali CurrentYear int UserName string *ResultsHistoryStruct - }{template.HTML(badge), head, string(progressmsg), srvcfg.GINAddresses.WebURL, year, loggedUsername, &resHistory} + LoadingSVG template.HTML + }{template.HTML(badge), head, string(progressmsg), srvcfg.GINAddresses.WebURL, year, loggedUsername, &resHistory, template.HTML(svgload)} err = tmpl.ExecuteTemplate(w, "layout", info) if err != nil { @@ -373,6 +378,7 @@ func renderNIXResults(w http.ResponseWriter, r *http.Request, badge []byte, cont head := fmt.Sprintf("NIX validation for %s/%s", user, repo) resHistory := resultsHistory("nix", user, repo) year, _, _ := time.Now().Date() + svgload := []byte("") loggedUsername := getLoggedUserName(r) srvcfg := config.Read() info := struct { @@ -383,7 +389,8 @@ func renderNIXResults(w http.ResponseWriter, r *http.Request, badge []byte, cont CurrentYear int UserName string *ResultsHistoryStruct - }{template.HTML(badge), head, string(content), srvcfg.GINAddresses.WebURL, year, loggedUsername, &resHistory} + LoadingSVG template.HTML + }{template.HTML(badge), head, string(content), srvcfg.GINAddresses.WebURL, year, loggedUsername, &resHistory, template.HTML(svgload)} err = tmpl.ExecuteTemplate(w, "layout", info) if err != nil { @@ -414,6 +421,7 @@ func renderODMLResults(w http.ResponseWriter, r *http.Request, badge []byte, con head := fmt.Sprintf("odML validation for %s/%s", user, repo) resHistory := resultsHistory("odml", user, repo) loggedUsername := getLoggedUserName(r) + svgload := []byte("") srvcfg := config.Read() year, _, _ := time.Now().Date() info := struct { @@ -424,7 +432,8 @@ func renderODMLResults(w http.ResponseWriter, r *http.Request, badge []byte, con CurrentYear int UserName string *ResultsHistoryStruct - }{template.HTML(badge), head, string(content), srvcfg.GINAddresses.WebURL, year, loggedUsername, &resHistory} + LoadingSVG template.HTML + }{template.HTML(badge), head, string(content), srvcfg.GINAddresses.WebURL, year, loggedUsername, &resHistory, template.HTML(svgload)} err = tmpl.ExecuteTemplate(w, "layout", info) if err != nil { diff --git a/internal/web/user.go b/internal/web/user.go index f328937..1cb5fc9 100644 --- a/internal/web/user.go +++ b/internal/web/user.go @@ -228,6 +228,10 @@ func Logout(w http.ResponseWriter, r *http.Request) { Expires: time.Time{}, Secure: false, // TODO: Switch when we go live } + if conn, ok := websockets[cookie.Value]; ok { + conn.Close() + delete(websockets, cookie.Value) + } http.SetCookie(w, &cookie) http.Redirect(w, r, "/login", http.StatusFound) } diff --git a/internal/web/validate.go b/internal/web/validate.go index 91aae70..438e1cb 100644 --- a/internal/web/validate.go +++ b/internal/web/validate.go @@ -27,6 +27,7 @@ import ( "github.com/google/uuid" "github.com/gorilla/mux" + "github.com/gorilla/websocket" ) // BidsMessages contains Errors, Warnings and Ignored messages. @@ -56,6 +57,8 @@ type Validationcfg struct { } `yaml:"bidsconfig"` } +var websockets map[string]*websocket.Conn = make(map[string]*websocket.Conn) + // handleValidationConfig unmarshalles a yaml config file // from file and returns the resulting Validationcfg struct. func handleValidationConfig(cfgpath string) (Validationcfg, error) { @@ -341,7 +344,7 @@ func validateODML(valroot, resdir string) error { return nil } -func runValidatorBoth(validator, repopath, commit, commitname string, gcl *ginclient.Client, automatic bool) string { +func runValidatorBoth(validator, repopath, commit, commitname string, gcl *ginclient.Client, automatic bool, r *http.Request) string { respath := filepath.Join(validator, repopath, commit) go func() { log.ShowWrite("[Info] Running %s validation on repository %q (%s)", validator, repopath, commitname) @@ -474,17 +477,19 @@ func runValidatorBoth(validator, repopath, commit, commitname string, gcl *gincl if err != nil { writeValFailure(resdir) } + cookie, _ := r.Cookie(srvcfg.Settings.CookieName) + websockets[cookie.Value].WriteMessage(websocket.TextMessage, []byte("REFRESH")) }() return respath } -func runValidator(validator, repopath, commit string, gcl *ginclient.Client) { +func runValidator(validator, repopath, commit string, gcl *ginclient.Client, r *http.Request) { automatic := true - runValidatorBoth(validator, repopath, commit, commit, gcl, automatic) + runValidatorBoth(validator, repopath, commit, commit, gcl, automatic, r) } -func runValidatorPub(validator, repopath string, gcl *ginclient.Client) string { +func runValidatorPub(validator, repopath string, gcl *ginclient.Client, r *http.Request) string { automatic := false - return runValidatorBoth(validator, repopath, uuid.New().String(), "HEAD", gcl, automatic) + return runValidatorBoth(validator, repopath, uuid.New().String(), "HEAD", gcl, automatic, r) } // writeValFailure writes a badge and page content for when a hook payload is @@ -555,6 +560,26 @@ func renderValidationForm(w http.ResponseWriter, r *http.Request, errMsg string) tmpl.Execute(w, &data) } +// Socket creates the websocket for async communication (refreshing the page) +func Socket(w http.ResponseWriter, r *http.Request) { + srvcfg := config.Read() + cookie, err := r.Cookie(srvcfg.Settings.CookieName) + if err != nil { + fail(w, r, http.StatusUnauthorized, "You are not logged in") + return + } + var upgrader = websocket.Upgrader{} + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + fail(w, r, http.StatusServiceUnavailable, "Cannot open websocket") + return + } + if conn, ok := websockets[cookie.Value]; ok { + conn.Close() + } + websockets[cookie.Value] = conn +} + // PubValidatePost parses the POST data from the root form and calls the // validator using the built-in ServiceWaiter. func PubValidatePost(w http.ResponseWriter, r *http.Request) { @@ -597,7 +622,7 @@ func PubValidatePost(w http.ResponseWriter, r *http.Request) { return } - respath := runValidatorPub(validator, repopath, gcl) + respath := runValidatorPub(validator, repopath, gcl, r) http.Redirect(w, r, filepath.Join("results", respath), http.StatusFound) } @@ -679,7 +704,7 @@ func Validate(w http.ResponseWriter, r *http.Request) { log.ShowWrite("[Info] Got user %s. Checking repo", gcl.Username) // Payload is good. Run validator asynchronously and return OK header - runValidator(validator, repopath, commithash, gcl) + runValidator(validator, repopath, commithash, gcl, r) w.WriteHeader(http.StatusOK) w.Write([]byte("OK"))