diff --git a/build-gosora-linux b/build-linux similarity index 100% rename from build-gosora-linux rename to build-linux diff --git a/build-gosora-linux-nowebsockets b/build-linux-nowebsockets similarity index 100% rename from build-gosora-linux-nowebsockets rename to build-linux-nowebsockets diff --git a/errors.go b/errors.go index b4b2e302..8b018b50 100644 --- a/errors.go +++ b/errors.go @@ -3,14 +3,18 @@ package main import "fmt" import "log" import "bytes" +import "sync" import "net/http" import "runtime/debug" +// TO-DO: Use the error_buffer variable to construct the system log in the Control Panel. Should we log errors caused by users too? Or just collect statistics on those or do nothing? Intercept recover()? Could we intercept the logger instead here? We might get too much information, if we intercept the logger, maybe make it part of the Debug page? +var error_buffer_mutex sync.RWMutex +var error_buffer []error //var notfound_count_per_second int //var noperms_count_per_second int - var error_internal []byte var error_notfound []byte + func init_errors() error { var b bytes.Buffer user := User{0,"guest","Guest","",0,false,false,false,false,false,false,GuestPerms,nil,"",false,"","","","","",0,0,"0.0.0.0.0"} @@ -34,6 +38,9 @@ func init_errors() error { func LogError(err error) { log.Print(err) debug.PrintStack() + error_buffer_mutex.Lock() + defer error_buffer_mutex.Unlock() + error_buffer = append(error_buffer,err) log.Fatal("") } @@ -41,6 +48,9 @@ func InternalError(err error, w http.ResponseWriter) { w.Write(error_internal) log.Print(err) debug.PrintStack() + error_buffer_mutex.Lock() + defer error_buffer_mutex.Unlock() + error_buffer = append(error_buffer,err) log.Fatal("") } @@ -53,12 +63,18 @@ func InternalErrorJSQ(err error, w http.ResponseWriter, r *http.Request, is_js s } log.Print(err) debug.PrintStack() + error_buffer_mutex.Lock() + defer error_buffer_mutex.Unlock() + error_buffer = append(error_buffer,err) log.Fatal("") } func InternalErrorJS(err error, w http.ResponseWriter, r *http.Request) { w.WriteHeader(500) w.Write([]byte(`{"errmsg":"A problem has occured in the system."}`)) + error_buffer_mutex.Lock() + defer error_buffer_mutex.Unlock() + error_buffer = append(error_buffer,err) log.Fatal(err) } diff --git a/gen_mysql.go b/gen_mysql.go index 1dd57bcb..e2f3b888 100644 --- a/gen_mysql.go +++ b/gen_mysql.go @@ -23,8 +23,10 @@ var get_themes_stmt *sql.Stmt var get_widgets_stmt *sql.Stmt var is_plugin_active_stmt *sql.Stmt var get_users_stmt *sql.Stmt +var get_users_offset_stmt *sql.Stmt var is_theme_default_stmt *sql.Stmt var get_modlogs_stmt *sql.Stmt +var get_modlogs_offset_stmt *sql.Stmt var get_reply_tid_stmt *sql.Stmt var get_topic_fid_stmt *sql.Stmt var get_user_reply_uid_stmt *sql.Stmt @@ -109,6 +111,7 @@ var delete_profile_reply_stmt *sql.Stmt var delete_forum_perms_by_forum_stmt *sql.Stmt var report_exists_stmt *sql.Stmt var group_count_stmt *sql.Stmt +var modlog_count_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_members_stmt *sql.Stmt @@ -215,6 +218,12 @@ func _gen_mysql() (err error) { return err } + log.Print("Preparing get_users_offset statement.") + get_users_offset_stmt, err = db.Prepare("SELECT `uid`,`name`,`group`,`active`,`is_super_admin`,`avatar` FROM `users` LIMIT ?,?") + if err != nil { + return err + } + log.Print("Preparing is_theme_default statement.") is_theme_default_stmt, err = db.Prepare("SELECT `default` FROM `themes` WHERE `uname` = ?") if err != nil { @@ -227,6 +236,12 @@ func _gen_mysql() (err error) { return err } + log.Print("Preparing get_modlogs_offset statement.") + get_modlogs_offset_stmt, err = db.Prepare("SELECT `action`,`elementID`,`elementType`,`ipaddress`,`actorID`,`doneAt` FROM `moderation_logs` LIMIT ?,?") + if err != nil { + return err + } + log.Print("Preparing get_reply_tid statement.") get_reply_tid_stmt, err = db.Prepare("SELECT `tid` FROM `replies` WHERE `rid` = ?") if err != nil { @@ -731,6 +746,12 @@ func _gen_mysql() (err error) { return err } + log.Print("Preparing modlog_count statement.") + modlog_count_stmt, err = db.Prepare("SELECT COUNT(*) AS `count` FROM `moderation_logs`") + if err != nil { + return err + } + 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") if err != nil { diff --git a/install-gosora-linux b/install-linux similarity index 100% rename from install-gosora-linux rename to install-linux diff --git a/mysql.sql b/mysql.sql index dcff8cf3..48d98ddb 100644 --- a/mysql.sql +++ b/mysql.sql @@ -190,6 +190,8 @@ INSERT INTO settings(`name`,`content`,`type`) VALUES ('url_tags','1','bool'); INSERT INTO settings(`name`,`content`,`type`,`constraints`) VALUES ('activation_type','1','list','1-3'); INSERT INTO settings(`name`,`content`,`type`) VALUES ('bigpost_min_words','250','int'); INSERT INTO settings(`name`,`content`,`type`) VALUES ('megapost_min_words','1000','int'); +/* TO-DO: Implement the html-attribute setting type before deploying this */ +/*INSERT INTO settings(`name`,`content`,`type`) VALUES ('meta_desc','','html-attribute');*/ INSERT INTO themes(`uname`,`default`) VALUES ('tempra-simple',1); INSERT INTO emails(`email`,`uid`,`validated`) VALUES ('admin@localhost',1,1); diff --git a/pages.go b/pages.go index ddc6dec9..2ce132b2 100644 --- a/pages.go +++ b/pages.go @@ -126,19 +126,6 @@ type PanelPage struct 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 { ID string @@ -171,6 +158,32 @@ type PanelThemesPage struct ExtData ExtData } +type PanelUserPage struct +{ + Title string + CurrentUser User + Header HeaderVars + Stats PanelStats + ItemList []User + PageList []int + Page int + LastPage int + ExtData ExtData +} + +type PanelGroupPage struct +{ + Title string + CurrentUser User + Header HeaderVars + Stats PanelStats + ItemList []GroupAdmin + PageList []int + Page int + LastPage int + ExtData ExtData +} + type PanelEditGroupPage struct { Title string @@ -245,6 +258,9 @@ type PanelLogsPage struct Header HeaderVars Stats PanelStats Logs []Log + PageList []int + Page int + LastPage int ExtData ExtData } diff --git a/panel_routes.go b/panel_routes.go index fd9a719a..bbcc4083 100644 --- a/panel_routes.go +++ b/panel_routes.go @@ -964,15 +964,19 @@ func route_panel_users(w http.ResponseWriter, r *http.Request, user User){ return } - var userList []interface{} - rows, err := get_users_stmt.Query() + page, _ := strconv.Atoi(r.FormValue("page")) + perPage := 10 + offset, page, lastPage := page_offset(stats.Users, page, perPage) + + var userList []User + rows, err := get_users_offset_stmt.Query(offset,perPage) if err != nil { InternalError(err,w) return } defer rows.Close() - // TO-DO: Add a UserStore method for iterating over global users + // TO-DO: Add a UserStore method for iterating over global users and global user offsets for rows.Next() { puser := User{ID: 0,} err := rows.Scan(&puser.ID, &puser.Name, &puser.Group, &puser.Active, &puser.Is_Super_Admin, &puser.Avatar) @@ -1003,7 +1007,8 @@ func route_panel_users(w http.ResponseWriter, r *http.Request, user User){ return } - pi := PanelPage{"User Manager",user,headerVars,stats,userList,nil} + pageList := paginate(stats.Users, perPage, 5) + pi := PanelUserPage{"User Manager",user,headerVars,stats,userList,pageList,page,lastPage,extData} if pre_render_hooks["pre_render_panel_users"] != nil { if run_pre_render_hook("pre_render_panel_users", w, r, &user, &pi) { return @@ -1740,7 +1745,18 @@ func route_panel_logs_mod(w http.ResponseWriter, r *http.Request, user User){ return } - rows, err := get_modlogs_stmt.Query() + var logCount int + err := modlog_count_stmt.QueryRow().Scan(&logCount) + if err != nil { + InternalError(err,w) + return + } + + page, _ := strconv.Atoi(r.FormValue("page")) + perPage := 10 + offset, page, lastPage := page_offset(logCount, page, perPage) + + rows, err := get_modlogs_offset_stmt.Query(offset,perPage) if err != nil { InternalError(err,w) return @@ -1826,7 +1842,8 @@ func route_panel_logs_mod(w http.ResponseWriter, r *http.Request, user User){ return } - pi := PanelLogsPage{"Moderation Logs",user,headerVars,stats,logs,extData} + pageList := paginate(logCount, perPage, 5) + pi := PanelLogsPage{"Moderation Logs",user,headerVars,stats,logs,pageList,page,lastPage,extData} if pre_render_hooks["pre_render_panel_mod_log"] != nil { if run_pre_render_hook("pre_render_panel_mod_log", w, r, &user, &pi) { return diff --git a/public/global.js b/public/global.js index 4d0f67f3..b876ad45 100644 --- a/public/global.js +++ b/public/global.js @@ -15,7 +15,7 @@ function load_alerts(menu_alerts) { var alertListNode = menu_alerts.getElementsByClassName("alertList")[0]; var alertCounterNode = menu_alerts.getElementsByClassName("alert_counter")[0]; - alertCounterNode.textContent = ""; + alertCounterNode.textContent = "0"; $.ajax({ type: 'get', dataType: 'json', @@ -58,7 +58,7 @@ function load_alerts(menu_alerts) //if(anyAvatar) menu_alerts.addClass("hasAvatars"); } alertListNode.innerHTML = alist; - if(data.msgCount != 0) { + if(data.msgCount != 0 && data.msgCount != undefined) { alertCounterNode.textContent = data.msgCount; menu_alerts.classList.add("has_alerts"); } else { @@ -108,6 +108,8 @@ $(document).ready(function(){ conn.onopen = function() { conn.send("page " + document.location.pathname + '\r'); + // TO-DO: Don't ask again, if it's denied. We could have a setting in the UCP which automatically requests this when someone flips desktop notifications on + Notification.requestPermission(); } conn.onclose = function() { conn = false; @@ -141,6 +143,16 @@ $(document).ready(function(){ //console.log(alist); $("#general_alerts").find(".alertList").html(alist); // Add support for other alert feeds like PM Alerts $("#general_alerts").find(".alert_counter").text(alertCount); + + // TO-DO: Add some sort of notification queue to avoid flooding the end-user with notices? + // TO-DO: Use the site name instead of "Something Happened" + if(Notification.permission === "granted") { + var n = new Notification("Something Happened",{ + body: msg, + icon: data.avatar, + }); + setTimeout(n.close.bind(n), 8000); + } } } diff --git a/query_gen/main.go b/query_gen/main.go index 565dbb82..6840e560 100644 --- a/query_gen/main.go +++ b/query_gen/main.go @@ -192,10 +192,14 @@ func write_selects(adapter qgen.DB_Adapter) error { adapter.SimpleSelect("get_users","users","uid, name, group, active, is_super_admin, avatar","","","") + adapter.SimpleSelect("get_users_offset","users","uid, name, group, active, is_super_admin, avatar","","","?,?") + adapter.SimpleSelect("is_theme_default","themes","default","uname = ?","","") adapter.SimpleSelect("get_modlogs","moderation_logs","action, elementID, elementType, ipaddress, actorID, doneAt","","","") + adapter.SimpleSelect("get_modlogs_offset","moderation_logs","action, elementID, elementType, ipaddress, actorID, doneAt","","","?,?") + adapter.SimpleSelect("get_reply_tid","replies","tid","rid = ?","","") adapter.SimpleSelect("get_topic_fid","topics","parentID","tid = ?","","") @@ -395,6 +399,8 @@ func write_simple_counts(adapter qgen.DB_Adapter) error { adapter.SimpleCount("group_count","users_groups","","") + adapter.SimpleCount("modlog_count","moderation_logs","","") + return nil } diff --git a/routes.go b/routes.go index 47cb0102..71b05b6a 100644 --- a/routes.go +++ b/routes.go @@ -24,6 +24,7 @@ var nList []string var hvars HeaderVars var extData ExtData var success_json_bytes []byte = []byte(`{"success":"1"}`) +var cacheControlMaxAge string = "max-age=" + strconv.Itoa(day) func init() { hvars.Site = site @@ -57,14 +58,14 @@ func route_static(w http.ResponseWriter, r *http.Request){ h := w.Header() h.Set("Last-Modified", file.FormattedModTime) h.Set("Content-Type", file.Mimetype) + //Cache-Control: max-age=31536000 + h.Set("Cache-Control", cacheControlMaxAge) + h.Set("Vary","Accept-Encoding") //http.ServeContent(w,r,r.URL.Path,file.Info.ModTime(),file) //w.Write(file.Data) if strings.Contains(r.Header.Get("Accept-Encoding"),"gzip") { h.Set("Content-Encoding","gzip") 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? } else { h.Set("Content-Length", strconv.FormatInt(file.Length, 10)) // Avoid doing a type conversion every time? diff --git a/run-gosora-linux b/run-linux similarity index 100% rename from run-gosora-linux rename to run-linux diff --git a/run-gosora-linux-nowebsockets b/run-linux-nowebsockets similarity index 100% rename from run-gosora-linux-nowebsockets rename to run-linux-nowebsockets diff --git a/setting.go b/setting.go index 506b644f..859dfc9c 100644 --- a/setting.go +++ b/setting.go @@ -50,6 +50,7 @@ func LoadSettings() error { return nil } +// TO-DO: Add better support for HTML attributes (html-attribute). E.g. Meta descriptions. func parseSetting(sname string, scontent string, stype string, constraint string) string { var err error if stype == "bool" { diff --git a/template_list.go b/template_list.go index 76241590..914dc54d 100644 --- a/template_list.go +++ b/template_list.go @@ -149,7 +149,7 @@ var topic_40 []byte = []byte(`' style="font-weight:normal;" title="Unpin Topic"> var topic_41 []byte = []byte(``) var topic_43 []byte = []byte(` - @@ -200,7 +200,7 @@ var topic_71 []byte = []byte(`" class="mod_button" title="Edit Reply">`) var topic_74 []byte = []byte(` - @@ -210,8 +210,8 @@ var topic_78 []byte = []byte(``) var topic_80 []byte = []byte(``) var topic_81 []byte = []byte(``) -var topic_82 []byte = []byte(``) -var topic_83 []byte = []byte(` +var topic_82 []byte = []byte(``) +var topic_83 []byte = []byte(` diff --git a/templates/panel-adminlogs.html b/templates/panel-adminlogs.html index ca8da843..c62dfbff 100644 --- a/templates/panel-adminlogs.html +++ b/templates/panel-adminlogs.html @@ -6,7 +6,6 @@
Moderation Logs
{{if .CurrentUser.Perms.ViewAdminLogs}}
Administration Logs
{{end}} - {{if .CurrentUser.Perms.ViewAdminLogs}}
System Logs
{{end}}
{{template "panel-inner-menu.html" . }} diff --git a/templates/panel-forums.html b/templates/panel-forums.html index 5772f431..a1f3b251 100644 --- a/templates/panel-forums.html +++ b/templates/panel-forums.html @@ -11,7 +11,7 @@
{{range .ItemList}} -
+
@@ -24,7 +24,8 @@ - {{.Name}} + {{/** TO-DO: Make sure the forum_active_name class is set and unset when the activity status of this forum is changed **/}} + {{.Name}}
{{.Desc}}
diff --git a/templates/panel-modlogs.html b/templates/panel-modlogs.html index 34cae9a8..2b87bbea 100644 --- a/templates/panel-modlogs.html +++ b/templates/panel-modlogs.html @@ -6,7 +6,6 @@
{{if .CurrentUser.Perms.ViewAdminLogs}}{{end}} - {{if .CurrentUser.Perms.ViewAdminLogs}}{{end}}
{{template "panel-inner-menu.html" . }}
@@ -28,5 +27,14 @@
{{end}}
+ {{if gt .LastPage 1}} +
+ {{if gt .Page 1}}
Prev
{{end}} + {{range .PageList}} +
{{.}}
+ {{end}} + {{if ne .LastPage .Page}}
Next
{{end}} +
+ {{end}} {{template "footer.html" . }} diff --git a/templates/panel-users.html b/templates/panel-users.html index 1b66e1af..cf26d74d 100644 --- a/templates/panel-users.html +++ b/templates/panel-users.html @@ -19,5 +19,14 @@ {{end}} + {{if gt .LastPage 1}} +
+ {{if gt .Page 1}}
Prev
{{end}} + {{range .PageList}} +
{{.}}
+ {{end}} + {{if ne .LastPage .Page}}
Next
{{end}} +
+ {{end}} {{template "footer.html" . }} diff --git a/templates/topic.html b/templates/topic.html index 279f30c1..df0dd975 100644 --- a/templates/topic.html +++ b/templates/topic.html @@ -39,7 +39,7 @@ {{if .CurrentUser.Perms.DeleteTopic}}{{end}} {{if .CurrentUser.Perms.PinTopic}}{{if .Topic.Sticky}}{{else}}{{end}}{{end}} - + {{if .Topic.LikeCount}}{{.Topic.LikeCount}}{{end}} @@ -65,11 +65,11 @@ {{if $.CurrentUser.Perms.EditReply}}{{end}} {{if $.CurrentUser.Perms.DeleteReply}}{{end}} - + {{if .LikeCount}}{{.LikeCount}}{{end}} - {{if .Tag}}{{.Tag}}{{else}}{{.Level}}{{end}} + {{if .Tag}}{{.Tag}}{{else}}{{.Level}}{{end}} diff --git a/themes/cosmo-conflux/public/panel.css b/themes/cosmo-conflux/public/panel.css index a41e9989..fa357f94 100644 --- a/themes/cosmo-conflux/public/panel.css +++ b/themes/cosmo-conflux/public/panel.css @@ -60,6 +60,8 @@ .forum_active_Hide:before { content: "Hidden"; } .forum_active_Hide + .forum_preset:before { content: " | "; } .forum_active_Show { display: none !important; } +.forum_active_name { color: #707070; } +.builtin_forum_divider { border-bottom-style: solid; } .perm_preset_no_access:before { content: "No Access"; color: maroon; } .perm_preset_read_only:before { content: "Read Only"; color: green; } diff --git a/themes/cosmo/public/panel.css b/themes/cosmo/public/panel.css index a41e9989..fa357f94 100644 --- a/themes/cosmo/public/panel.css +++ b/themes/cosmo/public/panel.css @@ -60,6 +60,8 @@ .forum_active_Hide:before { content: "Hidden"; } .forum_active_Hide + .forum_preset:before { content: " | "; } .forum_active_Show { display: none !important; } +.forum_active_name { color: #707070; } +.builtin_forum_divider { border-bottom-style: solid; } .perm_preset_no_access:before { content: "No Access"; color: maroon; } .perm_preset_read_only:before { content: "Read Only"; color: green; } diff --git a/themes/shadow/public/main.css b/themes/shadow/public/main.css index 04a4671e..02d9ff2c 100644 --- a/themes/shadow/public/main.css +++ b/themes/shadow/public/main.css @@ -86,8 +86,27 @@ li { } .selectedAlert .alertList { - /* Coming Soon. Hidden so it doesn't distract me while I design this theme */ - display: none; + display: block; + position: absolute; + top: 44px; + float: left; + width: 200px; + z-index: 50; + right: 15%; + font-size: 13px; + background-color: #333333; +} + +.alertItem { + margin-bottom: 2px; +} +.alertItem.withAvatar { + height: 40px; + background-size: 48px; + background-repeat: no-repeat; + background-color: rgb(61,61,61); + padding-left: 56px; + padding-top: 8px; } a { @@ -142,19 +161,13 @@ a { float: left; width: calc(70% - 24px); } -.colstack_left:empty, .colstack_right:empty { - display: none; -} - -.show_on_edit { +.colstack_left:empty, .colstack_right:empty, .show_on_edit { display: none; } .topic_sticky_head { - } .topic_closed_head { - } .post_item { @@ -585,6 +598,22 @@ input, select, textarea { display: none; } + .selectedAlert .alertList { + right: 10px; + top: 42px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + .alertItem.withAvatar { + height: 28px; + background-size: 38px; + padding-left: 46px; + padding-top: 10px; + overflow: hidden; + text-overflow: ellipsis; + } + #back { width: calc(100% - 20px); } diff --git a/themes/shadow/public/panel.css b/themes/shadow/public/panel.css index a18a91fb..a9b8940b 100644 --- a/themes/shadow/public/panel.css +++ b/themes/shadow/public/panel.css @@ -22,3 +22,10 @@ #forum_quick_perms .edit_fields { float: right; } + +.forum_active_name { + color: rgb(200,200,200); +} +.builtin_forum_divider { + margin-bottom: 5px; +} diff --git a/themes/tempra-conflux/public/panel.css b/themes/tempra-conflux/public/panel.css index 32776780..ddec014a 100644 --- a/themes/tempra-conflux/public/panel.css +++ b/themes/tempra-conflux/public/panel.css @@ -70,6 +70,8 @@ .forum_active_Hide:before { content: "Hidden"; } .forum_active_Hide + .forum_preset:before { content: " | "; } .forum_active_Show { display: none !important; } +.forum_active_name { color: #707070; } +.builtin_forum_divider { border-bottom-style: solid; } .perm_preset_no_access:before { content: "No Access"; color: maroon; } .perm_preset_read_only:before { content: "Read Only"; color: green; } diff --git a/themes/tempra-cursive/public/panel.css b/themes/tempra-cursive/public/panel.css index 5bcd83a4..1e3f1bf9 100644 --- a/themes/tempra-cursive/public/panel.css +++ b/themes/tempra-cursive/public/panel.css @@ -58,6 +58,8 @@ .forum_active_Hide:before { content: "Hidden"; } .forum_active_Hide + .forum_preset:before { content: " | "; } .forum_active_Show { display: none !important; } +.forum_active_name { color: #707070; } +.builtin_forum_divider { border-bottom-style: solid; } .perm_preset_no_access:before { content: "No Access"; color: maroon; } .perm_preset_read_only:before { content: "Read Only"; color: green; } diff --git a/themes/tempra-simple/public/panel.css b/themes/tempra-simple/public/panel.css index 542556c6..d63964dd 100644 --- a/themes/tempra-simple/public/panel.css +++ b/themes/tempra-simple/public/panel.css @@ -62,6 +62,8 @@ .forum_preset_all, .forum_preset_custom, .forum_preset_ { display: none !important; } .forum_active_Hide:before { content: "🕵️"; } .forum_active_Show { display: none !important; } +.forum_active_name { color: #707070; } +.builtin_forum_divider { border-bottom-style: solid; } .perm_preset_no_access:before { content: "No Access"; color: maroon; } .perm_preset_read_only:before { content: "Read Only"; color: green; } diff --git a/user.go b/user.go index 3c44106f..f7d43f13 100644 --- a/user.go +++ b/user.go @@ -324,6 +324,8 @@ func _pre_route(w http.ResponseWriter, r *http.Request) (User,bool) { } user.Last_IP = host } + + // TO-DO: Set the X-Frame-Options header return *user, true }