Online users badge in the Chat link on nav bar
This commit is contained in:
parent
50d05f92f1
commit
78abee6e9e
|
@ -10,6 +10,7 @@ import (
|
|||
"code.nonshy.com/nonshy/website/pkg/models"
|
||||
"code.nonshy.com/nonshy/website/pkg/models/backfill"
|
||||
"code.nonshy.com/nonshy/website/pkg/redis"
|
||||
"code.nonshy.com/nonshy/website/pkg/worker"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/driver/sqlite"
|
||||
|
@ -75,6 +76,9 @@ func main() {
|
|||
Port: c.Int("port"),
|
||||
}
|
||||
|
||||
// Kick off background worker threads.
|
||||
go worker.WatchBareRTC()
|
||||
|
||||
return app.Run()
|
||||
},
|
||||
},
|
||||
|
|
|
@ -64,6 +64,9 @@ const (
|
|||
|
||||
// How frequently to refresh LastLoginAt since sessions are long-lived.
|
||||
LastLoginAtCooldown = 8 * time.Hour
|
||||
|
||||
// Chat room status refresh interval.
|
||||
ChatStatusRefreshInterval = 30 * time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"code.nonshy.com/nonshy/website/pkg/photo"
|
||||
"code.nonshy.com/nonshy/website/pkg/session"
|
||||
"code.nonshy.com/nonshy/website/pkg/templates"
|
||||
"code.nonshy.com/nonshy/website/pkg/worker"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
)
|
||||
|
||||
|
@ -106,6 +107,9 @@ func Landing() http.HandlerFunc {
|
|||
var vars = map[string]interface{}{
|
||||
"ChatAPI": strings.TrimSuffix(config.Current.BareRTC.URL, "/") + "/api/statistics",
|
||||
"IsShyUser": isShy,
|
||||
|
||||
// Pre-populate the "who's online" widget from backend cache data
|
||||
"ChatStatistics": worker.GetChatStatistics(),
|
||||
}
|
||||
if err := tmpl.Execute(w, r, vars); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"code.nonshy.com/nonshy/website/pkg/log"
|
||||
"code.nonshy.com/nonshy/website/pkg/models"
|
||||
"code.nonshy.com/nonshy/website/pkg/session"
|
||||
"code.nonshy.com/nonshy/website/pkg/worker"
|
||||
)
|
||||
|
||||
// MergeVars mixes in globally available template variables. The http.Request is optional.
|
||||
|
@ -37,6 +38,7 @@ func MergeUserVars(r *http.Request, m map[string]interface{}) {
|
|||
m["NavFriendRequests"] = 0 // Friend requests
|
||||
m["NavUnreadNotifications"] = 0 // general notifications
|
||||
m["NavTotalNotifications"] = 0 // Total of above
|
||||
m["NavChatStatistics"] = worker.GetChatStatistics()
|
||||
|
||||
// Admin notification counts for nav bar.
|
||||
m["NavCertificationPhotos"] = 0 // Cert. photos needing approval
|
||||
|
|
92
pkg/worker/barertc.go
Normal file
92
pkg/worker/barertc.go
Normal file
|
@ -0,0 +1,92 @@
|
|||
package worker
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"code.nonshy.com/nonshy/website/pkg/config"
|
||||
"code.nonshy.com/nonshy/website/pkg/log"
|
||||
)
|
||||
|
||||
// ChatStatistics is the json result of the BareRTC /api/statistics endpoint.
|
||||
type ChatStatistics struct {
|
||||
UserCount int
|
||||
Usernames []string
|
||||
}
|
||||
|
||||
// GetChatStatistics returns the latest (cached) chat statistics.
|
||||
func GetChatStatistics() ChatStatistics {
|
||||
chatStatisticsMu.RLock()
|
||||
defer chatStatisticsMu.RUnlock()
|
||||
|
||||
if cachedChatStatistics != nil {
|
||||
return *cachedChatStatistics
|
||||
}
|
||||
return ChatStatistics{
|
||||
Usernames: []string{},
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
cachedChatStatistics *ChatStatistics
|
||||
chatStatisticsMu sync.RWMutex
|
||||
)
|
||||
|
||||
// WatchBareRTC is a worker goroutine that caches the current online chatters in the chat room.
|
||||
func WatchBareRTC() {
|
||||
if config.Current.BareRTC.JWTSecret == "" || config.Current.BareRTC.URL == "" {
|
||||
log.Error("Worker (WatchBareRTC): chat room is not configured, will not watch chat room status")
|
||||
return
|
||||
}
|
||||
|
||||
// Check it immediately.
|
||||
DoCheckBareRTC()
|
||||
|
||||
// And on an interval forever.
|
||||
ticker := time.NewTicker(config.ChatStatusRefreshInterval)
|
||||
for range ticker.C {
|
||||
DoCheckBareRTC()
|
||||
}
|
||||
}
|
||||
|
||||
// DoCheckBareRTC invokes the attempt to refresh data from the chat server about who's online.
|
||||
func DoCheckBareRTC() {
|
||||
log.Info("Refresh BareRTC")
|
||||
req, err := http.NewRequest(http.MethodGet, config.Current.BareRTC.URL+"/api/statistics", nil)
|
||||
|
||||
// Lock the cached statistics.
|
||||
chatStatisticsMu.Lock()
|
||||
defer chatStatisticsMu.Unlock()
|
||||
|
||||
if err != nil {
|
||||
log.Error("WatchBareRTC: couldn't make request: %s", err)
|
||||
cachedChatStatistics = nil
|
||||
return
|
||||
}
|
||||
|
||||
client := http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Error("WatchBareRTC: request error: %s", err)
|
||||
cachedChatStatistics = nil
|
||||
return
|
||||
}
|
||||
|
||||
if res.StatusCode == http.StatusOK {
|
||||
var cs ChatStatistics
|
||||
body, _ := ioutil.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
if err = json.Unmarshal(body, &cs); err != nil {
|
||||
log.Error("WatchBareRTC: json decode error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
cachedChatStatistics = &cs
|
||||
}
|
||||
}
|
|
@ -86,4 +86,44 @@ abbr {
|
|||
/* Hide an element */
|
||||
.nonshy-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/***
|
||||
* Mobile navbar notification count badge no.
|
||||
*/
|
||||
|
||||
/* mobile view: just superset text */
|
||||
.nonshy-navbar-notification-count {
|
||||
|
||||
font-size: xx-small;
|
||||
padding-bottom: 12px;
|
||||
margin-right: -4px;
|
||||
margin-left: -4px;
|
||||
}
|
||||
|
||||
/* desktop view: colored badge similar to bulma `tag is-warning ml-1`*/
|
||||
.nonshy-navbar-notification-tag {
|
||||
align-items: center;
|
||||
border-radius: 4px;
|
||||
display: inline-flex;
|
||||
font-size: xx-small;
|
||||
height: 1em;
|
||||
justify-content: center;
|
||||
vertical-align: top;
|
||||
line-height: 1.5;
|
||||
padding: .75em;
|
||||
white-space: nowrap;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
.nonshy-navbar-notification-tag.is-warning {
|
||||
background-color: #ffd324;
|
||||
color: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
.nonshy-navbar-notification-tag.is-info {
|
||||
background-color: #0f81cc;
|
||||
color: #fff;
|
||||
}
|
||||
.nonshy-navbar-notification-tag.is-danger {
|
||||
background-color: #ff0537;
|
||||
color: #fff;
|
||||
}
|
|
@ -52,6 +52,7 @@
|
|||
<a class="navbar-item" href="/chat">
|
||||
<span class="icon"><i class="fa fa-message"></i></span>
|
||||
<span>Chat</span>
|
||||
{{if .NavChatStatistics.UserCount}}<span class="nonshy-navbar-notification-tag is-info ml-1">{{.NavChatStatistics.UserCount}}</span>{{end}}
|
||||
</a>
|
||||
|
||||
<a class="navbar-item" href="/forum">
|
||||
|
@ -67,13 +68,13 @@
|
|||
<a class="navbar-item" href="/friends{{if gt .NavFriendRequests 0}}?view=requests{{end}}">
|
||||
<span class="icon"><i class="fa fa-user-group"></i></span>
|
||||
<span>Friends</span>
|
||||
{{if .NavFriendRequests}}<span class="tag is-warning ml-1">{{.NavFriendRequests}}</span>{{end}}
|
||||
{{if .NavFriendRequests}}<span class="nonshy-navbar-notification-tag is-warning ml-1">{{.NavFriendRequests}}</span>{{end}}
|
||||
</a>
|
||||
|
||||
<a class="navbar-item" href="/messages">
|
||||
<span class="icon"><i class="fa fa-envelope"></i></span>
|
||||
<span>Messages</span>
|
||||
{{if .NavUnreadMessages}}<span class="tag is-warning ml-1">{{.NavUnreadMessages}}</span>{{end}}
|
||||
{{if .NavUnreadMessages}}<span class="nonshy-navbar-notification-tag is-warning ml-1">{{.NavUnreadMessages}}</span>{{end}}
|
||||
</a>
|
||||
{{end}}
|
||||
|
||||
|
@ -134,8 +135,8 @@
|
|||
</div>
|
||||
<div class="column">
|
||||
{{.CurrentUser.Username}}
|
||||
{{if .NavUnreadNotifications}}<span class="tag is-warning ml-1">{{.NavUnreadNotifications}}</span>{{end}}
|
||||
{{if .NavAdminNotifications}}<span class="tag is-danger ml-1">{{.NavAdminNotifications}}</span>{{end}}
|
||||
{{if .NavUnreadNotifications}}<span class="nonshy-navbar-notification-tag is-warning ml-1">{{.NavUnreadNotifications}}</span>{{end}}
|
||||
{{if .NavAdminNotifications}}<span class="nonshy-navbar-notification-tag is-danger ml-1">{{.NavAdminNotifications}}</span>{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
@ -145,7 +146,7 @@
|
|||
<span class="icon"><i class="fa fa-home-user"></i></span>
|
||||
<span>Dashboard</span>
|
||||
{{if .NavUnreadNotifications}}
|
||||
<span class="tag is-warning ml-1">
|
||||
<span class="nonshy-navbar-notification-tag is-warning ml-1">
|
||||
<span class="icon"><i class="fa fa-bell"></i></span>
|
||||
<span>{{.NavUnreadNotifications}}</span>
|
||||
</span>
|
||||
|
@ -181,7 +182,7 @@
|
|||
<a class="navbar-item has-text-danger" href="/admin">
|
||||
<span class="icon"><i class="fa fa-gavel"></i></span>
|
||||
<span>Admin</span>
|
||||
{{if .NavAdminNotifications}}<span class="tag is-danger ml-1">{{.NavAdminNotifications}}</span>{{end}}
|
||||
{{if .NavAdminNotifications}}<span class="nonshy-navbar-notification-tag is-danger ml-1">{{.NavAdminNotifications}}</span>{{end}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{if .SessionImpersonated}}
|
||||
|
@ -216,9 +217,12 @@
|
|||
{{if .LoggedIn}}
|
||||
<div class="mobile nonshy-mobile-notification">
|
||||
{{if .CurrentUser.Certified}}
|
||||
<a class="tag is-grey py-4"
|
||||
<a class="tag {{if gt .NavChatStatistics.UserCount 0}}is-info{{else}}is-grey{{end}} py-4"
|
||||
href="/chat">
|
||||
<span class="icon"><i class="fa fa-message"></i></span>
|
||||
{{if gt .NavChatStatistics.UserCount 0}}
|
||||
<small class="nonshy-navbar-notification-count">{{.NavChatStatistics.UserCount}}</small>
|
||||
{{end}}
|
||||
</a>
|
||||
|
||||
<a class="tag is-grey py-4"
|
||||
|
@ -236,7 +240,7 @@
|
|||
href="/friends{{if gt .NavFriendRequests 0}}?view=requests{{end}}">
|
||||
<span class="icon"><i class="fa fa-user-group"></i></span>
|
||||
{{if gt .NavFriendRequests 0}}
|
||||
<small>{{.NavFriendRequests}}</small>
|
||||
<small class="nonshy-navbar-notification-count">{{.NavFriendRequests}}</small>
|
||||
{{end}}
|
||||
</a>
|
||||
|
||||
|
@ -244,21 +248,21 @@
|
|||
<a class="tag {{if gt .NavUnreadMessages 0}}is-warning{{else}}is-grey{{end}} py-4" href="/messages">
|
||||
<span class="icon"><i class="fa fa-envelope"></i></span>
|
||||
{{if gt .NavUnreadMessages 0}}
|
||||
<small>{{.NavUnreadMessages}}</small>
|
||||
<small class="nonshy-navbar-notification-count">{{.NavUnreadMessages}}</small>
|
||||
{{end}}
|
||||
</a>
|
||||
|
||||
{{if gt .NavUnreadNotifications 0}}
|
||||
<a class="tag is-warning py-4" href="/me#notifications">
|
||||
<span class="icon"><i class="fa fa-bell"></i></span>
|
||||
<small>{{.NavUnreadNotifications}}</small>
|
||||
<small class="nonshy-navbar-notification-count">{{.NavUnreadNotifications}}</small>
|
||||
</a>
|
||||
{{end}}
|
||||
|
||||
{{if gt .NavAdminNotifications 0}}
|
||||
<a class="tag is-danger py-4" href="/admin">
|
||||
<span class="icon"><i class="fa fa-gavel"></i></span>
|
||||
<small>{{.NavAdminNotifications}}</small>
|
||||
<small class="nonshy-navbar-notification-count">{{.NavAdminNotifications}}</small>
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
|
|
|
@ -128,13 +128,41 @@
|
|||
</div>
|
||||
|
||||
<script>
|
||||
function showWhoBanner(chatStatistics) {
|
||||
const $banner = document.querySelector("#chatStatsBanner"),
|
||||
$whoLink = document.querySelector("#whoLink"),
|
||||
$whoList = document.querySelector("#whoList"),
|
||||
$usersOnline = document.querySelector("#usersOnline");
|
||||
|
||||
$banner.style.display = "block";
|
||||
|
||||
console.log(chatStatistics);
|
||||
let people = chatStatistics.UserCount === 1 ? 'person' : 'people';
|
||||
let isAre = chatStatistics.UserCount === 1 ? 'is' : 'are';
|
||||
$usersOnline.innerHTML = `There ${isAre} currently <strong>${chatStatistics.UserCount}</strong> ${people}</span> in the chat room`;
|
||||
$whoList.innerHTML = chatStatistics.Usernames.join(", ");
|
||||
|
||||
// Show the "Who?" link if there's anybody.
|
||||
if (chatStatistics.UserCount > 0) {
|
||||
$usersOnline.innerHTML += ":";
|
||||
$whoLink.style.visibility = "visible";
|
||||
} else {
|
||||
$usersOnline.innerHTML += ".";
|
||||
}
|
||||
}
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
let url = "{{.ChatAPI}}",
|
||||
ChatStatistics = {{.ChatStatistics}},
|
||||
$banner = document.querySelector("#chatStatsBanner"),
|
||||
$whoLink = document.querySelector("#whoLink"),
|
||||
$whoList = document.querySelector("#whoList"),
|
||||
$usersOnline = document.querySelector("#usersOnline");
|
||||
|
||||
// If we already know people are online, show the banner immediately while we refresh from live data
|
||||
if (ChatStatistics.UserCount > 0) {
|
||||
showWhoBanner(ChatStatistics);
|
||||
}
|
||||
|
||||
$whoLink.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
$whoLink.style.display = "none";
|
||||
|
@ -145,20 +173,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
mode: 'cors',
|
||||
cache: 'no-cache',
|
||||
}).then(resp => resp.json()).then(result => {
|
||||
$banner.style.display = "block";
|
||||
console.log(result);
|
||||
let people = result.UserCount === 1 ? 'person' : 'people';
|
||||
let isAre = result.UserCount === 1 ? 'is' : 'are';
|
||||
$usersOnline.innerHTML = `There ${isAre} currently <strong>${result.UserCount}</strong> ${people}</span> in the chat room`;
|
||||
$whoList.innerHTML = result.Usernames.join(", ");
|
||||
|
||||
// Show the "Who?" link if there's anybody.
|
||||
if (result.UserCount > 0) {
|
||||
$usersOnline.innerHTML += ":";
|
||||
$whoLink.style.visibility = "visible";
|
||||
} else {
|
||||
$usersOnline.innerHTML += ".";
|
||||
}
|
||||
showWhoBanner(result);
|
||||
}).catch(console.error);
|
||||
});
|
||||
</script>
|
||||
|
|
Loading…
Reference in New Issue
Block a user