Added the Panel Debug page.

Added the mini stats for the various parts of the Control Panel in the Control Panel Menu.
Fixed a crash bug in the router.
The basic arithmetic template functions now work for the interpreted templates.
Tweaked the connection pool to hopefully ease the number of goroutines fighting over a database connection.
The Group Manager is now paginated.
panel.css is now pushed by HTTP/2 Push.
Fixed an issue with the padding on /forum/ and /topics/ for Shadow.
Fixed a bug in the login system forcefully overriding sessions on login.
All admin logins are now logged.
Refactored the client-side alert loading logic.
Added SimpleCount to the Query Builder.
This commit is contained in:
Azareal 2017-08-15 14:47:56 +01:00
parent 0955a6eda2
commit 3e4cfa8888
25 changed files with 480 additions and 93 deletions

View File

@ -6,6 +6,7 @@ import "database/sql"
var db *sql.DB var db *sql.DB
var db_version string var db_version string
var db_adapter string
var ErrNoRows = sql.ErrNoRows var ErrNoRows = sql.ErrNoRows

View File

@ -6,6 +6,9 @@ import "bytes"
import "net/http" import "net/http"
import "runtime/debug" import "runtime/debug"
//var notfound_count_per_second int
//var noperms_count_per_second int
var error_internal []byte var error_internal []byte
var error_notfound []byte var error_notfound []byte
func init_errors() error { func init_errors() error {

View File

@ -35,6 +35,8 @@ type ForumStore interface
//GetChildren(parentID int, parentType string) ([]*Forum,error) //GetChildren(parentID int, parentType string) ([]*Forum,error)
//GetFirstChild(parentID int, parentType string) (*Forum,error) //GetFirstChild(parentID int, parentType string) (*Forum,error)
CreateForum(forum_name string, forum_desc string, active bool, preset string) (int, error) CreateForum(forum_name string, forum_desc string, active bool, preset string) (int, error)
GetGlobalCount() int
} }
type StaticForumStore struct type StaticForumStore struct
@ -45,6 +47,7 @@ type StaticForumStore struct
get *sql.Stmt get *sql.Stmt
get_all *sql.Stmt get_all *sql.Stmt
forum_count *sql.Stmt
} }
func NewStaticForumStore() *StaticForumStore { func NewStaticForumStore() *StaticForumStore {
@ -56,9 +59,14 @@ func NewStaticForumStore() *StaticForumStore {
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
forum_count_stmt, err := qgen.Builder.SimpleCount("forums","name != ''","")
if err != nil {
log.Fatal(err)
}
return &StaticForumStore{ return &StaticForumStore{
get: get_stmt, get: get_stmt,
get_all: get_all_stmt, get_all: get_all_stmt,
forum_count: forum_count_stmt,
} }
} }
@ -322,6 +330,17 @@ func (sfs *StaticForumStore) fill_forum_id_gap(biggerID int, smallerID int) {
} }
} }
// Return the total number of forums
// TO-DO: Get the total count of forums in the forum store minus the blanked forums rather than doing a heavy query for this?
func (sfs *StaticForumStore) GetGlobalCount() int {
var fcount int
err := sfs.forum_count.QueryRow().Scan(&fcount)
if err != nil {
LogError(err)
}
return fcount
}
// TO-DO: Work on MapForumStore // TO-DO: Work on MapForumStore
// TO-DO: Work on SqlForumStore // TO-DO: Work on SqlForumStore

View File

@ -108,6 +108,7 @@ var delete_topic_stmt *sql.Stmt
var delete_profile_reply_stmt *sql.Stmt var delete_profile_reply_stmt *sql.Stmt
var delete_forum_perms_by_forum_stmt *sql.Stmt var delete_forum_perms_by_forum_stmt *sql.Stmt
var report_exists_stmt *sql.Stmt var report_exists_stmt *sql.Stmt
var group_count_stmt *sql.Stmt
var add_forum_perms_to_forum_admins_stmt *sql.Stmt var add_forum_perms_to_forum_admins_stmt *sql.Stmt
var add_forum_perms_to_forum_staff_stmt *sql.Stmt var add_forum_perms_to_forum_staff_stmt *sql.Stmt
var add_forum_perms_to_forum_members_stmt *sql.Stmt var add_forum_perms_to_forum_members_stmt *sql.Stmt
@ -724,6 +725,12 @@ func _gen_mysql() (err error) {
return err return err
} }
log.Print("Preparing group_count statement.")
group_count_stmt, err = db.Prepare("SELECT COUNT(*) AS `count` FROM `users_groups`")
if err != nil {
return err
}
log.Print("Preparing add_forum_perms_to_forum_admins statement.") log.Print("Preparing add_forum_perms_to_forum_admins statement.")
add_forum_perms_to_forum_admins_stmt, err = db.Prepare("INSERT INTO `forums_permissions`(`gid`,`fid`,`preset`,`permissions`) SELECT `gid`, ? AS `fid`, ? AS `preset`, ? AS `permissions` FROM `users_groups` WHERE `is_admin` = 1") add_forum_perms_to_forum_admins_stmt, err = db.Prepare("INSERT INTO `forums_permissions`(`gid`,`fid`,`preset`,`permissions`) SELECT `gid`, ? AS `fid`, ? AS `preset`, ? AS `permissions` FROM `users_groups` WHERE `is_admin` = 1")
if err != nil { if err != nil {

View File

@ -50,7 +50,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// default_route(w,req) // default_route(w,req)
// return // return
//} //}
if req.URL.Path[0] != '/' { if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' {
w.WriteHeader(405) w.WriteHeader(405)
w.Write([]byte("")) w.Write([]byte(""))
return return
@ -198,6 +198,9 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
case "/panel/logs/mod/": case "/panel/logs/mod/":
route_panel_logs_mod(w,req,user) route_panel_logs_mod(w,req,user)
return return
case "/panel/debug/":
route_panel_debug(w,req,user)
return
default: default:
route_panel(w,req,user) route_panel(w,req,user)
return return

View File

@ -145,6 +145,7 @@ site.Url = "` + site_url + `"
site.Port = "` + server_port + `" site.Port = "` + server_port + `"
site.EnableSsl = false site.EnableSsl = false
site.EnableEmails = false site.EnableEmails = false
site.HasProxy = false // Cloudflare counts as this, if it's sitting in the middle
config.SslPrivkey = "" config.SslPrivkey = ""
config.SslFullchain = "" config.SslFullchain = ""

61
main.go
View File

@ -210,15 +210,59 @@ func init_templates() {
} }
compile_templates() compile_templates()
// Filler functions for now... // TO-DO: Add support for 64-bit integers
filler_func := func(in interface{}, in2 interface{})interface{} { // TO-DO: Add support for floats
return 1
}
fmap := make(map[string]interface{}) fmap := make(map[string]interface{})
fmap["add"] = filler_func fmap["add"] = func(left interface{}, right interface{})interface{} {
fmap["subtract"] = filler_func var left_int int
fmap["multiply"] = filler_func var right_int int
fmap["divide"] = filler_func switch left := left.(type) {
case uint, uint8, uint16, int, int32: left_int = left.(int)
}
switch right := right.(type) {
case uint, uint8, uint16, int, int32: right_int = right.(int)
}
return left_int + right_int
}
fmap["subtract"] = func(left interface{}, right interface{})interface{} {
var left_int int
var right_int int
switch left := left.(type) {
case uint, uint8, uint16, int, int32: left_int = left.(int)
}
switch right := right.(type) {
case uint, uint8, uint16, int, int32: right_int = right.(int)
}
return left_int - right_int
}
fmap["multiply"] = func(left interface{}, right interface{})interface{} {
var left_int int
var right_int int
switch left := left.(type) {
case uint, uint8, uint16, int, int32: left_int = left.(int)
}
switch right := right.(type) {
case uint, uint8, uint16, int, int32: right_int = right.(int)
}
return left_int * right_int
}
fmap["divide"] = func(left interface{}, right interface{})interface{} {
var left_int int
var right_int int
switch left := left.(type) {
case uint, uint8, uint16, int, int32: left_int = left.(int)
}
switch right := right.(type) {
case uint, uint8, uint16, int, int32: right_int = right.(int)
}
if left_int == 0 || right_int == 0 {
return 0
}
return left_int / right_int
}
// The interpreted templates... // The interpreted templates...
if dev.DebugMode { if dev.DebugMode {
@ -377,6 +421,7 @@ func main(){
///router.HandleFunc("/panel/groups/edit/perms/submit/", route_panel_groups_edit_perms_submit) ///router.HandleFunc("/panel/groups/edit/perms/submit/", route_panel_groups_edit_perms_submit)
///router.HandleFunc("/panel/groups/create/", route_panel_groups_create_submit) ///router.HandleFunc("/panel/groups/create/", route_panel_groups_create_submit)
///router.HandleFunc("/panel/logs/mod/", route_panel_logs_mod) ///router.HandleFunc("/panel/logs/mod/", route_panel_logs_mod)
///router.HandleFunc("/panel/debug/", route_panel_debug)
///router.HandleFunc("/api/", route_api) ///router.HandleFunc("/api/", route_api)
//router.HandleFunc("/exit/", route_exit) //router.HandleFunc("/exit/", route_exit)

View File

@ -4,6 +4,7 @@
package main package main
import "log" import "log"
//import "time"
import "database/sql" import "database/sql"
import _ "github.com/go-sql-driver/mysql" import _ "github.com/go-sql-driver/mysql"
import "./query_gen/lib" import "./query_gen/lib"
@ -16,6 +17,10 @@ var todays_topic_count_stmt *sql.Stmt
var todays_report_count_stmt *sql.Stmt var todays_report_count_stmt *sql.Stmt
var todays_newuser_count_stmt *sql.Stmt var todays_newuser_count_stmt *sql.Stmt
func init() {
db_adapter = "mysql"
}
func _init_database() (err error) { func _init_database() (err error) {
var _dbpassword string var _dbpassword string
if(db_config.Password != ""){ if(db_config.Password != ""){
@ -39,6 +44,10 @@ func _init_database() (err error) {
// Set the number of max open connections // Set the number of max open connections
db.SetMaxOpenConns(64) db.SetMaxOpenConns(64)
db.SetMaxIdleConns(32)
// Only hold connections open for five seconds to avoid accumulating a large number of stale connections
//db.SetConnMaxLifetime(5 * time.Second)
// Build the generated prepared statements, we are going to slowly move the queries over to the query generator rather than writing them all by hand, this'll make it easier for us to implement database adapters for other databases like PostgreSQL, MSSQL, SQlite, etc. // Build the generated prepared statements, we are going to slowly move the queries over to the query generator rather than writing them all by hand, this'll make it easier for us to implement database adapters for other databases like PostgreSQL, MSSQL, SQlite, etc.
err = _gen_mysql() err = _gen_mysql()

View File

@ -106,6 +106,39 @@ type CreateTopicPage struct
ExtData ExtData ExtData ExtData
} }
type PanelStats struct
{
Users int
Groups int
Forums int
Settings int
Themes int
Reports int
}
type PanelPage struct
{
Title string
CurrentUser User
Header HeaderVars
Stats PanelStats
ItemList []interface{}
Something interface{}
}
type PanelGroupPage struct
{
Title string
CurrentUser User
Header HeaderVars
Stats PanelStats
ItemList []GroupAdmin
PageList []int
Page int
LastPage int
ExtData ExtData
}
type GridElement struct type GridElement struct
{ {
ID string ID string
@ -122,25 +155,28 @@ type PanelDashboardPage struct
Title string Title string
CurrentUser User CurrentUser User
Header HeaderVars Header HeaderVars
Stats PanelStats
GridItems []GridElement GridItems []GridElement
ExtData ExtData ExtData ExtData
} }
type ThemesPage struct type PanelThemesPage struct
{ {
Title string Title string
CurrentUser User CurrentUser User
Header HeaderVars Header HeaderVars
Stats PanelStats
PrimaryThemes []Theme PrimaryThemes []Theme
VariantThemes []Theme VariantThemes []Theme
ExtData ExtData ExtData ExtData
} }
type EditGroupPage struct type PanelEditGroupPage struct
{ {
Title string Title string
CurrentUser User CurrentUser User
Header HeaderVars Header HeaderVars
Stats PanelStats
ID int ID int
Name string Name string
Tag string Tag string
@ -155,11 +191,12 @@ type GroupForumPermPreset struct
Preset string Preset string
} }
type EditForumPage struct type PanelEditForumPage struct
{ {
Title string Title string
CurrentUser User CurrentUser User
Header HeaderVars Header HeaderVars
Stats PanelStats
ID int ID int
Name string Name string
Desc string Desc string
@ -182,11 +219,12 @@ type NameLangToggle struct
Toggle bool Toggle bool
} }
type EditGroupPermsPage struct type PanelEditGroupPermsPage struct
{ {
Title string Title string
CurrentUser User CurrentUser User
Header HeaderVars Header HeaderVars
Stats PanelStats
ID int ID int
Name string Name string
LocalPerms []NameLangToggle LocalPerms []NameLangToggle
@ -200,15 +238,28 @@ type Log struct {
DoneAt string DoneAt string
} }
type LogsPage struct type PanelLogsPage struct
{ {
Title string Title string
CurrentUser User CurrentUser User
Header HeaderVars Header HeaderVars
Stats PanelStats
Logs []Log Logs []Log
ExtData ExtData ExtData ExtData
} }
type PanelDebugPage struct
{
Title string
CurrentUser User
Header HeaderVars
Stats PanelStats
Uptime string
OpenConns int
DBAdapter string
ExtData ExtData
}
type PageSimple struct type PageSimple struct
{ {
Title string Title string
@ -747,3 +798,41 @@ func coerce_int_bytes(data []byte) (res int, length int) {
} }
return conv, i return conv, i
} }
// TO-DO: Write tests for this
func paginate(count int, per_page int, maxPages int) []int {
if count < per_page {
return []int{1}
}
var page int
var out []int
for current := 0; current < count; current += per_page {
page++
out = append(out,page)
if len(out) >= maxPages {
break
}
}
return out
}
// TO-DO: Write tests for this
func page_offset(count int, page int, perPage int) (int, int, int) {
var offset int
lastPage := int(count / perPage) + 1
if page > 1 {
offset = (perPage * page) - perPage
} else if page == -1 {
page = lastPage
offset = (perPage * page) - perPage
} else {
page = 1
}
// We don't want the offset to overflow the slices, if everything's in memory
if offset >= (count - 1) {
offset = 0
}
return offset, page, lastPage
}

View File

@ -15,7 +15,7 @@ import (
) )
func route_panel(w http.ResponseWriter, r *http.Request, user User){ func route_panel(w http.ResponseWriter, r *http.Request, user User){
headerVars, ok := PanelSessionCheck(w,r,&user) headerVars, stats, ok := PanelSessionCheck(w,r,&user)
if !ok { if !ok {
return return
} }
@ -168,7 +168,7 @@ func route_panel(w http.ResponseWriter, r *http.Request, user User){
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-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"*/}) 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,headerVars,gridElements,extData} pi := PanelDashboardPage{"Control Panel Dashboard",user,headerVars,stats,gridElements,extData}
if pre_render_hooks["pre_render_panel_dashboard"] != nil { if pre_render_hooks["pre_render_panel_dashboard"] != nil {
if run_pre_render_hook("pre_render_panel_dashboard", w, r, &user, &pi) { if run_pre_render_hook("pre_render_panel_dashboard", w, r, &user, &pi) {
return return
@ -181,7 +181,7 @@ func route_panel(w http.ResponseWriter, r *http.Request, user User){
} }
func route_panel_forums(w http.ResponseWriter, r *http.Request, user User){ func route_panel_forums(w http.ResponseWriter, r *http.Request, user User){
headerVars, ok := PanelSessionCheck(w,r,&user) headerVars, stats, ok := PanelSessionCheck(w,r,&user)
if !ok { if !ok {
return return
} }
@ -206,7 +206,7 @@ func route_panel_forums(w http.ResponseWriter, r *http.Request, user User){
forumList = append(forumList,fadmin) forumList = append(forumList,fadmin)
} }
} }
pi := Page{"Forum Manager",user,headerVars,forumList,nil} pi := PanelPage{"Forum Manager",user,headerVars,stats,forumList,nil}
if pre_render_hooks["pre_render_panel_forums"] != nil { if pre_render_hooks["pre_render_panel_forums"] != nil {
if run_pre_render_hook("pre_render_panel_forums", w, r, &user, &pi) { if run_pre_render_hook("pre_render_panel_forums", w, r, &user, &pi) {
return return
@ -254,7 +254,7 @@ func route_panel_forums_create_submit(w http.ResponseWriter, r *http.Request, us
} }
func route_panel_forums_delete(w http.ResponseWriter, r *http.Request, user User, sfid string){ func route_panel_forums_delete(w http.ResponseWriter, r *http.Request, user User, sfid string){
headerVars, ok := PanelSessionCheck(w,r,&user) headerVars, stats, ok := PanelSessionCheck(w,r,&user)
if !ok { if !ok {
return return
} }
@ -285,7 +285,7 @@ func route_panel_forums_delete(w http.ResponseWriter, r *http.Request, user User
confirm_msg := "Are you sure you want to delete the '" + forum.Name + "' forum?" confirm_msg := "Are you sure you want to delete the '" + forum.Name + "' forum?"
yousure := AreYouSure{"/panel/forums/delete/submit/" + strconv.Itoa(fid),confirm_msg} yousure := AreYouSure{"/panel/forums/delete/submit/" + strconv.Itoa(fid),confirm_msg}
pi := Page{"Delete Forum",user,headerVars,tList,yousure} pi := PanelPage{"Delete Forum",user,headerVars,stats,tList,yousure}
if pre_render_hooks["pre_render_panel_delete_forum"] != nil { if pre_render_hooks["pre_render_panel_delete_forum"] != nil {
if run_pre_render_hook("pre_render_panel_delete_forum", w, r, &user, &pi) { if run_pre_render_hook("pre_render_panel_delete_forum", w, r, &user, &pi) {
return return
@ -330,7 +330,7 @@ func route_panel_forums_delete_submit(w http.ResponseWriter, r *http.Request, us
} }
func route_panel_forums_edit(w http.ResponseWriter, r *http.Request, user User, sfid string) { func route_panel_forums_edit(w http.ResponseWriter, r *http.Request, user User, sfid string) {
headerVars, ok := PanelSessionCheck(w,r,&user) headerVars, stats, ok := PanelSessionCheck(w,r,&user)
if !ok { if !ok {
return return
} }
@ -367,7 +367,7 @@ func route_panel_forums_edit(w http.ResponseWriter, r *http.Request, user User,
gplist = append(gplist,GroupForumPermPreset{group,forum_perms_to_group_forum_preset(group.Forums[fid])}) gplist = append(gplist,GroupForumPermPreset{group,forum_perms_to_group_forum_preset(group.Forums[fid])})
} }
pi := EditForumPage{"Forum Editor",user,headerVars,forum.ID,forum.Name,forum.Desc,forum.Active,forum.Preset,gplist,extData} pi := PanelEditForumPage{"Forum Editor",user,headerVars,stats,forum.ID,forum.Name,forum.Desc,forum.Active,forum.Preset,gplist,extData}
if pre_render_hooks["pre_render_panel_edit_forum"] != nil { if pre_render_hooks["pre_render_panel_edit_forum"] != nil {
if run_pre_render_hook("pre_render_panel_edit_forum", w, r, &user, &pi) { if run_pre_render_hook("pre_render_panel_edit_forum", w, r, &user, &pi) {
return return
@ -548,7 +548,7 @@ func route_panel_forums_edit_perms_submit(w http.ResponseWriter, r *http.Request
} }
func route_panel_settings(w http.ResponseWriter, r *http.Request, user User){ func route_panel_settings(w http.ResponseWriter, r *http.Request, user User){
headerVars, ok := PanelSessionCheck(w,r,&user) headerVars, stats, ok := PanelSessionCheck(w,r,&user)
if !ok { if !ok {
return return
} }
@ -597,7 +597,7 @@ func route_panel_settings(w http.ResponseWriter, r *http.Request, user User){
return return
} }
pi := Page{"Setting Manager",user,headerVars,tList,settingList} pi := PanelPage{"Setting Manager",user,headerVars,stats,tList,settingList}
if pre_render_hooks["pre_render_panel_settings"] != nil { if pre_render_hooks["pre_render_panel_settings"] != nil {
if run_pre_render_hook("pre_render_panel_settings", w, r, &user, &pi) { if run_pre_render_hook("pre_render_panel_settings", w, r, &user, &pi) {
return return
@ -610,7 +610,7 @@ func route_panel_settings(w http.ResponseWriter, r *http.Request, user User){
} }
func route_panel_setting(w http.ResponseWriter, r *http.Request, user User, sname string){ func route_panel_setting(w http.ResponseWriter, r *http.Request, user User, sname string){
headerVars, ok := PanelSessionCheck(w,r,&user) headerVars, stats, ok := PanelSessionCheck(w,r,&user)
if !ok { if !ok {
return return
} }
@ -653,7 +653,7 @@ func route_panel_setting(w http.ResponseWriter, r *http.Request, user User, snam
} }
} }
pi := Page{"Edit Setting",user,headerVars,itemList,setting} pi := PanelPage{"Edit Setting",user,headerVars,stats,itemList,setting}
if pre_render_hooks["pre_render_panel_setting"] != nil { if pre_render_hooks["pre_render_panel_setting"] != nil {
if run_pre_render_hook("pre_render_panel_setting", w, r, &user, &pi) { if run_pre_render_hook("pre_render_panel_setting", w, r, &user, &pi) {
return return
@ -720,7 +720,7 @@ func route_panel_setting_edit(w http.ResponseWriter, r *http.Request, user User,
} }
func route_panel_plugins(w http.ResponseWriter, r *http.Request, user User){ func route_panel_plugins(w http.ResponseWriter, r *http.Request, user User){
headerVars, ok := PanelSessionCheck(w,r,&user) headerVars, stats, ok := PanelSessionCheck(w,r,&user)
if !ok { if !ok {
return return
} }
@ -736,7 +736,7 @@ func route_panel_plugins(w http.ResponseWriter, r *http.Request, user User){
pluginList = append(pluginList,plugin) pluginList = append(pluginList,plugin)
} }
pi := Page{"Plugin Manager",user,headerVars,pluginList,nil} pi := PanelPage{"Plugin Manager",user,headerVars,stats,pluginList,nil}
if pre_render_hooks["pre_render_panel_plugins"] != nil { if pre_render_hooks["pre_render_panel_plugins"] != nil {
if run_pre_render_hook("pre_render_panel_plugins", w, r, &user, &pi) { if run_pre_render_hook("pre_render_panel_plugins", w, r, &user, &pi) {
return return
@ -926,7 +926,6 @@ func route_panel_plugins_install(w http.ResponseWriter, r *http.Request, user Us
} }
} }
if has_plugin { if has_plugin {
_, err = update_plugin_install_stmt.Exec(1,uname) _, err = update_plugin_install_stmt.Exec(1,uname)
if err != nil { if err != nil {
@ -960,7 +959,7 @@ func route_panel_plugins_install(w http.ResponseWriter, r *http.Request, user Us
} }
func route_panel_users(w http.ResponseWriter, r *http.Request, user User){ func route_panel_users(w http.ResponseWriter, r *http.Request, user User){
headerVars, ok := PanelSessionCheck(w,r,&user) headerVars, stats, ok := PanelSessionCheck(w,r,&user)
if !ok { if !ok {
return return
} }
@ -1004,7 +1003,7 @@ func route_panel_users(w http.ResponseWriter, r *http.Request, user User){
return return
} }
pi := Page{"User Manager",user,headerVars,userList,nil} pi := PanelPage{"User Manager",user,headerVars,stats,userList,nil}
if pre_render_hooks["pre_render_panel_users"] != nil { if pre_render_hooks["pre_render_panel_users"] != nil {
if run_pre_render_hook("pre_render_panel_users", w, r, &user, &pi) { if run_pre_render_hook("pre_render_panel_users", w, r, &user, &pi) {
return return
@ -1017,7 +1016,7 @@ func route_panel_users(w http.ResponseWriter, r *http.Request, user User){
} }
func route_panel_users_edit(w http.ResponseWriter, r *http.Request, user User, suid string){ func route_panel_users_edit(w http.ResponseWriter, r *http.Request, user User, suid string){
headerVars, ok := PanelSessionCheck(w,r,&user) headerVars, stats, ok := PanelSessionCheck(w,r,&user)
if !ok { if !ok {
return return
} }
@ -1058,7 +1057,7 @@ func route_panel_users_edit(w http.ResponseWriter, r *http.Request, user User, s
groupList = append(groupList,group) groupList = append(groupList,group)
} }
pi := Page{"User Editor",user,headerVars,groupList,targetUser} pi := PanelPage{"User Editor",user,headerVars,stats,groupList,targetUser}
if pre_render_hooks["pre_render_panel_edit_user"] != nil { if pre_render_hooks["pre_render_panel_edit_user"] != nil {
if run_pre_render_hook("pre_render_panel_edit_user", w, r, &user, &pi) { if run_pre_render_hook("pre_render_panel_edit_user", w, r, &user, &pi) {
return return
@ -1166,13 +1165,25 @@ func route_panel_users_edit_submit(w http.ResponseWriter, r *http.Request, user
} }
func route_panel_groups(w http.ResponseWriter, r *http.Request, user User){ func route_panel_groups(w http.ResponseWriter, r *http.Request, user User){
headerVars, ok := PanelSessionCheck(w,r,&user) headerVars, stats, ok := PanelSessionCheck(w,r,&user)
if !ok { if !ok {
return return
} }
var groupList []interface{} page, _ := strconv.Atoi(r.FormValue("page"))
for _, group := range groups[1:] { perPage := 9
offset, page, lastPage := page_offset(stats.Groups, page, perPage)
// Skip the System group
offset++
var count int
var groupList []GroupAdmin
for _, group := range groups[offset:] {
if count == perPage {
break
}
var rank string var rank string
var rank_class string var rank_class string
var can_edit bool var can_edit bool
@ -1197,10 +1208,12 @@ func route_panel_groups(w http.ResponseWriter, r *http.Request, user User){
can_edit = user.Perms.EditGroup && (!group.Is_Admin || user.Perms.EditGroupAdmin) && (!group.Is_Mod || user.Perms.EditGroupSuperMod) can_edit = user.Perms.EditGroup && (!group.Is_Admin || user.Perms.EditGroupAdmin) && (!group.Is_Mod || user.Perms.EditGroupSuperMod)
groupList = append(groupList, GroupAdmin{group.ID,group.Name,rank,rank_class,can_edit,can_delete}) groupList = append(groupList, GroupAdmin{group.ID,group.Name,rank,rank_class,can_edit,can_delete})
count++
} }
//log.Printf("groupList: %+v\n", groupList) //log.Printf("groupList: %+v\n", groupList)
pi := Page{"Group Manager",user,headerVars,groupList,nil} pageList := paginate(stats.Groups, perPage, 5)
pi := PanelGroupPage{"Group Manager",user,headerVars,stats,groupList,pageList,page,lastPage,extData}
if pre_render_hooks["pre_render_panel_groups"] != nil { if pre_render_hooks["pre_render_panel_groups"] != nil {
if run_pre_render_hook("pre_render_panel_groups", w, r, &user, &pi) { if run_pre_render_hook("pre_render_panel_groups", w, r, &user, &pi) {
return return
@ -1214,7 +1227,7 @@ func route_panel_groups(w http.ResponseWriter, r *http.Request, user User){
} }
func route_panel_groups_edit(w http.ResponseWriter, r *http.Request, user User, sgid string){ func route_panel_groups_edit(w http.ResponseWriter, r *http.Request, user User, sgid string){
headerVars, ok := PanelSessionCheck(w,r,&user) headerVars, stats, ok := PanelSessionCheck(w,r,&user)
if !ok { if !ok {
return return
} }
@ -1260,7 +1273,7 @@ func route_panel_groups_edit(w http.ResponseWriter, r *http.Request, user User,
disable_rank := !user.Perms.EditGroupGlobalPerms || (group.ID == 6) disable_rank := !user.Perms.EditGroupGlobalPerms || (group.ID == 6)
pi := EditGroupPage{"Group Editor",user,headerVars,group.ID,group.Name,group.Tag,rank,disable_rank,extData} pi := PanelEditGroupPage{"Group Editor",user,headerVars,stats,group.ID,group.Name,group.Tag,rank,disable_rank,extData}
if pre_render_hooks["pre_render_panel_edit_group"] != nil { if pre_render_hooks["pre_render_panel_edit_group"] != nil {
if run_pre_render_hook("pre_render_panel_edit_group", w, r, &user, &pi) { if run_pre_render_hook("pre_render_panel_edit_group", w, r, &user, &pi) {
return return
@ -1273,7 +1286,7 @@ func route_panel_groups_edit(w http.ResponseWriter, r *http.Request, user User,
} }
func route_panel_groups_edit_perms(w http.ResponseWriter, r *http.Request, user User, sgid string){ func route_panel_groups_edit_perms(w http.ResponseWriter, r *http.Request, user User, sgid string){
headerVars, ok := PanelSessionCheck(w,r,&user) headerVars, stats, ok := PanelSessionCheck(w,r,&user)
if !ok { if !ok {
return return
} }
@ -1338,7 +1351,7 @@ func route_panel_groups_edit_perms(w http.ResponseWriter, r *http.Request, user
globalPerms = append(globalPerms, NameLangToggle{"ViewAdminLogs",GetGlobalPermPhrase("ViewAdminLogs"),group.Perms.ViewAdminLogs}) globalPerms = append(globalPerms, NameLangToggle{"ViewAdminLogs",GetGlobalPermPhrase("ViewAdminLogs"),group.Perms.ViewAdminLogs})
globalPerms = append(globalPerms, NameLangToggle{"ViewIPs",GetGlobalPermPhrase("ViewIPs"),group.Perms.ViewIPs}) globalPerms = append(globalPerms, NameLangToggle{"ViewIPs",GetGlobalPermPhrase("ViewIPs"),group.Perms.ViewIPs})
pi := EditGroupPermsPage{"Group Editor",user,headerVars,group.ID,group.Name,localPerms,globalPerms,extData} pi := PanelEditGroupPermsPage{"Group Editor",user,headerVars,stats,group.ID,group.Name,localPerms,globalPerms,extData}
if pre_render_hooks["pre_render_panel_edit_group_perms"] != nil { if pre_render_hooks["pre_render_panel_edit_group_perms"] != nil {
if run_pre_render_hook("pre_render_panel_edit_group_perms", w, r, &user, &pi) { if run_pre_render_hook("pre_render_panel_edit_group_perms", w, r, &user, &pi) {
return return
@ -1609,7 +1622,7 @@ func route_panel_groups_create_submit(w http.ResponseWriter, r *http.Request, us
} }
func route_panel_themes(w http.ResponseWriter, r *http.Request, user User){ func route_panel_themes(w http.ResponseWriter, r *http.Request, user User){
headerVars, ok := PanelSessionCheck(w,r,&user) headerVars, stats, ok := PanelSessionCheck(w,r,&user)
if !ok { if !ok {
return return
} }
@ -1631,7 +1644,7 @@ func route_panel_themes(w http.ResponseWriter, r *http.Request, user User){
} }
pi := ThemesPage{"Theme Manager",user,headerVars,pThemeList,vThemeList,extData} pi := PanelThemesPage{"Theme Manager",user,headerVars,stats,pThemeList,vThemeList,extData}
if pre_render_hooks["pre_render_panel_themes"] != nil { if pre_render_hooks["pre_render_panel_themes"] != nil {
if run_pre_render_hook("pre_render_panel_themes", w, r, &user, &pi) { if run_pre_render_hook("pre_render_panel_themes", w, r, &user, &pi) {
return return
@ -1722,7 +1735,7 @@ func route_panel_themes_default(w http.ResponseWriter, r *http.Request, user Use
} }
func route_panel_logs_mod(w http.ResponseWriter, r *http.Request, user User){ func route_panel_logs_mod(w http.ResponseWriter, r *http.Request, user User){
headerVars, ok := PanelSessionCheck(w,r,&user) headerVars, stats, ok := PanelSessionCheck(w,r,&user)
if !ok { if !ok {
return return
} }
@ -1813,7 +1826,7 @@ func route_panel_logs_mod(w http.ResponseWriter, r *http.Request, user User){
return return
} }
pi := LogsPage{"Moderation Logs",user,headerVars,logs,extData} pi := PanelLogsPage{"Moderation Logs",user,headerVars,stats,logs,extData}
if pre_render_hooks["pre_render_panel_mod_log"] != nil { if pre_render_hooks["pre_render_panel_mod_log"] != nil {
if run_pre_render_hook("pre_render_panel_mod_log", w, r, &user, &pi) { if run_pre_render_hook("pre_render_panel_mod_log", w, r, &user, &pi) {
return return
@ -1821,6 +1834,28 @@ func route_panel_logs_mod(w http.ResponseWriter, r *http.Request, user User){
} }
err = templates.ExecuteTemplate(w,"panel-modlogs.html",pi) err = templates.ExecuteTemplate(w,"panel-modlogs.html",pi)
if err != nil { if err != nil {
log.Print(err) InternalError(err,w)
}
}
func route_panel_debug(w http.ResponseWriter, r *http.Request, user User) {
headerVars, stats, ok := PanelSessionCheck(w,r,&user)
if !ok {
return
}
if !user.Is_Admin {
NoPermissions(w,r,user)
return
}
uptime := "..."
db_stats := db.Stats()
open_conn_count := db_stats.OpenConnections
// Disk I/O?
pi := PanelDebugPage{"Debug",user,headerVars,stats,uptime,open_conn_count,db_adapter,extData}
err := templates.ExecuteTemplate(w,"panel-debug.html",pi)
if err != nil {
InternalError(err,w)
} }
} }

View File

@ -5,6 +5,7 @@
package main package main
import "strings" import "strings"
//import "time"
import "database/sql" import "database/sql"
import _ "github.com/lib/pq" import _ "github.com/lib/pq"
import "./query_gen/lib" import "./query_gen/lib"
@ -18,6 +19,10 @@ var todays_topic_count_stmt *sql.Stmt
var todays_report_count_stmt *sql.Stmt var todays_report_count_stmt *sql.Stmt
var todays_newuser_count_stmt *sql.Stmt var todays_newuser_count_stmt *sql.Stmt
func init() {
db_adapter = "pgsql"
}
func _init_database() (err error) { func _init_database() (err error) {
// TO-DO: Investigate connect_timeout to see what it does exactly and whether it's relevant to us // TO-DO: Investigate connect_timeout to see what it does exactly and whether it's relevant to us
var _dbpassword string var _dbpassword string
@ -40,6 +45,10 @@ func _init_database() (err error) {
// Set the number of max open connections. How many do we need? Might need to do some tests. // Set the number of max open connections. How many do we need? Might need to do some tests.
db.SetMaxOpenConns(64) db.SetMaxOpenConns(64)
db.SetMaxIdleConns(32)
// Only hold connections open for five seconds to avoid accumulating a large number of stale connections
//db.SetConnMaxLifetime(5 * time.Second)
err = _gen_pgsql() err = _gen_pgsql()
if err != nil { if err != nil {

View File

@ -13,14 +13,16 @@ function post_link(event)
function load_alerts(menu_alerts) function load_alerts(menu_alerts)
{ {
menu_alerts.find(".alert_counter").text(""); var alertListNode = menu_alerts.getElementsByClassName("alertList")[0];
var alertCounterNode = menu_alerts.getElementsByClassName("alert_counter")[0];
alertCounterNode.textContent = "";
$.ajax({ $.ajax({
type: 'get', type: 'get',
dataType: 'json', dataType: 'json',
url:'/api/?action=get&module=alerts&format=json', url:'/api/?action=get&module=alerts&format=json',
success: function(data) { success: function(data) {
if("errmsg" in data) { if("errmsg" in data) {
menu_alerts.find(".alertList").html("<div class='alertItem'>"+data.errmsg+"</div>"); alertListNode.innerHTML = "<div class='alertItem'>"+data.errmsg+"</div>";
return; return;
} }
@ -55,8 +57,13 @@ function load_alerts(menu_alerts)
//menu_alerts.removeClass("hasAvatars"); //menu_alerts.removeClass("hasAvatars");
//if(anyAvatar) menu_alerts.addClass("hasAvatars"); //if(anyAvatar) menu_alerts.addClass("hasAvatars");
} }
menu_alerts.find(".alertList").html(alist); alertListNode.innerHTML = alist;
if(data.msgCount != 0) menu_alerts.find(".alert_counter").text(data.msgCount); if(data.msgCount != 0) {
alertCounterNode.textContent = data.msgCount;
menu_alerts.classList.add("has_alerts");
} else {
menu_alerts.classList.remove("has_alerts");
}
alertCount = data.msgCount; alertCount = data.msgCount;
}, },
error: function(magic,theStatus,error) { error: function(magic,theStatus,error) {
@ -69,31 +76,31 @@ function load_alerts(menu_alerts)
console.log(magic.responseText); console.log(magic.responseText);
console.log(err); console.log(err);
} }
menu_alerts.find(".alertList").html("<div class='alertItem'>"+errtxt+"</div>"); alertListNode.innerHTML = "<div class='alertItem'>"+errtxt+"</div>";
} }
}); });
} }
$(document).ready(function(){ function SplitN(data,ch,n) {
function SplitN(data,ch,n) { var out = [];
var out = []; if(data.length == 0) return out;
if(data.length == 0) return out;
var lastIndex = 0; var lastIndex = 0;
var j = 0; var j = 0;
var lastN = 1; var lastN = 1;
for(var i = 0; i < data.length; i++) { for(var i = 0; i < data.length; i++) {
if(data[i] == ch) { if(data[i] == ch) {
out[j++] = data.substring(lastIndex,i); out[j++] = data.substring(lastIndex,i);
lastIndex = i; lastIndex = i;
if(lastN == n) break; if(lastN == n) break;
lastN++; lastN++;
}
} }
if(data.length > lastIndex) out[out.length - 1] += data.substring(lastIndex);
return out;
} }
if(data.length > lastIndex) out[out.length - 1] += data.substring(lastIndex);
return out;
}
$(document).ready(function(){
if(window["WebSocket"]) { if(window["WebSocket"]) {
if(window.location.protocol == "https:") if(window.location.protocol == "https:")
conn = new WebSocket("wss://" + document.location.host + "/ws/"); conn = new WebSocket("wss://" + document.location.host + "/ws/");
@ -342,14 +349,15 @@ $(document).ready(function(){
} }
}); });
$(".menu_alerts").ready(function(){ var alert_menu_list = document.getElementsByClassName("menu_alerts");
load_alerts($(this)); for(var i = 0; i < alert_menu_list.length; i++) {
}); load_alerts(alert_menu_list[i]);
}
$(".menu_alerts").click(function(event) { $(".menu_alerts").click(function(event) {
event.stopPropagation(); event.stopPropagation();
if($(this).hasClass("selectedAlert")) return; if($(this).hasClass("selectedAlert")) return;
if(!conn) load_alerts($(this)); if(!conn) load_alerts(this);
this.className += " selectedAlert"; this.className += " selectedAlert";
document.getElementById("back").className += " alertActive" document.getElementById("back").className += " alertActive"
}); });

View File

@ -1,7 +1,7 @@
/* WIP Under Construction */ /* WIP Under Construction */
package qgen package qgen
//import "fmt" //import "log"
import "database/sql" import "database/sql"
var Builder *builder var Builder *builder
@ -38,12 +38,21 @@ func (build *builder) SimpleSelect(table string, columns string, where string, o
return build.conn.Prepare(res) return build.conn.Prepare(res)
} }
func (build *builder) SimpleCount(table string, where string, limit string) (stmt *sql.Stmt, err error) {
res, err := build.adapter.SimpleCount("_builder", table, where, limit)
if err != nil {
return stmt, err
}
//log.Print("res",res)
return build.conn.Prepare(res)
}
func (build *builder) SimpleLeftJoin(table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (stmt *sql.Stmt, err error) { func (build *builder) SimpleLeftJoin(table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (stmt *sql.Stmt, err error) {
res, err := build.adapter.SimpleLeftJoin("_builder", table1, table2, columns, joiners, where, orderby, limit) res, err := build.adapter.SimpleLeftJoin("_builder", table1, table2, columns, joiners, where, orderby, limit)
if err != nil { if err != nil {
return stmt, err return stmt, err
} }
//fmt.Println("res",res) //log.Print("res",res)
return build.conn.Prepare(res) return build.conn.Prepare(res)
} }
@ -52,7 +61,7 @@ func (build *builder) SimpleInnerJoin(table1 string, table2 string, columns stri
if err != nil { if err != nil {
return stmt, err return stmt, err
} }
//fmt.Println("res",res) //log.Print("res",res)
return build.conn.Prepare(res) return build.conn.Prepare(res)
} }

View File

@ -393,6 +393,8 @@ func write_deletes(adapter qgen.DB_Adapter) error {
func write_simple_counts(adapter qgen.DB_Adapter) error { func write_simple_counts(adapter qgen.DB_Adapter) error {
adapter.SimpleCount("report_exists","topics","data = ? AND data != '' AND parentID = 1","") adapter.SimpleCount("report_exists","topics","data = ? AND data != '' AND parentID = 1","")
adapter.SimpleCount("group_count","users_groups","","")
return nil return nil
} }

View File

@ -31,7 +31,7 @@ func (router *Router) HandleFunc(pattern string, handle func(http.ResponseWriter
} }
func (router *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (router *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if req.URL.Path[0] != '/' { if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' {
w.WriteHeader(405) w.WriteHeader(405)
w.Write([]byte("")) w.Write([]byte(""))
return return

View File

@ -127,7 +127,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// default_route(w,req) // default_route(w,req)
// return // return
//} //}
if req.URL.Path[0] != '/' { if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' {
w.WriteHeader(405) w.WriteHeader(405)
w.Write([]byte("")) w.Write([]byte(""))
return return

View File

@ -75,5 +75,6 @@ func routes() {
Route{"route_panel_groups_create_submit","/panel/groups/create/","",[]string{}}, Route{"route_panel_groups_create_submit","/panel/groups/create/","",[]string{}},
Route{"route_panel_logs_mod","/panel/logs/mod/","",[]string{}}, Route{"route_panel_logs_mod","/panel/logs/mod/","",[]string{}},
Route{"route_panel_debug","/panel/debug/","",[]string{}},
) )
} }

View File

@ -62,6 +62,9 @@ func route_static(w http.ResponseWriter, r *http.Request){
if strings.Contains(r.Header.Get("Accept-Encoding"),"gzip") { if strings.Contains(r.Header.Get("Accept-Encoding"),"gzip") {
h.Set("Content-Encoding","gzip") h.Set("Content-Encoding","gzip")
h.Set("Content-Length", strconv.FormatInt(file.GzipLength, 10)) h.Set("Content-Length", strconv.FormatInt(file.GzipLength, 10))
if site.HasProxy {
h.Set("Vary","Accept-Encoding")
}
io.Copy(w, bytes.NewReader(file.GzipData)) // Use w.Write instead? io.Copy(w, bytes.NewReader(file.GzipData)) // Use w.Write instead?
} else { } else {
h.Set("Content-Length", strconv.FormatInt(file.Length, 10)) // Avoid doing a type conversion every time? h.Set("Content-Length", strconv.FormatInt(file.Length, 10)) // Avoid doing a type conversion every time?
@ -139,6 +142,7 @@ func route_custom_page(w http.ResponseWriter, r *http.Request, user User){
} }
} }
// TO-DO: Paginate this
func route_topics(w http.ResponseWriter, r *http.Request, user User){ func route_topics(w http.ResponseWriter, r *http.Request, user User){
headerVars, ok := SessionCheck(w,r,&user) headerVars, ok := SessionCheck(w,r,&user)
if !ok { if !ok {
@ -1726,6 +1730,9 @@ func route_login(w http.ResponseWriter, r *http.Request, user User) {
templates.ExecuteTemplate(w,"login.html",pi) templates.ExecuteTemplate(w,"login.html",pi)
} }
// TO-DO: Log failed attempted logins?
// TO-DO: Lock IPS out if they have too many failed attempts?
// TO-DO: Log unusual countries in comparison to the country a user usually logs in from? Alert the user about this?
func route_login_submit(w http.ResponseWriter, r *http.Request, user User) { func route_login_submit(w http.ResponseWriter, r *http.Request, user User) {
if user.Loggedin { if user.Loggedin {
LocalError("You're already logged in.",w,r,user) LocalError("You're already logged in.",w,r,user)
@ -1743,6 +1750,13 @@ func route_login_submit(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
userPtr, err := users.CascadeGet(uid)
if err != nil {
LocalError("Bad account",w,r,user)
return
}
user = *userPtr
var session string var session string
if user.Session == "" { if user.Session == "" {
session, err = auth.CreateSession(uid) session, err = auth.CreateSession(uid)
@ -1755,6 +1769,15 @@ func route_login_submit(w http.ResponseWriter, r *http.Request, user User) {
} }
auth.SetCookies(w,uid,session) auth.SetCookies(w,uid,session)
if user.Is_Admin {
// Is this error check reundant? We already check for the error in PreRoute for the same IP
host, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
InternalError(err,w)
return
}
log.Print("#" + strconv.Itoa(uid) + " has logged in with IP " + host)
}
http.Redirect(w,r,"/",http.StatusSeeOther) http.Redirect(w,r,"/",http.StatusSeeOther)
} }

View File

@ -15,6 +15,7 @@ type Site struct
Port string Port string
EnableSsl bool EnableSsl bool
EnableEmails bool EnableEmails bool
HasProxy bool
} }
type DB_Config struct type DB_Config struct

View File

@ -0,0 +1,17 @@
{{template "header.html" . }}
{{template "panel-menu.html" . }}
<div id="panel_dashboard_right" class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem"><a>Debug</a></div>
</div>
<div id="panel_debug" class="colstack_grid">
<div class="grid_item grid_stat">Uptime</div>
<div class="grid_item grid_stat">Open DB Conns</div>
<div class="grid_item grid_stat">Adapter</div>
<div class="grid_item grid_stat">{{.Uptime}}</div>
<div class="grid_item grid_stat">{{.OpenConns}}</div>
<div class="grid_item grid_stat">{{.DBAdapter}}</div>
</div>
</div>
{{template "footer.html" . }}

View File

@ -18,6 +18,15 @@
</div> </div>
{{end}} {{end}}
</div> </div>
{{if gt .LastPage 1}}
<div class="pageset">
{{if gt .Page 1}}<div class="pageitem"><a href="?page={{subtract .Page 1}}">Prev</a></div>{{end}}
{{range .PageList}}
<div class="pageitem"><a href="?page={{.}}">{{.}}</a></div>
{{end}}
{{if ne .LastPage .Page}}<div class="pageitem"><a href="?page={{add .Page 1}}">Next</a></div>{{end}}
</div>
{{end}}
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><a>Create Group</a></div> <div class="rowitem"><a>Create Group</a></div>
</div> </div>

View File

@ -2,12 +2,36 @@
<div class="rowitem"><a href="/panel/">Control Panel</a></div> <div class="rowitem"><a href="/panel/">Control Panel</a></div>
</div> </div>
<div class="colstack_item rowmenu"> <div class="colstack_item rowmenu">
<div class="rowitem passive"><a href="/panel/users/">Users</a></div> <div class="rowitem passive">
<div class="rowitem passive"><a href="/panel/groups/">Groups</a></div> <a href="/panel/users/">Users</a> <a class="menu_stats" href="#">({{.Stats.Users}})</a>
{{if .CurrentUser.Perms.ManageForums}}<div class="rowitem passive"><a href="/panel/forums/">Forums</a></div>{{end}} </div>
{{if .CurrentUser.Perms.EditSettings}}<div class="rowitem passive"><a href="/panel/settings/">Settings</a></div>{{end}} <div class="rowitem passive">
{{if .CurrentUser.Perms.ManageThemes}}<div class="rowitem passive"><a href="/panel/themes/">Themes</a></div>{{end}} <a href="/panel/groups/">Groups</a> <a class="menu_stats" href="#">({{.Stats.Groups}})</a>
{{if .CurrentUser.Perms.ManagePlugins}}<div class="rowitem passive"><a href="/panel/plugins/">Plugins</a></div>{{end}} </div>
<div class="rowitem passive"><a href="/forum/1">Reports</a></div> {{if .CurrentUser.Perms.ManageForums}}<div class="rowitem passive">
<div class="rowitem passive"><a href="/panel/logs/mod/">Logs</a></div> <a href="/panel/forums/">Forums</a> <a class="menu_stats" href="#">({{.Stats.Forums}})</a>
</div>{{end}}
{{if .CurrentUser.Perms.EditSettings}}<div class="rowitem passive">
<a href="/panel/settings/">Settings</a> <a class="menu_stats" href="#">({{.Stats.Settings}})</a>
</div>{{end}}
{{if .CurrentUser.Perms.ManageThemes}}<div class="rowitem passive">
<a href="/panel/themes/">Themes</a> <a class="menu_stats" href="#">({{.Stats.Themes}})</a>
</div>{{end}}
<div class="rowitem passive">
<a href="/forum/1">Reports</a> <a class="menu_stats" href="#">({{.Stats.Reports}})</a>
</div>
</div>
<div class="colstack_item colstack_head">
<div class="rowitem"><a href="#">System</a></div>
</div>
<div class="colstack_item rowmenu">
{{if .CurrentUser.Perms.ManagePlugins}}<div class="rowitem passive">
<a href="/panel/plugins/">Plugins</a>
</div>{{end}}
<div class="rowitem passive">
<a href="/panel/logs/mod/">Logs</a>
</div>
{{if .CurrentUser.Is_Admin}}<div class="rowitem passive">
<a href="/panel/debug/">Debug</a>
</div>{{end}}
</div> </div>

View File

@ -330,6 +330,23 @@ textarea.large {
font-size: 13px; font-size: 13px;
padding: 10px; padding: 10px;
} }
.menu_stats {
font-size: 12px;
}
/* Mini paginators aka panel paginators */
.pageset {
margin-top: 4px;
clear: both;
height: 32px;
}
.pageitem {
background-color: rgb(61,61,61);
padding: 10px;
margin-right: 4px;
font-size: 13px;
float: left;
}
.rowlist.bgavatars .rowitem { .rowlist.bgavatars .rowitem {
background-repeat: no-repeat; background-repeat: no-repeat;
@ -429,6 +446,9 @@ input, select, textarea {
margin-left: 8px; margin-left: 8px;
width: 108px; width: 108px;
} }
.topic_list .rowitem:last-child {
margin-bottom: 10px;
}
/* Profiles */ /* Profiles */
#profile_left_lane { #profile_left_lane {

21
user.go
View File

@ -14,7 +14,7 @@ import (
var guest_user User = User{ID:0,Link:"#",Group:6,Perms:GuestPerms} var guest_user User = User{ID:0,Link:"#",Group:6,Perms:GuestPerms}
var PreRoute func(http.ResponseWriter, *http.Request) (User,bool) = _pre_route var PreRoute func(http.ResponseWriter, *http.Request) (User,bool) = _pre_route
var PanelSessionCheck func(http.ResponseWriter, *http.Request, *User) (HeaderVars,bool) = _panel_session_check var PanelSessionCheck func(http.ResponseWriter, *http.Request, *User) (HeaderVars,PanelStats,bool) = _panel_session_check
var SimplePanelSessionCheck func(http.ResponseWriter, *http.Request, *User) bool = _simple_panel_session_check var SimplePanelSessionCheck func(http.ResponseWriter, *http.Request, *User) bool = _simple_panel_session_check
var SimpleForumSessionCheck func(w http.ResponseWriter, r *http.Request, user *User, fid int) (success bool) = _simple_forum_session_check var SimpleForumSessionCheck func(w http.ResponseWriter, r *http.Request, user *User, fid int) (success bool) = _simple_forum_session_check
var ForumSessionCheck func(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerVars HeaderVars, success bool) = _forum_session_check var ForumSessionCheck func(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerVars HeaderVars, success bool) = _forum_session_check
@ -208,11 +208,11 @@ func _forum_session_check(w http.ResponseWriter, r *http.Request, user *User, fi
} }
// Even if they have the right permissions, the control panel is only open to supermods+. There are many areas without subpermissions which assume that the current user is a supermod+ and admins are extremely unlikely to give these permissions to someone who isn't at-least a supermod to begin with // Even if they have the right permissions, the control panel is only open to supermods+. There are many areas without subpermissions which assume that the current user is a supermod+ and admins are extremely unlikely to give these permissions to someone who isn't at-least a supermod to begin with
func _panel_session_check(w http.ResponseWriter, r *http.Request, user *User) (headerVars HeaderVars, success bool) { func _panel_session_check(w http.ResponseWriter, r *http.Request, user *User) (headerVars HeaderVars, stats PanelStats, success bool) {
headerVars.Site = site headerVars.Site = site
if !user.Is_Super_Mod { if !user.Is_Super_Mod {
NoPermissions(w,r,*user) NoPermissions(w,r,*user)
return headerVars, false return headerVars, stats, false
} }
headerVars.Stylesheets = append(headerVars.Stylesheets,"panel.css") headerVars.Stylesheets = append(headerVars.Stylesheets,"panel.css")
@ -233,9 +233,22 @@ func _panel_session_check(w http.ResponseWriter, r *http.Request, user *User) (h
} }
} }
err := group_count_stmt.QueryRow().Scan(&stats.Groups)
if err != nil {
InternalError(err,w)
return headerVars, stats, false
}
stats.Users = users.GetGlobalCount()
stats.Forums = fstore.GetGlobalCount() // TO-DO: Stop it from showing the blanked forums
stats.Settings = len(settings) // TO-DO: IS this racey?
stats.Themes = len(themes)
stats.Reports = 0 // TO-DO: Do the report count. Only show open threads?
pusher, ok := w.(http.Pusher) pusher, ok := w.(http.Pusher)
if ok { if ok {
pusher.Push("/static/main.css", nil) pusher.Push("/static/main.css", nil)
pusher.Push("/static/panel.css", nil)
pusher.Push("/static/global.js", nil) pusher.Push("/static/global.js", nil)
pusher.Push("/static/jquery-3.1.1.min.js", nil) pusher.Push("/static/jquery-3.1.1.min.js", nil)
// TO-DO: Push the theme CSS files // TO-DO: Push the theme CSS files
@ -243,7 +256,7 @@ func _panel_session_check(w http.ResponseWriter, r *http.Request, user *User) (h
// TO-DO: Push avatars? // TO-DO: Push avatars?
} }
return headerVars, true return headerVars, stats, true
} }
func _simple_panel_session_check(w http.ResponseWriter, r *http.Request, user *User) (success bool) { func _simple_panel_session_check(w http.ResponseWriter, r *http.Request, user *User) (success bool) {
if !user.Is_Super_Mod { if !user.Is_Super_Mod {

View File

@ -32,6 +32,7 @@ type UserStore interface {
CreateUser(username string, password string, email string, group int, active int) (int, error) CreateUser(username string, password string, email string, group int, active int) (int, error)
GetLength() int GetLength() int
GetCapacity() int GetCapacity() int
GetGlobalCount() int
} }
type MemoryUserStore struct { type MemoryUserStore struct {
@ -41,6 +42,7 @@ type MemoryUserStore struct {
get *sql.Stmt get *sql.Stmt
register *sql.Stmt register *sql.Stmt
username_exists *sql.Stmt username_exists *sql.Stmt
user_count *sql.Stmt
sync.RWMutex sync.RWMutex
} }
@ -62,12 +64,18 @@ func NewMemoryUserStore(capacity int) *MemoryUserStore {
log.Fatal(err) log.Fatal(err)
} }
user_count_stmt, err := qgen.Builder.SimpleCount("users","","")
if err != nil {
log.Fatal(err)
}
return &MemoryUserStore{ return &MemoryUserStore{
items:make(map[int]*User), items:make(map[int]*User),
capacity:capacity, capacity:capacity,
get:get_stmt, get:get_stmt,
register:register_stmt, register:register_stmt,
username_exists:username_exists_stmt, username_exists:username_exists_stmt,
user_count:user_count_stmt,
} }
} }
@ -352,10 +360,21 @@ func (sus *MemoryUserStore) GetCapacity() int {
return sus.capacity return sus.capacity
} }
// Return the total number of users registered on the forums
func (sus *MemoryUserStore) GetGlobalCount() int {
var ucount int
err := sus.user_count.QueryRow().Scan(&ucount)
if err != nil {
LogError(err)
}
return ucount
}
type SqlUserStore struct { type SqlUserStore struct {
get *sql.Stmt get *sql.Stmt
register *sql.Stmt register *sql.Stmt
username_exists *sql.Stmt username_exists *sql.Stmt
user_count *sql.Stmt
} }
func NewSqlUserStore() *SqlUserStore { func NewSqlUserStore() *SqlUserStore {
@ -376,10 +395,16 @@ func NewSqlUserStore() *SqlUserStore {
log.Fatal(err) log.Fatal(err)
} }
user_count_stmt, err := qgen.Builder.SimpleCount("users","","")
if err != nil {
log.Fatal(err)
}
return &SqlUserStore{ return &SqlUserStore{
get:get_stmt, get: get_stmt,
register:register_stmt, register: register_stmt,
username_exists:username_exists_stmt, username_exists: username_exists_stmt,
user_count: user_count_stmt,
} }
} }
@ -551,6 +576,20 @@ func (sus *SqlUserStore) GetCapacity() int {
return 0 return 0
} }
// Return the total number of users registered on the forums
func (sus *SqlUserStore) GetLength() int { func (sus *SqlUserStore) GetLength() int {
return 0 // Return the total number of users registered on the forums? var ucount int
err := sus.user_count.QueryRow().Scan(&ucount)
if err != nil {
LogError(err)
}
return ucount
}
func (sus *SqlUserStore) GetGlobalCount() int {
var ucount int
err := sus.user_count.QueryRow().Scan(&ucount)
if err != nil {
LogError(err)
}
return ucount
} }