Skip to content

Commit

Permalink
Make livereloading of watched dashboards more reliable
Browse files Browse the repository at this point in the history
Instead of using Grafana's websocket to trigger a dashboard refresh,
let's use a more crude approach and refresh the entire page.

The server-side implementation of the livereload process is
taken from https://github.com/gohugoio/hugo/tree/62567d38205a61134a6822d37a534520772419f1/livereload

It has been adjusted to ensure that only dashboards that changed will be
reloaded.
  • Loading branch information
K-Phoen committed Oct 30, 2024
1 parent 97dc50a commit 18bf2f6
Show file tree
Hide file tree
Showing 10 changed files with 221 additions and 332 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ require (
github.com/go-openapi/runtime v0.28.0
github.com/gobwas/glob v0.2.3
github.com/google/go-jsonnet v0.20.0
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.1
github.com/grafana/grafana-openapi-client-go v0.0.0-20240325012504-4958bdd139e7
github.com/grafana/synthetic-monitoring-agent v0.23.1
Expand Down Expand Up @@ -51,6 +50,7 @@ require (
github.com/go-openapi/validate v0.24.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
Expand Down
66 changes: 66 additions & 0 deletions internal/livereload/connection.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright 2015 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package livereload

import (
"bytes"
"sync"

"github.com/gorilla/websocket"
)

type connection struct {
// The websocket connection.
ws *websocket.Conn

// Buffered channel of outbound messages.
send chan []byte

// There is a potential data race, especially visible with large files.
// This is protected by synchronization of the send channel's close.
closer sync.Once
}

func (c *connection) close() {
c.closer.Do(func() {
close(c.send)
})
}

func (c *connection) reader() {
for {
_, message, err := c.ws.ReadMessage()
if err != nil {
break
}
if bytes.Contains(message, []byte(`"command":"hello"`)) {
c.send <- []byte(`{
"command": "hello",
"protocols": ["http://livereload.com/protocols/official-7"],
"serverName": "Grizzly"
}`)
}
}
c.ws.Close()
}

func (c *connection) writer() {
for message := range c.send {
err := c.ws.WriteMessage(websocket.TextMessage, message)
if err != nil {
break
}
}
c.ws.Close()
}
56 changes: 56 additions & 0 deletions internal/livereload/hub.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright 2015 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package livereload

type hub struct {
// Registered connections.
connections map[*connection]bool

// Inbound messages from the connections.
broadcast chan []byte

// Register requests from the connections.
register chan *connection

// Unregister requests from connections.
unregister chan *connection
}

var wsHub = hub{
broadcast: make(chan []byte),
register: make(chan *connection),
unregister: make(chan *connection),
connections: make(map[*connection]bool),
}

func (h *hub) run() {
for {
select {
case c := <-h.register:
h.connections[c] = true
case c := <-h.unregister:
delete(h.connections, c)
c.close()
case m := <-h.broadcast:
for c := range h.connections {
select {
case c.send <- m:
default:
delete(h.connections, c)
c.close()
}
}
}
}
}
47 changes: 47 additions & 0 deletions internal/livereload/livereload.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright 2015 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package livereload

import (
"fmt"
"net/http"

"github.com/gorilla/websocket"
)

// Initialize starts the Websocket Hub handling live reloads.
func Initialize() {
go wsHub.run()
}

// Handler is a HandlerFunc handling the livereload
// Websocket interaction.
func Handler(upgrader *websocket.Upgrader) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
c := &connection{send: make(chan []byte, 256), ws: ws}
wsHub.register <- c
defer func() { wsHub.unregister <- c }()
go c.writer()
c.reader()
}
}

func ReloadDashboard(uid string) {
msg := fmt.Sprintf(`{"command": "reload", "path": "/grizzly/Dashboard/%s"}`, uid)
wsHub.broadcast <- []byte(msg)
}
46 changes: 40 additions & 6 deletions pkg/grizzly/embed/templates/proxy/iframe.html.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,44 @@
<title>Grizzly</title>
<link rel="stylesheet" href="/grizzly/assets/style.css"/>
</head>
<body>
{{ template "proxy/header.html.tmpl" . }}
<iframe src="{{ .IframeURL }}"></iframe>

<html>
<body>
{{ template "proxy/header.html.tmpl" . }}
<iframe src="{{ .IframeURL }}"></iframe>
</body>
</html>
<script>
window.LiveReloadOptions = {
host: 'localhost',
port: {{ .Port }},
};
</script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/livereload.min.js"></script>
<script>
class CustomReloadPlugin {
constructor (window, host) {
this.window = window;
this.host = host;
}

reload (path, options) {
console.info('reload() path: ', path);
console.info('window.location.pathname', window.location.pathname);

if (path === window.location.pathname) {
this.window.document.location.reload();
}

return true;
}

analyze () {
return {};
}
}

CustomReloadPlugin.identifier = 'custom-reload';
CustomReloadPlugin.version = '1.0';

LiveReload.addPlugin(CustomReloadPlugin);
</script>
</body>
</html>
Loading

0 comments on commit 18bf2f6

Please sign in to comment.