Skip to content

Commit

Permalink
Move notifications/update notifications into template function, add w…
Browse files Browse the repository at this point in the history
…ebsockets handler, add notification sending for node errors and updates
  • Loading branch information
NHAS committed Apr 27, 2024
1 parent 6c3a0ce commit 1211f6b
Show file tree
Hide file tree
Showing 15 changed files with 183 additions and 56 deletions.
3 changes: 2 additions & 1 deletion internal/data/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const (
ConfigPrefix = "wag-config-"
AuthenticationPrefix = "wag-config-authentication-"
NodeEvents = "wag/node/"
NodeErrors = "wag/node/errors"
)

var (
Expand All @@ -66,7 +67,7 @@ func RegisterEventListener[T any](path string, isPrefix bool, f func(key string,

key, err := generateRandomBytes(16)
if err != nil {
return "", nil
return "", err
}

ctx, cancel := context.WithCancel(context.Background())
Expand Down
4 changes: 2 additions & 2 deletions ui/clustering.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func clusterMembersUI(w http.ResponseWriter, r *http.Request) {
CurrentNode string
}{
Page: Page{
Notification: getUpdate(),

Description: "Clustering Management Page",
Title: "Clustering",
User: u.Username,
Expand Down Expand Up @@ -187,7 +187,7 @@ func clusterEventsUI(w http.ResponseWriter, r *http.Request) {
Errors []data.EventError
}{
Page: Page{
Notification: getUpdate(),

Description: "Clustering Management Page",
Title: "Clustering",
User: u.Username,
Expand Down
4 changes: 2 additions & 2 deletions ui/daignostics.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func firewallDiagnositicsUI(w http.ResponseWriter, r *http.Request) {
XDPState string
}{
Page: Page{
Notification: getUpdate(),

Description: "Firewall state page",
Title: "Firewall",
User: u.Username,
Expand Down Expand Up @@ -75,7 +75,7 @@ func wgDiagnositicsUI(w http.ResponseWriter, r *http.Request) {
}

d := Page{
Notification: getUpdate(),

Description: "Wireguard Devices",
Title: "wg",
User: u.Username,
Expand Down
2 changes: 1 addition & 1 deletion ui/dashboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func populateDashboard(w http.ResponseWriter, r *http.Request) {

d := Dashboard{
Page: Page{
Notification: getUpdate(),

Description: "Dashboard",
Title: "Dashboard",
User: u.Username,
Expand Down
2 changes: 1 addition & 1 deletion ui/devices.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func devicesMgmtUI(w http.ResponseWriter, r *http.Request) {
}

d := Page{
Notification: getUpdate(),

Description: "Devices Management Page",
Title: "Devices",
User: u.Username,
Expand Down
2 changes: 1 addition & 1 deletion ui/groups.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func groupsUI(w http.ResponseWriter, r *http.Request) {
}

d := Page{
Notification: getUpdate(),

Description: "Groups",
Title: "Groups",
User: u.Username,
Expand Down
146 changes: 110 additions & 36 deletions ui/notifications.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import (
"encoding/json"
"log"
"net/http"
"sort"
"strings"
"sync"
"time"

"github.com/NHAS/wag/internal/data"
"github.com/gorilla/websocket"
"golang.org/x/exp/maps"
)

var upgrader = websocket.Upgrader{
Expand All @@ -21,19 +24,55 @@ type Acknowledgement struct {
ID string
}

func notificationsWS(w http.ResponseWriter, r *http.Request) {
// Upgrade HTTP connection to WebSocket connection
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)
return
}
defer conn.Close()
func notificationsWS(notifications <-chan Notification) func(w http.ResponseWriter, r *http.Request) {

go func() {
var mapLck sync.RWMutex
servingConnections := map[string]chan<- Notification{}

go func() {
for notification := range notifications {
for key := range servingConnections {
go func(key string, notification Notification) {
servingConnections[key] <- notification
}(key, notification)
}
}
}()

return func(w http.ResponseWriter, r *http.Request) {
// Upgrade HTTP connection to WebSocket connection
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)
return
}

connectionChan := make(chan Notification)
defer func() {
mapLck.Lock()
delete(servingConnections, r.RemoteAddr)
mapLck.Unlock()

close(connectionChan)
conn.Close()
}()

mapLck.Lock()
servingConnections[r.RemoteAddr] = connectionChan
mapLck.Unlock()

for notf := range connectionChan {
conn.SetWriteDeadline(time.Now().Add(10 * time.Second))

err := conn.WriteJSON(notf)
if err != nil {
return
}

conn.SetWriteDeadline(time.Time{})
}

}
}

type githubResponse struct {
Expand All @@ -45,43 +84,78 @@ type githubResponse struct {
}

type Notification struct {
ID string
Heading string
Message []string
Url string
ID string
Heading string
Message []string
Url string
Time time.Time
Color string
OpenNewTab bool
}

var (
mostRecentUpdate *Notification
lastChecked time.Time
notifications = map[string]Notification{}
)

func getUpdate() Notification {
func getNotifications() []Notification {

should, err := data.ShouldCheckUpdates()
if err != nil || !should {
return Notification{}
}
notfs := maps.Values(notifications)
sort.Slice(notfs, func(i, j int) bool {
return notfs[i].Time.After(notfs[j].Time)
})

if time.Now().After(lastChecked.Add(15*time.Minute)) || mostRecentUpdate == nil {
resp, err := http.Get("https://api.github.com/repos/NHAS/wag/releases/latest")
if err != nil {
return Notification{}
}
defer resp.Body.Close()
return notfs
}

var gr githubResponse
err = json.NewDecoder(resp.Body).Decode(&gr)
if err != nil {
return Notification{}
func startUpdateChecker(notifications chan<- Notification) {
go func() {

for {
resp, err := http.Get("https://api.github.com/repos/NHAS/wag/releases/latest")
if err != nil {
log.Println("unable to fetch updates: ", err)
return
}
defer resp.Body.Close()

var gr githubResponse
err = json.NewDecoder(resp.Body).Decode(&gr)
if err != nil {
log.Println("unable to parse update json: ", err)
return
}

notifications <- Notification{
Heading: gr.TagName,
Message: strings.Split(gr.Body, "\r\n"),
Url: gr.Url,
Time: time.Now(),
OpenNewTab: true,
Color: "#0bb329",
}

<-time.After(15 * time.Minute)
}
}()
}

func recieveErrorNotifications(notifications chan<- Notification) func(key string, current, previous data.EventError, et data.EventType) error {

mostRecentUpdate = &Notification{
Heading: gr.TagName,
Message: strings.Split(gr.Body, "\r\n"),
Url: gr.Url,
return func(key string, current, previous data.EventError, et data.EventType) error {
if et == data.CREATED {

msg := Notification{
ID: current.ErrorID,
Heading: "Node Error",
Message: []string{"Node " + current.NodeID, current.Error},
Url: "/cluster/events/",
Time: time.Now(),
OpenNewTab: false,
Color: "#db0b3c",
}

notifications <- msg
}
return nil
}

return *mostRecentUpdate
}
2 changes: 1 addition & 1 deletion ui/policies.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func policiesUI(w http.ResponseWriter, r *http.Request) {
return
}
d := Page{
Notification: getUpdate(),

Description: "Firewall rules",
Title: "Rules",
User: u.Username,
Expand Down
2 changes: 1 addition & 1 deletion ui/registration.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func registrationUI(w http.ResponseWriter, r *http.Request) {
}

d := Page{
Notification: getUpdate(),

Description: "Registration Tokens Management Page",
Title: "Registration",
User: u.Username,
Expand Down
4 changes: 2 additions & 2 deletions ui/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func adminUsersUI(w http.ResponseWriter, r *http.Request) {
}

d := Page{
Notification: getUpdate(),

Description: "Wag settings",
Title: "Settings - Admin Users",
User: u.Username,
Expand Down Expand Up @@ -93,7 +93,7 @@ func generalSettingsUI(w http.ResponseWriter, r *http.Request) {
MFAMethods []authenticators.Authenticator
}{
Page: Page{
Notification: getUpdate(),

Description: "Wag settings",
Title: "Settings - General",
User: u.Username,
Expand Down
31 changes: 28 additions & 3 deletions ui/src/js/notifications.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,39 @@





const httpsEnabled = window.location.protocol == "https:";
const url = (httpsEnabled ? 'wss://' : 'ws://') + window.location.host + "/notifications";

var socket = new WebSocket(url)
let socket = new WebSocket(url)

const alertBadge = document.getElementById("numNotifications");
const dropDownlist = document.getElementById("notificationsDropDown");

socket.onmessage = function (e) {
const msg = JSON.parse(e.data)

if (dropDownlist.querySelectorAll(".notification").length > 0) {
alertBadge.textContent = dropDownlist.querySelectorAll(".notification").length
alertBadge.hidden = false
}

socket.onmessage = function (e) {
const msg = JSON.parse(e.data)
Toastify({
text: msg.Message.join('\n'),
className: "info",
destination: msg.Url,
newWindow: msg.OpenNewTab,
position: "right",
gravity: "top",
offset: {
y: 30,
},
stopOnFocus: true,
style: {
background: msg.Color,
}
}).showToast();
}


Expand Down
1 change: 0 additions & 1 deletion ui/structs.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package ui

type Page struct {
Notification
Description string
Title string
User string
Expand Down
15 changes: 15 additions & 0 deletions ui/templates/menus.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<!-- Toastify for notifications-->
<script src="/vendor/toastify/js/toastify.js"></script>
<link rel="stylesheet" type="text/css" href="/vendor/toastify/css/toastify.css">

</head>

<template id="notificationTemplate">
Expand Down Expand Up @@ -226,6 +227,20 @@ <h6 class="collapse-header">Tools:</h6>
<!-- Dropdown - Alerts -->
<div class="dropdown-list dropdown-menu dropdown-menu-right shadow animated--grow-in"
aria-labelledby="alertsDropdown" id="notificationsDropDown">

{{range $val := notifications}}
<a id={{$val.ID}} class="notification dropdown-item d-flex align-items-center"
href="{{$val.Url}}" {{if $val.OpenNewTab}}target="_blank" rel="noopener noreferrer"
{{end}}>
<div>
<div class="small text-gray-500">{{$val.Time}}</div>
<span class="font-weight-bold">{{$val.Heading}}</span>
{{range $val := $val.Message}}
<p>{{$val}}</p>
{{end}}
</div>
</a>
{{end}}
</div>
</li>

Expand Down
Loading

0 comments on commit 1211f6b

Please sign in to comment.