Added support for Websockets.
The Control Panel Dashboard now updates every second. You can now see how many guests and users are online via the Control Panel Dashboard. The Control Panel Dashboard is now a little more mobile friendly.
This commit is contained in:
parent
fab2db0936
commit
3b5f48b5a2
|
@ -28,12 +28,14 @@ A plugin system. More on this to come.
|
|||
|
||||
A responsive design. Looks great on mobile phones, tablets, laptops, desktops and more!
|
||||
|
||||
Other modern features like alerts, advanced dashboard, etc.
|
||||
|
||||
|
||||
# Dependencies
|
||||
|
||||
Go 1.7. You will need to install this. Pick the .msi, if you want everything sorted out for you rather than having to go around updating the environment settings. https://golang.org/doc/install
|
||||
Go 1.8 - You will need to install this. Pick the .msi, if you want everything sorted out for you rather than having to go around updating the environment settings. https://golang.org/doc/install
|
||||
|
||||
MySQL Database. You will need to setup a MySQL Database somewhere. A MariaDB Database works equally well and is much faster than MySQL. You could use something like WNMP / XAMPP which have a little PHP script called PhpMyAdmin for managing MySQL databases or you could install MariaDB directly.
|
||||
MySQL Database - You will need to setup a MySQL Database somewhere. A MariaDB Database works equally well and is much faster than MySQL. You could use something like WNMP / XAMPP which have a little PHP script called PhpMyAdmin for managing MySQL databases or you could install MariaDB directly.
|
||||
|
||||
Download the .msi installer from [MariaDB](https://mariadb.com/downloads) and run that. You may want to set it up as a service to avoid running it every-time the computer starts up.
|
||||
|
||||
|
@ -122,6 +124,8 @@ We're looking for ways to clean-up the plugin system so that all of them (except
|
|||
|
||||
* github.com/StackExchange/wmi Dependency for gopsutil on Windows.
|
||||
|
||||
* github.com/gorilla/websocket Needed for Gosora's Optional WebSockets Module.
|
||||
|
||||
# Bundled Plugins
|
||||
|
||||
There are several plugins which are bundled with the software by default. These cover various common tasks which aren't common enough to clutter the core with or which have competing implementation methods (E.g. plugin_markdown vs plugin_bbcode for post mark-up).
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
echo "Building Gosora"
|
||||
go build -o Gosora -tags no_ws
|
||||
echo "Building the installer"
|
||||
go build ./install
|
|
@ -0,0 +1,30 @@
|
|||
@echo off
|
||||
echo Generating the dynamic code
|
||||
go generate
|
||||
if %errorlevel% neq 0 (
|
||||
pause
|
||||
exit /b %errorlevel%
|
||||
)
|
||||
|
||||
echo Building the executable
|
||||
go build -o gosora.exe -tags no_ws
|
||||
if %errorlevel% neq 0 (
|
||||
pause
|
||||
exit /b %errorlevel%
|
||||
)
|
||||
|
||||
echo Building the installer
|
||||
go build ./install
|
||||
if %errorlevel% neq 0 (
|
||||
pause
|
||||
exit /b %errorlevel%
|
||||
)
|
||||
|
||||
echo Building the router generator
|
||||
go build ./router_gen
|
||||
if %errorlevel% neq 0 (
|
||||
pause
|
||||
exit /b %errorlevel%
|
||||
)
|
||||
echo Gosora was successfully built
|
||||
pause
|
Binary file not shown.
After Width: | Height: | Size: 49 KiB |
|
@ -4,6 +4,8 @@ echo "Installing bcrypt"
|
|||
go get -u golang.org/x/crypto/bcrypt
|
||||
echo "Installing gopsutil"
|
||||
go get -u github.com/shirou/gopsutil
|
||||
echo "Installing Gorilla WebSockets"
|
||||
go get -u github.com/gorilla/websocket
|
||||
|
||||
echo "Preparing the installer"
|
||||
go generate
|
||||
|
|
|
@ -20,6 +20,11 @@ if %errorlevel% neq 0 (
|
|||
pause
|
||||
exit /b %errorlevel%
|
||||
)
|
||||
go get -u github.com/gorilla/websocket
|
||||
if %errorlevel% neq 0 (
|
||||
pause
|
||||
exit /b %errorlevel%
|
||||
)
|
||||
|
||||
echo Preparing the installer
|
||||
go generate
|
||||
|
@ -27,7 +32,7 @@ if %errorlevel% neq 0 (
|
|||
pause
|
||||
exit /b %errorlevel%
|
||||
)
|
||||
go build
|
||||
go build -o gosora.exe
|
||||
if %errorlevel% neq 0 (
|
||||
pause
|
||||
exit /b %errorlevel%
|
||||
|
|
2
main.go
2
main.go
|
@ -27,6 +27,7 @@ const gigabyte int = megabyte * 1024
|
|||
const terabyte int = gigabyte * 1024
|
||||
const saltLength int = 32
|
||||
const sessionLength int = 80
|
||||
var enable_websockets bool = false // Don't change this, the value is overwritten by an initialiser
|
||||
|
||||
var templates = template.New("")
|
||||
var no_css_tmpl = template.CSS("")
|
||||
|
@ -273,6 +274,7 @@ func main(){
|
|||
///router.HandleFunc("/api/", route_api)
|
||||
//router.HandleFunc("/exit/", route_exit)
|
||||
///router.HandleFunc("/", default_route)
|
||||
router.HandleFunc("/ws/", route_websockets)
|
||||
defer db.Close()
|
||||
|
||||
//if profiling {
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
package main
|
||||
|
||||
import "log"
|
||||
import "fmt"
|
||||
import "strconv"
|
||||
import "net"
|
||||
import "net/http"
|
||||
import "html"
|
||||
import "database/sql"
|
||||
import _ "github.com/go-sql-driver/mysql"
|
||||
import (
|
||||
"log"
|
||||
// "fmt"
|
||||
"strconv"
|
||||
"net"
|
||||
"net/http"
|
||||
"html"
|
||||
"database/sql"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
func route_edit_topic(w http.ResponseWriter, r *http.Request) {
|
||||
err := r.ParseForm()
|
||||
|
@ -20,8 +22,7 @@ func route_edit_topic(w http.ResponseWriter, r *http.Request) {
|
|||
is_js = "0"
|
||||
}
|
||||
|
||||
var tid int
|
||||
var fid int
|
||||
var tid, fid int
|
||||
tid, err = strconv.Atoi(r.URL.Path[len("/topic/edit/submit/"):])
|
||||
if err != nil {
|
||||
PreErrorJSQ("The provided TopicID is not a valid number.",w,r,is_js)
|
||||
|
@ -104,7 +105,7 @@ func route_edit_topic(w http.ResponseWriter, r *http.Request) {
|
|||
if is_js == "0" {
|
||||
http.Redirect(w,r,"/topic/" + strconv.Itoa(tid),http.StatusSeeOther)
|
||||
} else {
|
||||
fmt.Fprintf(w,`{"success":"1"}`)
|
||||
w.Write(success_json_bytes)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,8 +117,7 @@ func route_delete_topic(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
var content string
|
||||
var createdBy int
|
||||
var fid int
|
||||
var createdBy, fid int
|
||||
err = db.QueryRow("select content, createdBy, parentID from topics where tid = ?", tid).Scan(&content, &createdBy, &fid)
|
||||
if err == sql.ErrNoRows {
|
||||
PreError("The topic you tried to delete doesn't exist.",w,r)
|
||||
|
@ -344,7 +344,7 @@ func route_reply_edit_submit(w http.ResponseWriter, r *http.Request) {
|
|||
if is_js == "0" {
|
||||
http.Redirect(w,r, "/topic/" + strconv.Itoa(tid) + "#reply-" + strconv.Itoa(rid), http.StatusSeeOther)
|
||||
} else {
|
||||
fmt.Fprintf(w,`{"success":"1"}`)
|
||||
w.Write(success_json_bytes)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -365,9 +365,8 @@ func route_reply_delete_submit(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
var tid int
|
||||
var tid, createdBy int
|
||||
var content string
|
||||
var createdBy int
|
||||
err = db.QueryRow("select tid, content, createdBy from replies where rid = ?", rid).Scan(&tid, &content, &createdBy)
|
||||
if err == sql.ErrNoRows {
|
||||
PreErrorJSQ("The reply you tried to delete doesn't exist.",w,r,is_js)
|
||||
|
@ -405,7 +404,7 @@ func route_reply_delete_submit(w http.ResponseWriter, r *http.Request) {
|
|||
if is_js == "0" {
|
||||
//http.Redirect(w,r, "/topic/" + strconv.Itoa(tid), http.StatusSeeOther)
|
||||
} else {
|
||||
fmt.Fprintf(w,`{"success":"1"}`)
|
||||
w.Write(success_json_bytes)
|
||||
}
|
||||
|
||||
wcount := word_count(content)
|
||||
|
@ -482,7 +481,7 @@ func route_profile_reply_edit_submit(w http.ResponseWriter, r *http.Request) {
|
|||
if is_js == "0" {
|
||||
http.Redirect(w,r,"/user/" + strconv.Itoa(uid) + "#reply-" + strconv.Itoa(rid), http.StatusSeeOther)
|
||||
} else {
|
||||
fmt.Fprintf(w,`{"success":"1"}`)
|
||||
w.Write(success_json_bytes)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -533,7 +532,7 @@ func route_profile_reply_delete_submit(w http.ResponseWriter, r *http.Request) {
|
|||
if is_js == "0" {
|
||||
//http.Redirect(w,r, "/user/" + strconv.Itoa(uid), http.StatusSeeOther)
|
||||
} else {
|
||||
fmt.Fprintf(w,`{"success":"1"}`)
|
||||
w.Write(success_json_bytes)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
// +build no_ws
|
||||
|
||||
package main
|
||||
|
||||
import "net/http"
|
||||
|
||||
var ws_hub WS_Hub
|
||||
|
||||
type WS_Hub struct
|
||||
{
|
||||
}
|
||||
|
||||
func (_ *WS_Hub) GuestCount() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (_ *WS_Hub) UserCount() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func route_websockets(_ http.ResponseWriter, _ *http.Request) {
|
||||
}
|
1
pages.go
1
pages.go
|
@ -79,6 +79,7 @@ type CreateTopicPage struct
|
|||
|
||||
type GridElement struct
|
||||
{
|
||||
ID string
|
||||
Body string
|
||||
Order int // For future use
|
||||
Class string
|
||||
|
|
|
@ -34,19 +34,9 @@ func route_panel(w http.ResponseWriter, r *http.Request){
|
|||
if err != nil {
|
||||
cpustr = "Unknown"
|
||||
} else {
|
||||
/*cpures, _ := cpu.Times(true)
|
||||
totcpu := cpures[0].Idle + cpures[0].System + cpures[0].User
|
||||
fmt.Println("System",cpures[0].System)
|
||||
fmt.Println("User",cpures[0].User)
|
||||
fmt.Println("Usage",cpures[0].System + cpures[0].User)
|
||||
fmt.Println("Idle",cpures[0].Idle)
|
||||
fmt.Println("Gap",totcpu - (cpures[0].System + cpures[0].User))
|
||||
perc := ((cpures[0].System + + cpures[0].User) * 100) / totcpu
|
||||
fmt.Println("Perc",perc)
|
||||
fmt.Println("Perc2",perc2)*/
|
||||
calcperc := int(perc2[0]) / runtime.NumCPU()
|
||||
cpustr = strconv.Itoa(calcperc)
|
||||
if calcperc < 25 {
|
||||
if calcperc < 30 {
|
||||
cpuColour = "stat_green"
|
||||
} else if calcperc < 75 {
|
||||
cpuColour = "stat_orange"
|
||||
|
@ -99,9 +89,9 @@ func route_panel(w http.ResponseWriter, r *http.Request){
|
|||
var postInterval string = "day"
|
||||
|
||||
var postColour string
|
||||
if postCount > 10 {
|
||||
if postCount > 25 {
|
||||
postColour = "stat_green"
|
||||
} else if postCount > 0 {
|
||||
} else if postCount > 5 {
|
||||
postColour = "stat_orange"
|
||||
} else {
|
||||
postColour = "stat_red"
|
||||
|
@ -116,7 +106,7 @@ func route_panel(w http.ResponseWriter, r *http.Request){
|
|||
var topicInterval string = "day"
|
||||
|
||||
var topicColour string
|
||||
if topicCount > 10 {
|
||||
if topicCount > 8 {
|
||||
topicColour = "stat_green"
|
||||
} else if topicCount > 0 {
|
||||
topicColour = "stat_orange"
|
||||
|
@ -141,23 +131,60 @@ func route_panel(w http.ResponseWriter, r *http.Request){
|
|||
var newUserInterval string = "week"
|
||||
|
||||
var gridElements []GridElement = []GridElement{
|
||||
GridElement{"v" + version.String(),0,"grid_istat stat_green","","","Gosora is up-to-date :)"},
|
||||
GridElement{"CPU: " + cpustr + "%",1,"grid_istat " + cpuColour,"","","The global CPU usage of this server"},
|
||||
GridElement{"RAM: " + ramstr,2,"grid_istat " + ramColour,"","","The global RAM usage of this server"},
|
||||
|
||||
GridElement{strconv.Itoa(postCount) + " posts / " + postInterval,3,"grid_stat " + postColour,"","","The number of new posts over the last 24 hours"},
|
||||
GridElement{strconv.Itoa(topicCount) + " topics / " + topicInterval,4,"grid_stat " + topicColour,"","","The number of new topics over the last 24 hours"},
|
||||
GridElement{"20 online / day",5,"grid_stat stat_disabled","","","Coming Soon!"/*"The people online over the last 24 hours"*/},
|
||||
|
||||
GridElement{"8 searches / week",6,"grid_stat stat_disabled","","","Coming Soon!"/*"The number of searches over the last 7 days"*/},
|
||||
GridElement{strconv.Itoa(newUserCount) + " new users / " + newUserInterval,7,"grid_stat","","","The number of new users over the last 7 days"},
|
||||
GridElement{strconv.Itoa(reportCount) + " reports / " + reportInterval,8,"grid_stat","","","The number of reports over the last 7 days"},
|
||||
|
||||
GridElement{"2 minutes / user / week",9,"grid_stat stat_disabled","","","Coming Soon!"/*"The average number of number of minutes spent by each active user over the last 7 days"*/},
|
||||
GridElement{"2 visitors / week",10,"grid_stat stat_disabled","","","Coming Soon!"/*"The number of unique visitors we've had over the last 7 days"*/},
|
||||
GridElement{"5 posts / user / week",11,"grid_stat stat_disabled","","","Coming Soon!"/*"The average number of posts made by each active user over the past week"*/},
|
||||
GridElement{"dash-version","v" + version.String(),0,"grid_istat stat_green","","","Gosora is up-to-date :)"},
|
||||
GridElement{"dash-cpu","CPU: " + cpustr + "%",1,"grid_istat " + cpuColour,"","","The global CPU usage of this server"},
|
||||
GridElement{"dash-ram","RAM: " + ramstr,2,"grid_istat " + ramColour,"","","The global RAM usage of this server"},
|
||||
}
|
||||
|
||||
if enable_websockets {
|
||||
uonline := ws_hub.UserCount()
|
||||
gonline := ws_hub.GuestCount()
|
||||
totonline := uonline + gonline
|
||||
|
||||
var onlineColour string
|
||||
if totonline > 10 {
|
||||
onlineColour = "stat_green"
|
||||
} else if totonline > 3 {
|
||||
onlineColour = "stat_orange"
|
||||
} else {
|
||||
onlineColour = "stat_red"
|
||||
}
|
||||
|
||||
var onlineGuestsColour string
|
||||
if gonline > 10 {
|
||||
onlineGuestsColour = "stat_green"
|
||||
} else if gonline > 1 {
|
||||
onlineGuestsColour = "stat_orange"
|
||||
} else {
|
||||
onlineGuestsColour = "stat_red"
|
||||
}
|
||||
|
||||
var onlineUsersColour string
|
||||
if uonline > 5 {
|
||||
onlineUsersColour = "stat_green"
|
||||
} else if uonline > 1 {
|
||||
onlineUsersColour = "stat_orange"
|
||||
} else {
|
||||
onlineUsersColour = "stat_red"
|
||||
}
|
||||
|
||||
gridElements = append(gridElements, GridElement{"dash-totonline",strconv.Itoa(totonline) + " online",3,"grid_stat " + onlineColour,"","","The number of people who are currently online"})
|
||||
gridElements = append(gridElements, GridElement{"dash-gonline",strconv.Itoa(gonline) + " guests online",4,"grid_stat " + onlineGuestsColour,"","","The number of guests who are currently online"})
|
||||
gridElements = append(gridElements, GridElement{"dash-uonline",strconv.Itoa(uonline) + " users online",5,"grid_stat " + onlineUsersColour,"","","The number of logged-in users who are currently online"})
|
||||
}
|
||||
|
||||
gridElements = append(gridElements, GridElement{"dash-postsperday",strconv.Itoa(postCount) + " posts / " + postInterval,6,"grid_stat " + postColour,"","","The number of new posts over the last 24 hours"})
|
||||
gridElements = append(gridElements, GridElement{"dash-topicsperday",strconv.Itoa(topicCount) + " topics / " + topicInterval,7,"grid_stat " + topicColour,"","","The number of new topics over the last 24 hours"})
|
||||
gridElements = append(gridElements, GridElement{"dash-totonlineperday","20 online / day",8,"grid_stat stat_disabled","","","Coming Soon!"/*"The people online over the last 24 hours"*/})
|
||||
|
||||
gridElements = append(gridElements, GridElement{"dash-searches","8 searches / week",9,"grid_stat stat_disabled","","","Coming Soon!"/*"The number of searches over the last 7 days"*/})
|
||||
gridElements = append(gridElements, GridElement{"dash-newusers",strconv.Itoa(newUserCount) + " new users / " + newUserInterval,10,"grid_stat","","","The number of new users over the last 7 days"})
|
||||
gridElements = append(gridElements, GridElement{"dash-reports",strconv.Itoa(reportCount) + " reports / " + reportInterval,11,"grid_stat","","","The number of reports over the last 7 days"})
|
||||
|
||||
gridElements = append(gridElements, GridElement{"dash-minperuser","2 minutes / user / week",12,"grid_stat stat_disabled","","","Coming Soon!"/*"The average number of number of minutes spent by each active user over the last 7 days"*/})
|
||||
gridElements = append(gridElements, GridElement{"dash-visitorsperweek","2 visitors / week",13,"grid_stat stat_disabled","","","Coming Soon!"/*"The number of unique visitors we've had over the last 7 days"*/})
|
||||
gridElements = append(gridElements, GridElement{"dash-postsperuser","5 posts / user / week",14,"grid_stat stat_disabled","","","Coming Soon!"/*"The average number of posts made by each active user over the past week"*/})
|
||||
|
||||
pi := PanelDashboardPage{"Control Panel Dashboard",user,noticeList,gridElements,nil}
|
||||
templates.ExecuteTemplate(w,"panel-dashboard.html",pi)
|
||||
}
|
||||
|
@ -388,7 +415,7 @@ func route_panel_forums_edit_submit(w http.ResponseWriter, r *http.Request, sfid
|
|||
if is_js == "0" {
|
||||
http.Redirect(w,r,"/panel/forums/",http.StatusSeeOther)
|
||||
} else {
|
||||
fmt.Fprintf(w,`{"success":"1"}`)
|
||||
w.Write(success_json_bytes)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -73,6 +73,66 @@ function load_alerts(menu_alerts)
|
|||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
function SplitN(data,ch,n) {
|
||||
var out = []
|
||||
if(data.length == 0) {
|
||||
return out
|
||||
}
|
||||
|
||||
var lastIndex = 0
|
||||
var j = 0
|
||||
var lastN = 1
|
||||
for(var i = 0; i < data.length; i++) {
|
||||
if(data[i] == ch) {
|
||||
out[j++] = data.substring(lastIndex,i)
|
||||
lastIndex = i
|
||||
if(lastN == n) {
|
||||
break
|
||||
}
|
||||
lastN++
|
||||
}
|
||||
}
|
||||
if(data.length > lastIndex) {
|
||||
out[out.length - 1] += data.substring(lastIndex)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
if(window["WebSocket"]) {
|
||||
conn = new WebSocket("ws://" + document.location.host + "/ws/")
|
||||
conn.onopen = function() {
|
||||
conn.send("page " + document.location.pathname + '\r')
|
||||
}
|
||||
conn.onclose = function() {
|
||||
conn = false
|
||||
}
|
||||
conn.onmessage = function(event) {
|
||||
//console.log("WS_Message:")
|
||||
//console.log(event.data)
|
||||
var messages = event.data.split('\r')
|
||||
for(var i = 0; i < messages.length; i++) {
|
||||
//console.log("Message:")
|
||||
//console.log(messages[i])
|
||||
if(messages[i].startsWith("set ")) {
|
||||
//msgblocks = messages[i].split(' ',3)
|
||||
msgblocks = SplitN(messages[i]," ",3)
|
||||
if(msgblocks.length < 3) {
|
||||
continue
|
||||
}
|
||||
document.querySelector(msgblocks[1]).innerHTML = msgblocks[2]
|
||||
} else if(messages[i].startsWith("set-class ")) {
|
||||
msgblocks = SplitN(messages[i]," ",3)
|
||||
if(msgblocks.length < 3) {
|
||||
continue
|
||||
}
|
||||
document.querySelector(msgblocks[1]).className = msgblocks[2]
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
conn = false
|
||||
}
|
||||
|
||||
$(".open_edit").click(function(event){
|
||||
//console.log("Clicked on edit");
|
||||
event.preventDefault();
|
||||
|
|
31
routes.go
31
routes.go
|
@ -1,20 +1,22 @@
|
|||
/* Copyright Azareal 2016 - 2017 */
|
||||
package main
|
||||
|
||||
import "log"
|
||||
//import "fmt"
|
||||
import "strconv"
|
||||
import "bytes"
|
||||
import "regexp"
|
||||
import "strings"
|
||||
import "time"
|
||||
import "io"
|
||||
import "os"
|
||||
import "net"
|
||||
import "net/http"
|
||||
import "html"
|
||||
import "html/template"
|
||||
import "database/sql"
|
||||
import (
|
||||
"log"
|
||||
// "fmt"
|
||||
"strconv"
|
||||
"bytes"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
"io"
|
||||
"os"
|
||||
"net"
|
||||
"net/http"
|
||||
"html"
|
||||
"html/template"
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
import _ "github.com/go-sql-driver/mysql"
|
||||
import "golang.org/x/crypto/bcrypt"
|
||||
|
@ -22,6 +24,7 @@ import "golang.org/x/crypto/bcrypt"
|
|||
// A blank list to fill out that parameter in Page for routes which don't use it
|
||||
var tList []interface{}
|
||||
var nList []string
|
||||
var success_json_bytes []byte = []byte(`{"success":"1"}`)
|
||||
|
||||
// GET functions
|
||||
func route_static(w http.ResponseWriter, r *http.Request){
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
echo "Generating the dynamic code"
|
||||
go generate
|
||||
echo "Building Gosora"
|
||||
go build -o Gosora -tags no_ws
|
||||
echo "Running Gosora"
|
||||
./Gosora
|
|
@ -0,0 +1,27 @@
|
|||
@echo off
|
||||
echo Generating the dynamic code
|
||||
go generate
|
||||
if %errorlevel% neq 0 (
|
||||
pause
|
||||
exit /b %errorlevel%
|
||||
)
|
||||
|
||||
echo Building the router generator
|
||||
go build ./router_gen
|
||||
if %errorlevel% neq 0 (
|
||||
pause
|
||||
exit /b %errorlevel%
|
||||
)
|
||||
echo Running the router generator
|
||||
router_gen.exe
|
||||
|
||||
echo Building the executable
|
||||
go build -o gosora.exe -tags no_ws
|
||||
if %errorlevel% neq 0 (
|
||||
pause
|
||||
exit /b %errorlevel%
|
||||
)
|
||||
|
||||
echo Running Gosora
|
||||
gosora.exe
|
||||
pause
|
|
@ -3,7 +3,7 @@
|
|||
<div class="colstack_right">
|
||||
<div class="colstack_grid">
|
||||
{{range .GridItems}}
|
||||
<div class="grid_item {{.Class}}" title="{{.Note}}" style="{{if .TextColour}}color: {{.TextColour}};{{end}}
|
||||
<div id="{{.ID}}" class="grid_item {{.Class}}" title="{{.Note}}" style="{{if .TextColour}}color: {{.TextColour}};{{end}}
|
||||
{{if .Background}}background-color: {{.Background}};{{end}}">{{.Body}}</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
|
|
@ -899,9 +899,7 @@ blockquote p
|
|||
.notice:first-child { display: inline-block; }
|
||||
.getTopics { display: none; }
|
||||
|
||||
.userinfo {
|
||||
width: 70px;
|
||||
}
|
||||
.userinfo { width: 70px; }
|
||||
.userinfo .avatar_item {
|
||||
background-size: 64px;
|
||||
width: 64px;
|
||||
|
@ -913,6 +911,9 @@ blockquote p
|
|||
}
|
||||
.user_content { min-height: 80px !important; }
|
||||
.user_content.nobuttons { min-height: 103px !important; }
|
||||
|
||||
.colstack_grid { grid-template-columns: none; grid-gap: 8px; }
|
||||
.grid_istat { margin-bottom: 0px; }
|
||||
}
|
||||
@media (min-width: 800px)
|
||||
{
|
||||
|
|
|
@ -940,9 +940,7 @@ blockquote p
|
|||
.forumLastposter img { display: none; }
|
||||
.getTopics { display: none; }
|
||||
|
||||
.userinfo {
|
||||
width: 70px;
|
||||
}
|
||||
.userinfo { width: 70px; }
|
||||
.userinfo .avatar_item {
|
||||
background-size: 64px;
|
||||
width: 64px;
|
||||
|
@ -954,6 +952,9 @@ blockquote p
|
|||
}
|
||||
.user_content { min-height: 97.5px !important; }
|
||||
.user_content.nobuttons { min-height: 121px !important; }
|
||||
|
||||
.colstack_grid { grid-template-columns: none; grid-gap: 8px; }
|
||||
.grid_istat { margin-bottom: 0px; }
|
||||
}
|
||||
@media (min-width: 800px)
|
||||
{
|
||||
|
@ -1113,11 +1114,7 @@ blockquote p
|
|||
}
|
||||
|
||||
#main { width: 1690px; }
|
||||
.index_category
|
||||
{
|
||||
float: left;
|
||||
width: 835px;
|
||||
}
|
||||
.index_category { float: left; width: 835px; }
|
||||
.index_category:nth-child(even) { margin-left: 10px; }
|
||||
.index_category:nth-child(odd) { overflow: hidden; }
|
||||
.index_category:only-child { width: 100%; }
|
||||
|
|
|
@ -620,8 +620,11 @@ button.username
|
|||
.menu_right { padding-right: 5px; }
|
||||
.menu_create_topic { display: none; }
|
||||
.menu_alerts { padding-left: 4px; padding-right: 4px; }
|
||||
|
||||
.hide_on_mobile { display: none; }
|
||||
.prev_button, .next_button { top: auto;bottom: 5px; }
|
||||
.colstack_grid { grid-template-columns: none; grid-gap: 8px; }
|
||||
.grid_istat { margin-bottom: 0px; }
|
||||
}
|
||||
|
||||
@media (max-width: 470px) {
|
||||
|
|
|
@ -515,8 +515,11 @@ button.username
|
|||
.menu_right { padding-right: 5px; }
|
||||
.menu_create_topic { display: none;}
|
||||
.menu_alerts { padding-left: 4px; padding-right: 4px; }
|
||||
|
||||
.hide_on_mobile { display: none; }
|
||||
.prev_button, .next_button { top: auto; bottom: 5px; }
|
||||
.colstack_grid { grid-template-columns: none; grid-gap: 8px; }
|
||||
.grid_istat { margin-bottom: 0px; }
|
||||
}
|
||||
|
||||
@media (max-width: 470px) {
|
||||
|
|
|
@ -507,8 +507,11 @@ button.username
|
|||
.menu_right { padding-right: 5px; }
|
||||
.menu_create_topic { display: none;}
|
||||
.menu_alerts { padding-left: 4px; padding-right: 4px; }
|
||||
|
||||
.hide_on_mobile { display: none !important; }
|
||||
.prev_button, .next_button { top: auto; bottom: 5px; }
|
||||
.colstack_grid { grid-template-columns: none; grid-gap: 8px; }
|
||||
.grid_istat { margin-bottom: 0px; }
|
||||
}
|
||||
|
||||
@media (max-width: 470px) {
|
||||
|
|
|
@ -4,3 +4,5 @@ echo "Updating bcrypt"
|
|||
go get -u golang.org/x/crypto/bcrypt
|
||||
echo "Updating gopsutil"
|
||||
go get -u github.com/shirou/gopsutil
|
||||
echo "Updating Gorilla WebSockets"
|
||||
go get -u github.com/gorilla/websocket
|
||||
|
|
|
@ -27,5 +27,12 @@ if %errorlevel% neq 0 (
|
|||
exit /b %errorlevel%
|
||||
)
|
||||
|
||||
echo Updating Gorilla Websockets
|
||||
go get -u github.com/gorilla/websocket
|
||||
if %errorlevel% neq 0 (
|
||||
pause
|
||||
exit /b %errorlevel%
|
||||
)
|
||||
|
||||
echo The dependencies were successfully updated
|
||||
pause
|
24
user.go
24
user.go
|
@ -1,4 +1,5 @@
|
|||
package main
|
||||
|
||||
//import "fmt"
|
||||
import "sync"
|
||||
import "strings"
|
||||
|
@ -34,6 +35,7 @@ type User struct
|
|||
Level int
|
||||
Score int
|
||||
Last_IP string
|
||||
//WS_Conn interface{}
|
||||
}
|
||||
|
||||
type Email struct
|
||||
|
@ -53,6 +55,8 @@ type UserStore interface {
|
|||
Set(item *User) error
|
||||
Add(item *User) error
|
||||
AddUnsafe(item *User) error
|
||||
//SetConn(conn interface{}) error
|
||||
//GetConn() interface{}
|
||||
Remove(id int) error
|
||||
RemoveUnsafe(id int) error
|
||||
GetLength() int
|
||||
|
@ -109,7 +113,7 @@ func (sts *StaticUserStore) CascadeGet(id int) (*User, error) {
|
|||
user.Tag = groups[user.Group].Tag
|
||||
init_user_perms(user)
|
||||
if err == nil {
|
||||
sts.Add(user)
|
||||
sts.Set(user)
|
||||
}
|
||||
return user, err
|
||||
}
|
||||
|
@ -137,17 +141,18 @@ func (sts *StaticUserStore) Load(id int) error {
|
|||
|
||||
func (sts *StaticUserStore) Set(item *User) error {
|
||||
sts.Lock()
|
||||
_, ok := sts.items[item.ID]
|
||||
user, ok := sts.items[item.ID]
|
||||
if ok {
|
||||
sts.items[item.ID] = item
|
||||
sts.Unlock()
|
||||
*user = *item
|
||||
} else if sts.length >= sts.capacity {
|
||||
sts.Unlock()
|
||||
return ErrStoreCapacityOverflow
|
||||
} else {
|
||||
sts.items[item.ID] = item
|
||||
sts.Unlock()
|
||||
sts.length++
|
||||
}
|
||||
sts.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -171,6 +176,17 @@ func (sts *StaticUserStore) AddUnsafe(item *User) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
/*func (sts *StaticUserStore) SetConn(id int, conn interface{}) *User, error {
|
||||
sts.Lock()
|
||||
user, err := sts.CascadeGet(id)
|
||||
sts.Unlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user.WS_Conn = conn
|
||||
return user, nil
|
||||
}*/
|
||||
|
||||
func (sts *StaticUserStore) Remove(id int) error {
|
||||
sts.Lock()
|
||||
delete(sts.items,id)
|
||||
|
|
|
@ -0,0 +1,319 @@
|
|||
// +build !no_ws
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
import "sync"
|
||||
import "time"
|
||||
import "bytes"
|
||||
import "strconv"
|
||||
import "runtime"
|
||||
import "net/http"
|
||||
|
||||
import "github.com/gorilla/websocket"
|
||||
import "github.com/shirou/gopsutil/cpu"
|
||||
import "github.com/shirou/gopsutil/mem"
|
||||
|
||||
type WS_User struct
|
||||
{
|
||||
conn *websocket.Conn
|
||||
User *User
|
||||
}
|
||||
|
||||
type WS_Hub struct
|
||||
{
|
||||
online_users map[int]*WS_User
|
||||
online_guests map[*WS_User]bool
|
||||
guests sync.RWMutex
|
||||
users sync.RWMutex
|
||||
}
|
||||
|
||||
var ws_hub WS_Hub
|
||||
var ws_upgrader = websocket.Upgrader{ReadBufferSize:1024,WriteBufferSize:1024}
|
||||
|
||||
func init() {
|
||||
enable_websockets = true
|
||||
admin_stats_watchers = make(map[*WS_User]bool)
|
||||
ws_hub = WS_Hub{
|
||||
online_users: make(map[int]*WS_User),
|
||||
online_guests: make(map[*WS_User]bool),
|
||||
}
|
||||
}
|
||||
|
||||
func (hub *WS_Hub) GuestCount() int {
|
||||
defer hub.guests.RUnlock()
|
||||
hub.guests.RLock()
|
||||
return len(hub.online_guests)
|
||||
}
|
||||
|
||||
func (hub *WS_Hub) UserCount() int {
|
||||
defer hub.users.RUnlock()
|
||||
hub.users.RLock()
|
||||
return len(hub.online_users)
|
||||
}
|
||||
|
||||
func route_websockets(w http.ResponseWriter, r *http.Request) {
|
||||
user, ok := SimpleSessionCheck(w,r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
conn, err := ws_upgrader.Upgrade(w,r,nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
userptr, err := users.CascadeGet(user.ID)
|
||||
if err != nil && err != ErrStoreCapacityOverflow {
|
||||
return
|
||||
}
|
||||
|
||||
ws_user := &WS_User{conn,userptr}
|
||||
if user.ID == 0 {
|
||||
ws_hub.guests.Lock()
|
||||
ws_hub.online_guests[ws_user] = true
|
||||
ws_hub.guests.Unlock()
|
||||
} else {
|
||||
ws_hub.users.Lock()
|
||||
ws_hub.online_users[user.ID] = ws_user
|
||||
ws_hub.users.Unlock()
|
||||
}
|
||||
|
||||
//conn.SetReadLimit(/* put the max request size from earlier here? */)
|
||||
//conn.SetReadDeadline(time.Now().Add(60 * time.Second))
|
||||
var current_page []byte
|
||||
for {
|
||||
_, message, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
if user.ID == 0 {
|
||||
ws_hub.guests.Lock()
|
||||
delete(ws_hub.online_guests,ws_user)
|
||||
ws_hub.guests.Unlock()
|
||||
} else {
|
||||
ws_hub.users.Lock()
|
||||
delete(ws_hub.online_users,user.ID)
|
||||
ws_hub.users.Unlock()
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
//fmt.Println("Message",message)
|
||||
//fmt.Println("Message",string(message))
|
||||
messages := bytes.Split(message,[]byte("\r"))
|
||||
for _, msg := range messages {
|
||||
//fmt.Println("Submessage",msg)
|
||||
//fmt.Println("Submessage",string(msg))
|
||||
if bytes.HasPrefix(msg,[]byte("page ")) {
|
||||
msgblocks := bytes.SplitN(msg,[]byte(" "),2)
|
||||
if len(msgblocks) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
if !bytes.Equal(msgblocks[1],current_page) {
|
||||
ws_leave_page(ws_user, current_page)
|
||||
current_page = msgblocks[1]
|
||||
//fmt.Println("Current Page: ",current_page)
|
||||
//fmt.Println("Current Page: ",string(current_page))
|
||||
ws_page_responses(ws_user, current_page)
|
||||
}
|
||||
}
|
||||
/*if bytes.Equal(message,[]byte(`start-view`)) {
|
||||
|
||||
} else if bytes.Equal(message,[]byte(`end-view`)) {
|
||||
|
||||
}*/
|
||||
}
|
||||
}
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
func ws_page_responses(ws_user *WS_User, page []byte) {
|
||||
switch(string(page)) {
|
||||
case "/panel/":
|
||||
//fmt.Println("/panel/ WS Route")
|
||||
w, err := ws_user.conn.NextWriter(websocket.TextMessage)
|
||||
if err != nil {
|
||||
//fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(ws_hub.online_users)
|
||||
uonline := ws_hub.UserCount()
|
||||
gonline := ws_hub.GuestCount()
|
||||
totonline := uonline + gonline
|
||||
|
||||
w.Write([]byte("set #dash-totonline " + strconv.Itoa(totonline) + " online\r"))
|
||||
w.Write([]byte("set #dash-gonline " + strconv.Itoa(gonline) + " guests online\r"))
|
||||
w.Write([]byte("set #dash-uonline " + strconv.Itoa(uonline) + " users online\r"))
|
||||
w.Close()
|
||||
|
||||
// Listen for changes and inform the admins...
|
||||
admin_stats_mutex.Lock()
|
||||
watchers := len(admin_stats_watchers)
|
||||
admin_stats_watchers[ws_user] = true
|
||||
if watchers == 0 {
|
||||
go admin_stats_ticker()
|
||||
}
|
||||
admin_stats_mutex.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func ws_leave_page(ws_user *WS_User, page []byte) {
|
||||
switch(string(page)) {
|
||||
case "/panel/":
|
||||
admin_stats_mutex.Lock()
|
||||
delete(admin_stats_watchers,ws_user)
|
||||
admin_stats_mutex.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
var admin_stats_watchers map[*WS_User]bool
|
||||
var admin_stats_mutex sync.RWMutex
|
||||
func admin_stats_ticker() {
|
||||
time.Sleep(time.Second)
|
||||
|
||||
var last_uonline int = -1
|
||||
var last_gonline int = -1
|
||||
var last_totonline int = -1
|
||||
var last_cpu_perc int = -1
|
||||
var last_available_ram int64 = -1
|
||||
var no_stat_updates bool = false
|
||||
|
||||
var onlineColour, onlineGuestsColour, onlineUsersColour, cpustr, cpuColour, ramstr, ramColour string
|
||||
var cpuerr, ramerr error
|
||||
var memres *mem.VirtualMemoryStat
|
||||
var cpu_perc []float64
|
||||
|
||||
AdminStatLoop:
|
||||
for {
|
||||
//fmt.Println("tick tock")
|
||||
admin_stats_mutex.RLock()
|
||||
watch_count := len(admin_stats_watchers)
|
||||
admin_stats_mutex.RUnlock()
|
||||
if watch_count == 0 {
|
||||
break AdminStatLoop
|
||||
}
|
||||
|
||||
cpu_perc, cpuerr = cpu.Percent(time.Duration(time.Second),true)
|
||||
memres, ramerr = mem.VirtualMemory()
|
||||
uonline := ws_hub.UserCount()
|
||||
gonline := ws_hub.GuestCount()
|
||||
totonline := uonline + gonline
|
||||
|
||||
// It's far more likely that the CPU Usage will change than the other stats, so we'll optimise them seperately...
|
||||
no_stat_updates = (uonline == last_uonline && gonline == last_gonline && totonline == last_totonline)
|
||||
if no_stat_updates && int(cpu_perc[0]) == last_cpu_perc && last_available_ram == int64(memres.Available) {
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
if !no_stat_updates {
|
||||
if totonline > 10 {
|
||||
onlineColour = "stat_green"
|
||||
} else if totonline > 3 {
|
||||
onlineColour = "stat_orange"
|
||||
} else {
|
||||
onlineColour = "stat_red"
|
||||
}
|
||||
|
||||
if gonline > 10 {
|
||||
onlineGuestsColour = "stat_green"
|
||||
} else if gonline > 1 {
|
||||
onlineGuestsColour = "stat_orange"
|
||||
} else {
|
||||
onlineGuestsColour = "stat_red"
|
||||
}
|
||||
|
||||
if uonline > 5 {
|
||||
onlineUsersColour = "stat_green"
|
||||
} else if uonline > 1 {
|
||||
onlineUsersColour = "stat_orange"
|
||||
} else {
|
||||
onlineUsersColour = "stat_red"
|
||||
}
|
||||
}
|
||||
|
||||
if cpuerr != nil {
|
||||
cpustr = "Unknown"
|
||||
} else {
|
||||
calcperc := int(cpu_perc[0]) / runtime.NumCPU()
|
||||
cpustr = strconv.Itoa(calcperc)
|
||||
if calcperc < 30 {
|
||||
cpuColour = "stat_green"
|
||||
} else if calcperc < 75 {
|
||||
cpuColour = "stat_orange"
|
||||
} else {
|
||||
cpuColour = "stat_red"
|
||||
}
|
||||
}
|
||||
|
||||
if ramerr != nil {
|
||||
ramstr = "Unknown"
|
||||
} else {
|
||||
total_count, total_unit := convert_byte_unit(float64(memres.Total))
|
||||
used_count := convert_byte_in_unit(float64(memres.Total - memres.Available),total_unit)
|
||||
|
||||
// Round totals with .9s up, it's how most people see it anyway. Floats are notoriously imprecise, so do it off 0.85
|
||||
var totstr string
|
||||
if (total_count - float64(int(total_count))) > 0.85 {
|
||||
used_count += 1.0 - (total_count - float64(int(total_count)))
|
||||
totstr = strconv.Itoa(int(total_count) + 1)
|
||||
} else {
|
||||
totstr = fmt.Sprintf("%.1f",total_count)
|
||||
}
|
||||
|
||||
if used_count > total_count {
|
||||
used_count = total_count
|
||||
}
|
||||
ramstr = fmt.Sprintf("%.1f",used_count) + " / " + totstr + total_unit
|
||||
|
||||
ramperc := ((memres.Total - memres.Available) * 100) / memres.Total
|
||||
if ramperc < 50 {
|
||||
ramColour = "stat_green"
|
||||
} else if ramperc < 75 {
|
||||
ramColour = "stat_orange"
|
||||
} else {
|
||||
ramColour = "stat_red"
|
||||
}
|
||||
}
|
||||
|
||||
admin_stats_mutex.RLock()
|
||||
watchers := admin_stats_watchers
|
||||
admin_stats_mutex.RUnlock()
|
||||
|
||||
for watcher, _ := range watchers {
|
||||
w, err := watcher.conn.NextWriter(websocket.TextMessage)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
admin_stats_mutex.Lock()
|
||||
delete(admin_stats_watchers,watcher)
|
||||
admin_stats_mutex.Unlock()
|
||||
continue
|
||||
}
|
||||
|
||||
if !no_stat_updates {
|
||||
w.Write([]byte("set #dash-totonline " + strconv.Itoa(totonline) + " online\r"))
|
||||
w.Write([]byte("set #dash-gonline " + strconv.Itoa(gonline) + " guests online\r"))
|
||||
w.Write([]byte("set #dash-uonline " + strconv.Itoa(uonline) + " users online\r"))
|
||||
|
||||
w.Write([]byte("set-class #dash-totonline grid_stat " + onlineColour + "\r"))
|
||||
w.Write([]byte("set-class #dash-gonline grid_stat " + onlineGuestsColour + "\r"))
|
||||
w.Write([]byte("set-class #dash-uonline grid_stat " + onlineUsersColour + "\r"))
|
||||
}
|
||||
|
||||
w.Write([]byte("set #dash-cpu CPU: " + cpustr + "%\r"))
|
||||
w.Write([]byte("set-class #dash-cpu grid_istat " + cpuColour + "\r"))
|
||||
|
||||
w.Write([]byte("set #dash-ram RAM: " + ramstr + "\r"))
|
||||
w.Write([]byte("set-class #dash-ram grid_istat " + ramColour + "\r"))
|
||||
|
||||
w.Close()
|
||||
}
|
||||
|
||||
last_uonline = uonline
|
||||
last_gonline = gonline
|
||||
last_totonline = totonline
|
||||
last_cpu_perc = int(cpu_perc[0])
|
||||
last_available_ram = int64(memres.Available)
|
||||
|
||||
//time.Sleep(time.Second)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue