diff --git a/README.md b/README.md index 3f497a68..6bade74c 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/build-gosora-linux-nowebsockets b/build-gosora-linux-nowebsockets new file mode 100644 index 00000000..32cb9ac1 --- /dev/null +++ b/build-gosora-linux-nowebsockets @@ -0,0 +1,4 @@ +echo "Building Gosora" +go build -o Gosora -tags no_ws +echo "Building the installer" +go build ./install diff --git a/build-nowebsockets.bat b/build-nowebsockets.bat new file mode 100644 index 00000000..c8299f2b --- /dev/null +++ b/build-nowebsockets.bat @@ -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 \ No newline at end of file diff --git a/images/panel-dashboard.png b/images/panel-dashboard.png new file mode 100644 index 00000000..8dd11c87 Binary files /dev/null and b/images/panel-dashboard.png differ diff --git a/install-gosora-linux b/install-gosora-linux index 9da702d0..82438ade 100644 --- a/install-gosora-linux +++ b/install-gosora-linux @@ -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 diff --git a/install.bat b/install.bat index 8b6bcbc2..83c69b14 100644 --- a/install.bat +++ b/install.bat @@ -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% diff --git a/main.go b/main.go index bca4c986..7e4eb621 100644 --- a/main.go +++ b/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 { diff --git a/mod_routes.go b/mod_routes.go index 27c31799..f168baeb 100644 --- a/mod_routes.go +++ b/mod_routes.go @@ -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) } } diff --git a/no_websockets.go b/no_websockets.go new file mode 100644 index 00000000..ea76e359 --- /dev/null +++ b/no_websockets.go @@ -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) { +} diff --git a/pages.go b/pages.go index a70f6db2..3576c8dc 100644 --- a/pages.go +++ b/pages.go @@ -79,6 +79,7 @@ type CreateTopicPage struct type GridElement struct { + ID string Body string Order int // For future use Class string diff --git a/panel_routes.go b/panel_routes.go index 557390e9..61a4cd49 100644 --- a/panel_routes.go +++ b/panel_routes.go @@ -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) } } diff --git a/public/global.js b/public/global.js index 42b76138..2e60e0a2 100644 --- a/public/global.js +++ b/public/global.js @@ -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(); diff --git a/routes.go b/routes.go index 17166957..8ab4b1de 100644 --- a/routes.go +++ b/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){ diff --git a/run-gosora-linux-nowebsockets b/run-gosora-linux-nowebsockets new file mode 100644 index 00000000..0360c35d --- /dev/null +++ b/run-gosora-linux-nowebsockets @@ -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 diff --git a/run-nowebsockets.bat b/run-nowebsockets.bat new file mode 100644 index 00000000..db6c13ff --- /dev/null +++ b/run-nowebsockets.bat @@ -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 \ No newline at end of file diff --git a/templates/panel-dashboard.html b/templates/panel-dashboard.html index 58ddfa20..557598d8 100644 --- a/templates/panel-dashboard.html +++ b/templates/panel-dashboard.html @@ -3,7 +3,7 @@