You can now filter analytics graphs by a day, twelve hours and six hours.

Added a X-XSS-Protection header.
Fixed some Group Editor CSS for Cosora.
This commit is contained in:
Azareal 2018-01-08 08:53:51 +00:00
parent d0b6559df9
commit c7aec90612
14 changed files with 409 additions and 177 deletions

33
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,33 @@
We're not accepting contributions right now, although you're welcome to poke me about things. I'd like to put a process together at some point.
# Coding Standards
All code must be unit tested where ever possible with the exception of JavaScript which is untestable with our current technologies, tread with caution there.
# Golang
Use the standard linter and listen to what it tells you to do.
The route assignments in main.go are *legacy code*, add new routes to `router_gen/routes.go` instead.
Try to use the single responsibility principle where ever possible, with the exception for if doing so will cause a large performance drop. In other words, don't give your interfaces / structs too many responsibilities, keep them simple.
Avoid hand-rolling queries. Use the builders, a ready built statement or a datastore structure instead. Preferably a datastore.
More coming up.
# JavaScript
Use semicolons at the end of statements. If you don't, you might wind up breaking a minifier or two.
Always use strict mode.
Don't worry about ES5, we're targetting modern browsers. If we decide to backport code to older browsers, then we'll transpile the files.
Don't use await until Edge gets around to implementing it.
To keep consistency with Go code, variables must be camelCase.
# JSON
To keep consistency with Go code, map keys must be camelCase.

View File

@ -154,6 +154,11 @@ type PanelTimeGraph struct {
Labels []int64 // unixtimes for the bottom, gets converted into 1:00, 2:00, etc. with JS Labels []int64 // unixtimes for the bottom, gets converted into 1:00, 2:00, etc. with JS
} }
type PanelAnalyticsItem struct {
Time int64
Count int64
}
type PanelAnalyticsPage struct { type PanelAnalyticsPage struct {
Title string Title string
CurrentUser User CurrentUser User
@ -161,6 +166,8 @@ type PanelAnalyticsPage struct {
Stats PanelStats Stats PanelStats
Zone string Zone string
PrimaryGraph PanelTimeGraph PrimaryGraph PanelTimeGraph
ViewItems []PanelAnalyticsItem
TimeRange string
} }
type PanelAnalyticsRoutesItem struct { type PanelAnalyticsRoutesItem struct {
@ -185,6 +192,7 @@ type PanelAnalyticsRoutePage struct {
Zone string Zone string
Route string Route string
PrimaryGraph PanelTimeGraph PrimaryGraph PanelTimeGraph
TimeRange string
} }
type PanelThemesPage struct { type PanelThemesPage struct {

View File

@ -278,7 +278,7 @@ func preRoute(w http.ResponseWriter, r *http.Request) (User, bool) {
h := w.Header() h := w.Header()
h.Set("X-Frame-Options", "deny") h.Set("X-Frame-Options", "deny")
//h.Set("X-XSS-Protection", "1") h.Set("X-XSS-Protection", "1; mode=block") // TODO: Remove when we add a CSP? CSP's are horrendously glitchy things, tread with caution before removing
// TODO: Set the content policy header // TODO: Set the content policy header
return *usercpy, true return *usercpy, true

View File

@ -45,6 +45,7 @@ type User struct {
Perms Perms Perms Perms
PluginPerms map[string]bool PluginPerms map[string]bool
Session string Session string
//AuthToken string
Loggedin bool Loggedin bool
Avatar string Avatar string
Message string Message string

View File

@ -32,6 +32,7 @@ var RouteMap = map[string]interface{}{
"routePanelForumsEdit": routePanelForumsEdit, "routePanelForumsEdit": routePanelForumsEdit,
"routePanelForumsEditSubmit": routePanelForumsEditSubmit, "routePanelForumsEditSubmit": routePanelForumsEditSubmit,
"routePanelForumsEditPermsSubmit": routePanelForumsEditPermsSubmit, "routePanelForumsEditPermsSubmit": routePanelForumsEditPermsSubmit,
"routePanelForumsEditPermsAdvance": routePanelForumsEditPermsAdvance,
"routePanelSettings": routePanelSettings, "routePanelSettings": routePanelSettings,
"routePanelSettingEdit": routePanelSettingEdit, "routePanelSettingEdit": routePanelSettingEdit,
"routePanelSettingEditSubmit": routePanelSettingEditSubmit, "routePanelSettingEditSubmit": routePanelSettingEditSubmit,
@ -98,51 +99,52 @@ var routeMapEnum = map[string]int{
"routePanelForumsEdit": 14, "routePanelForumsEdit": 14,
"routePanelForumsEditSubmit": 15, "routePanelForumsEditSubmit": 15,
"routePanelForumsEditPermsSubmit": 16, "routePanelForumsEditPermsSubmit": 16,
"routePanelSettings": 17, "routePanelForumsEditPermsAdvance": 17,
"routePanelSettingEdit": 18, "routePanelSettings": 18,
"routePanelSettingEditSubmit": 19, "routePanelSettingEdit": 19,
"routePanelWordFilters": 20, "routePanelSettingEditSubmit": 20,
"routePanelWordFiltersCreate": 21, "routePanelWordFilters": 21,
"routePanelWordFiltersEdit": 22, "routePanelWordFiltersCreate": 22,
"routePanelWordFiltersEditSubmit": 23, "routePanelWordFiltersEdit": 23,
"routePanelWordFiltersDeleteSubmit": 24, "routePanelWordFiltersEditSubmit": 24,
"routePanelThemes": 25, "routePanelWordFiltersDeleteSubmit": 25,
"routePanelThemesSetDefault": 26, "routePanelThemes": 26,
"routePanelPlugins": 27, "routePanelThemesSetDefault": 27,
"routePanelPluginsActivate": 28, "routePanelPlugins": 28,
"routePanelPluginsDeactivate": 29, "routePanelPluginsActivate": 29,
"routePanelPluginsInstall": 30, "routePanelPluginsDeactivate": 30,
"routePanelUsers": 31, "routePanelPluginsInstall": 31,
"routePanelUsersEdit": 32, "routePanelUsers": 32,
"routePanelUsersEditSubmit": 33, "routePanelUsersEdit": 33,
"routePanelAnalyticsViews": 34, "routePanelUsersEditSubmit": 34,
"routePanelAnalyticsRoutes": 35, "routePanelAnalyticsViews": 35,
"routePanelAnalyticsRouteViews": 36, "routePanelAnalyticsRoutes": 36,
"routePanelGroups": 37, "routePanelAnalyticsRouteViews": 37,
"routePanelGroupsEdit": 38, "routePanelGroups": 38,
"routePanelGroupsEditPerms": 39, "routePanelGroupsEdit": 39,
"routePanelGroupsEditSubmit": 40, "routePanelGroupsEditPerms": 40,
"routePanelGroupsEditPermsSubmit": 41, "routePanelGroupsEditSubmit": 41,
"routePanelGroupsCreateSubmit": 42, "routePanelGroupsEditPermsSubmit": 42,
"routePanelBackups": 43, "routePanelGroupsCreateSubmit": 43,
"routePanelLogsMod": 44, "routePanelBackups": 44,
"routePanelDebug": 45, "routePanelLogsMod": 45,
"routePanel": 46, "routePanelDebug": 46,
"routeAccountEditCritical": 47, "routePanel": 47,
"routeAccountEditCriticalSubmit": 48, "routeAccountEditCritical": 48,
"routeAccountEditAvatar": 49, "routeAccountEditCriticalSubmit": 49,
"routeAccountEditAvatarSubmit": 50, "routeAccountEditAvatar": 50,
"routeAccountEditUsername": 51, "routeAccountEditAvatarSubmit": 51,
"routeAccountEditUsernameSubmit": 52, "routeAccountEditUsername": 52,
"routeAccountEditEmail": 53, "routeAccountEditUsernameSubmit": 53,
"routeAccountEditEmailTokenSubmit": 54, "routeAccountEditEmail": 54,
"routeProfile": 55, "routeAccountEditEmailTokenSubmit": 55,
"routeBanSubmit": 56, "routeProfile": 56,
"routeUnban": 57, "routeBanSubmit": 57,
"routeActivate": 58, "routeUnban": 58,
"routeIps": 59, "routeActivate": 59,
"routeDynamic": 60, "routeIps": 60,
"routeUploads": 61, "routeDynamic": 61,
"routeUploads": 62,
} }
var reverseRouteMapEnum = map[int]string{ var reverseRouteMapEnum = map[int]string{
0: "routeAPI", 0: "routeAPI",
@ -162,51 +164,52 @@ var reverseRouteMapEnum = map[int]string{
14: "routePanelForumsEdit", 14: "routePanelForumsEdit",
15: "routePanelForumsEditSubmit", 15: "routePanelForumsEditSubmit",
16: "routePanelForumsEditPermsSubmit", 16: "routePanelForumsEditPermsSubmit",
17: "routePanelSettings", 17: "routePanelForumsEditPermsAdvance",
18: "routePanelSettingEdit", 18: "routePanelSettings",
19: "routePanelSettingEditSubmit", 19: "routePanelSettingEdit",
20: "routePanelWordFilters", 20: "routePanelSettingEditSubmit",
21: "routePanelWordFiltersCreate", 21: "routePanelWordFilters",
22: "routePanelWordFiltersEdit", 22: "routePanelWordFiltersCreate",
23: "routePanelWordFiltersEditSubmit", 23: "routePanelWordFiltersEdit",
24: "routePanelWordFiltersDeleteSubmit", 24: "routePanelWordFiltersEditSubmit",
25: "routePanelThemes", 25: "routePanelWordFiltersDeleteSubmit",
26: "routePanelThemesSetDefault", 26: "routePanelThemes",
27: "routePanelPlugins", 27: "routePanelThemesSetDefault",
28: "routePanelPluginsActivate", 28: "routePanelPlugins",
29: "routePanelPluginsDeactivate", 29: "routePanelPluginsActivate",
30: "routePanelPluginsInstall", 30: "routePanelPluginsDeactivate",
31: "routePanelUsers", 31: "routePanelPluginsInstall",
32: "routePanelUsersEdit", 32: "routePanelUsers",
33: "routePanelUsersEditSubmit", 33: "routePanelUsersEdit",
34: "routePanelAnalyticsViews", 34: "routePanelUsersEditSubmit",
35: "routePanelAnalyticsRoutes", 35: "routePanelAnalyticsViews",
36: "routePanelAnalyticsRouteViews", 36: "routePanelAnalyticsRoutes",
37: "routePanelGroups", 37: "routePanelAnalyticsRouteViews",
38: "routePanelGroupsEdit", 38: "routePanelGroups",
39: "routePanelGroupsEditPerms", 39: "routePanelGroupsEdit",
40: "routePanelGroupsEditSubmit", 40: "routePanelGroupsEditPerms",
41: "routePanelGroupsEditPermsSubmit", 41: "routePanelGroupsEditSubmit",
42: "routePanelGroupsCreateSubmit", 42: "routePanelGroupsEditPermsSubmit",
43: "routePanelBackups", 43: "routePanelGroupsCreateSubmit",
44: "routePanelLogsMod", 44: "routePanelBackups",
45: "routePanelDebug", 45: "routePanelLogsMod",
46: "routePanel", 46: "routePanelDebug",
47: "routeAccountEditCritical", 47: "routePanel",
48: "routeAccountEditCriticalSubmit", 48: "routeAccountEditCritical",
49: "routeAccountEditAvatar", 49: "routeAccountEditCriticalSubmit",
50: "routeAccountEditAvatarSubmit", 50: "routeAccountEditAvatar",
51: "routeAccountEditUsername", 51: "routeAccountEditAvatarSubmit",
52: "routeAccountEditUsernameSubmit", 52: "routeAccountEditUsername",
53: "routeAccountEditEmail", 53: "routeAccountEditUsernameSubmit",
54: "routeAccountEditEmailTokenSubmit", 54: "routeAccountEditEmail",
55: "routeProfile", 55: "routeAccountEditEmailTokenSubmit",
56: "routeBanSubmit", 56: "routeProfile",
57: "routeUnban", 57: "routeBanSubmit",
58: "routeActivate", 58: "routeUnban",
59: "routeIps", 59: "routeActivate",
60: "routeDynamic", 60: "routeIps",
61: "routeUploads", 61: "routeDynamic",
62: "routeUploads",
} }
// TODO: Stop spilling these into the package scope? // TODO: Stop spilling these into the package scope?
@ -469,11 +472,14 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
common.RouteViewCounter.Bump(16) common.RouteViewCounter.Bump(16)
err = routePanelForumsEditPermsSubmit(w,req,user,extraData) err = routePanelForumsEditPermsSubmit(w,req,user,extraData)
case "/panel/settings/": case "/panel/forums/edit/perms/":
common.RouteViewCounter.Bump(17) common.RouteViewCounter.Bump(17)
err = routePanelForumsEditPermsAdvance(w,req,user,extraData)
case "/panel/settings/":
common.RouteViewCounter.Bump(18)
err = routePanelSettings(w,req,user) err = routePanelSettings(w,req,user)
case "/panel/settings/edit/": case "/panel/settings/edit/":
common.RouteViewCounter.Bump(18) common.RouteViewCounter.Bump(19)
err = routePanelSettingEdit(w,req,user,extraData) err = routePanelSettingEdit(w,req,user,extraData)
case "/panel/settings/edit/submit/": case "/panel/settings/edit/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -482,10 +488,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(19) common.RouteViewCounter.Bump(20)
err = routePanelSettingEditSubmit(w,req,user,extraData) err = routePanelSettingEditSubmit(w,req,user,extraData)
case "/panel/settings/word-filters/": case "/panel/settings/word-filters/":
common.RouteViewCounter.Bump(20) common.RouteViewCounter.Bump(21)
err = routePanelWordFilters(w,req,user) err = routePanelWordFilters(w,req,user)
case "/panel/settings/word-filters/create/": case "/panel/settings/word-filters/create/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -494,10 +500,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(21) common.RouteViewCounter.Bump(22)
err = routePanelWordFiltersCreate(w,req,user) err = routePanelWordFiltersCreate(w,req,user)
case "/panel/settings/word-filters/edit/": case "/panel/settings/word-filters/edit/":
common.RouteViewCounter.Bump(22) common.RouteViewCounter.Bump(23)
err = routePanelWordFiltersEdit(w,req,user,extraData) err = routePanelWordFiltersEdit(w,req,user,extraData)
case "/panel/settings/word-filters/edit/submit/": case "/panel/settings/word-filters/edit/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -506,7 +512,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(23) common.RouteViewCounter.Bump(24)
err = routePanelWordFiltersEditSubmit(w,req,user,extraData) err = routePanelWordFiltersEditSubmit(w,req,user,extraData)
case "/panel/settings/word-filters/delete/submit/": case "/panel/settings/word-filters/delete/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -515,10 +521,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(24) common.RouteViewCounter.Bump(25)
err = routePanelWordFiltersDeleteSubmit(w,req,user,extraData) err = routePanelWordFiltersDeleteSubmit(w,req,user,extraData)
case "/panel/themes/": case "/panel/themes/":
common.RouteViewCounter.Bump(25) common.RouteViewCounter.Bump(26)
err = routePanelThemes(w,req,user) err = routePanelThemes(w,req,user)
case "/panel/themes/default/": case "/panel/themes/default/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -527,10 +533,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(26) common.RouteViewCounter.Bump(27)
err = routePanelThemesSetDefault(w,req,user,extraData) err = routePanelThemesSetDefault(w,req,user,extraData)
case "/panel/plugins/": case "/panel/plugins/":
common.RouteViewCounter.Bump(27) common.RouteViewCounter.Bump(28)
err = routePanelPlugins(w,req,user) err = routePanelPlugins(w,req,user)
case "/panel/plugins/activate/": case "/panel/plugins/activate/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -539,7 +545,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(28) common.RouteViewCounter.Bump(29)
err = routePanelPluginsActivate(w,req,user,extraData) err = routePanelPluginsActivate(w,req,user,extraData)
case "/panel/plugins/deactivate/": case "/panel/plugins/deactivate/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -548,7 +554,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(29) common.RouteViewCounter.Bump(30)
err = routePanelPluginsDeactivate(w,req,user,extraData) err = routePanelPluginsDeactivate(w,req,user,extraData)
case "/panel/plugins/install/": case "/panel/plugins/install/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -557,13 +563,13 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(30) common.RouteViewCounter.Bump(31)
err = routePanelPluginsInstall(w,req,user,extraData) err = routePanelPluginsInstall(w,req,user,extraData)
case "/panel/users/": case "/panel/users/":
common.RouteViewCounter.Bump(31) common.RouteViewCounter.Bump(32)
err = routePanelUsers(w,req,user) err = routePanelUsers(w,req,user)
case "/panel/users/edit/": case "/panel/users/edit/":
common.RouteViewCounter.Bump(32) common.RouteViewCounter.Bump(33)
err = routePanelUsersEdit(w,req,user,extraData) err = routePanelUsersEdit(w,req,user,extraData)
case "/panel/users/edit/submit/": case "/panel/users/edit/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -572,25 +578,31 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(33) common.RouteViewCounter.Bump(34)
err = routePanelUsersEditSubmit(w,req,user,extraData) err = routePanelUsersEditSubmit(w,req,user,extraData)
case "/panel/analytics/views/": case "/panel/analytics/views/":
common.RouteViewCounter.Bump(34) err = common.ParseForm(w,req,user)
if err != nil {
router.handleError(err,w,req,user)
return
}
common.RouteViewCounter.Bump(35)
err = routePanelAnalyticsViews(w,req,user) err = routePanelAnalyticsViews(w,req,user)
case "/panel/analytics/routes/": case "/panel/analytics/routes/":
common.RouteViewCounter.Bump(35) common.RouteViewCounter.Bump(36)
err = routePanelAnalyticsRoutes(w,req,user) err = routePanelAnalyticsRoutes(w,req,user)
case "/panel/analytics/route/": case "/panel/analytics/route/":
common.RouteViewCounter.Bump(36) common.RouteViewCounter.Bump(37)
err = routePanelAnalyticsRouteViews(w,req,user,extraData) err = routePanelAnalyticsRouteViews(w,req,user,extraData)
case "/panel/groups/": case "/panel/groups/":
common.RouteViewCounter.Bump(37) common.RouteViewCounter.Bump(38)
err = routePanelGroups(w,req,user) err = routePanelGroups(w,req,user)
case "/panel/groups/edit/": case "/panel/groups/edit/":
common.RouteViewCounter.Bump(38) common.RouteViewCounter.Bump(39)
err = routePanelGroupsEdit(w,req,user,extraData) err = routePanelGroupsEdit(w,req,user,extraData)
case "/panel/groups/edit/perms/": case "/panel/groups/edit/perms/":
common.RouteViewCounter.Bump(39) common.RouteViewCounter.Bump(40)
err = routePanelGroupsEditPerms(w,req,user,extraData) err = routePanelGroupsEditPerms(w,req,user,extraData)
case "/panel/groups/edit/submit/": case "/panel/groups/edit/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -599,7 +611,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(40) common.RouteViewCounter.Bump(41)
err = routePanelGroupsEditSubmit(w,req,user,extraData) err = routePanelGroupsEditSubmit(w,req,user,extraData)
case "/panel/groups/edit/perms/submit/": case "/panel/groups/edit/perms/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -608,7 +620,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(41) common.RouteViewCounter.Bump(42)
err = routePanelGroupsEditPermsSubmit(w,req,user,extraData) err = routePanelGroupsEditPermsSubmit(w,req,user,extraData)
case "/panel/groups/create/": case "/panel/groups/create/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -617,7 +629,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(42) common.RouteViewCounter.Bump(43)
err = routePanelGroupsCreateSubmit(w,req,user) err = routePanelGroupsCreateSubmit(w,req,user)
case "/panel/backups/": case "/panel/backups/":
err = common.SuperAdminOnly(w,req,user) err = common.SuperAdminOnly(w,req,user)
@ -626,10 +638,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(43) common.RouteViewCounter.Bump(44)
err = routePanelBackups(w,req,user,extraData) err = routePanelBackups(w,req,user,extraData)
case "/panel/logs/mod/": case "/panel/logs/mod/":
common.RouteViewCounter.Bump(44) common.RouteViewCounter.Bump(45)
err = routePanelLogsMod(w,req,user) err = routePanelLogsMod(w,req,user)
case "/panel/debug/": case "/panel/debug/":
err = common.AdminOnly(w,req,user) err = common.AdminOnly(w,req,user)
@ -638,10 +650,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(45) common.RouteViewCounter.Bump(46)
err = routePanelDebug(w,req,user) err = routePanelDebug(w,req,user)
default: default:
common.RouteViewCounter.Bump(46) common.RouteViewCounter.Bump(47)
err = routePanel(w,req,user) err = routePanel(w,req,user)
} }
if err != nil { if err != nil {
@ -656,7 +668,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(47) common.RouteViewCounter.Bump(48)
err = routeAccountEditCritical(w,req,user) err = routeAccountEditCritical(w,req,user)
case "/user/edit/critical/submit/": case "/user/edit/critical/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -671,7 +683,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(48) common.RouteViewCounter.Bump(49)
err = routeAccountEditCriticalSubmit(w,req,user) err = routeAccountEditCriticalSubmit(w,req,user)
case "/user/edit/avatar/": case "/user/edit/avatar/":
err = common.MemberOnly(w,req,user) err = common.MemberOnly(w,req,user)
@ -680,7 +692,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(49) common.RouteViewCounter.Bump(50)
err = routeAccountEditAvatar(w,req,user) err = routeAccountEditAvatar(w,req,user)
case "/user/edit/avatar/submit/": case "/user/edit/avatar/submit/":
err = common.MemberOnly(w,req,user) err = common.MemberOnly(w,req,user)
@ -689,7 +701,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(50) common.RouteViewCounter.Bump(51)
err = routeAccountEditAvatarSubmit(w,req,user) err = routeAccountEditAvatarSubmit(w,req,user)
case "/user/edit/username/": case "/user/edit/username/":
err = common.MemberOnly(w,req,user) err = common.MemberOnly(w,req,user)
@ -698,7 +710,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(51) common.RouteViewCounter.Bump(52)
err = routeAccountEditUsername(w,req,user) err = routeAccountEditUsername(w,req,user)
case "/user/edit/username/submit/": case "/user/edit/username/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -713,7 +725,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(52) common.RouteViewCounter.Bump(53)
err = routeAccountEditUsernameSubmit(w,req,user) err = routeAccountEditUsernameSubmit(w,req,user)
case "/user/edit/email/": case "/user/edit/email/":
err = common.MemberOnly(w,req,user) err = common.MemberOnly(w,req,user)
@ -722,7 +734,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(53) common.RouteViewCounter.Bump(54)
err = routeAccountEditEmail(w,req,user) err = routeAccountEditEmail(w,req,user)
case "/user/edit/token/": case "/user/edit/token/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -737,11 +749,11 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(54) common.RouteViewCounter.Bump(55)
err = routeAccountEditEmailTokenSubmit(w,req,user,extraData) err = routeAccountEditEmailTokenSubmit(w,req,user,extraData)
default: default:
req.URL.Path += extraData req.URL.Path += extraData
common.RouteViewCounter.Bump(55) common.RouteViewCounter.Bump(56)
err = routeProfile(w,req,user) err = routeProfile(w,req,user)
} }
if err != nil { if err != nil {
@ -762,7 +774,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(56) common.RouteViewCounter.Bump(57)
err = routeBanSubmit(w,req,user,extraData) err = routeBanSubmit(w,req,user,extraData)
case "/users/unban/": case "/users/unban/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -777,7 +789,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(57) common.RouteViewCounter.Bump(58)
err = routeUnban(w,req,user,extraData) err = routeUnban(w,req,user,extraData)
case "/users/activate/": case "/users/activate/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -792,7 +804,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(58) common.RouteViewCounter.Bump(59)
err = routeActivate(w,req,user,extraData) err = routeActivate(w,req,user,extraData)
case "/users/ips/": case "/users/ips/":
err = common.MemberOnly(w,req,user) err = common.MemberOnly(w,req,user)
@ -801,7 +813,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(59) common.RouteViewCounter.Bump(60)
err = routeIps(w,req,user) err = routeIps(w,req,user)
} }
if err != nil { if err != nil {
@ -818,7 +830,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
common.NotFound(w,req) common.NotFound(w,req)
return return
} }
common.RouteViewCounter.Bump(61) common.RouteViewCounter.Bump(62)
req.URL.Path += extraData req.URL.Path += extraData
// TODO: Find a way to propagate errors up from this? // TODO: Find a way to propagate errors up from this?
router.UploadHandler(w,req) // TODO: Count these views router.UploadHandler(w,req) // TODO: Count these views
@ -862,7 +874,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
router.RUnlock() router.RUnlock()
if ok { if ok {
common.RouteViewCounter.Bump(60) // TODO: Be more specific about *which* dynamic route it is common.RouteViewCounter.Bump(61) // TODO: Be more specific about *which* dynamic route it is
req.URL.Path += extraData req.URL.Path += extraData
err = handle(w,req,user) err = handle(w,req,user)
if err != nil { if err != nil {

View File

@ -427,6 +427,58 @@ func routePanelForumsEditPermsSubmit(w http.ResponseWriter, r *http.Request, use
return nil return nil
} }
func routePanelForumsEditPermsAdvance(w http.ResponseWriter, r *http.Request, user common.User, sfid string) common.RouteError {
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
if ferr != nil {
return ferr
}
if !user.Perms.ManageForums {
return common.NoPermissions(w, r, user)
}
fid, err := strconv.Atoi(sfid)
if err != nil {
return common.LocalError("The provided Forum ID is not a valid number.", w, r, user)
}
forum, err := common.Forums.Get(fid)
if err == ErrNoRows {
return common.LocalError("The forum you're trying to edit doesn't exist.", w, r, user)
} else if err != nil {
return common.InternalError(err, w, r)
}
if forum.Preset == "" {
forum.Preset = "custom"
}
glist, err := common.Groups.GetAll()
if err != nil {
return common.InternalError(err, w, r)
}
var gplist []common.GroupForumPermPreset
for gid, group := range glist {
if gid == 0 {
continue
}
gplist = append(gplist, common.GroupForumPermPreset{group, common.ForumPermsToGroupForumPreset(group.Forums[fid])})
}
pi := common.PanelEditForumPage{common.GetTitlePhrase("panel-edit-forum"), user, headerVars, stats, "forums", forum.ID, forum.Name, forum.Desc, forum.Active, forum.Preset, gplist}
if common.PreRenderHooks["pre_render_panel_edit_forum"] != nil {
if common.RunPreRenderHook("pre_render_panel_edit_forum", w, r, &user, &pi) {
return nil
}
}
err = common.Templates.ExecuteTemplate(w, "panel-forum-edit-perms.html", pi)
if err != nil {
return common.InternalError(err, w, r)
}
return nil
}
func routePanelAnalyticsViews(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { func routePanelAnalyticsViews(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user) headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
if ferr != nil { if ferr != nil {
@ -435,13 +487,36 @@ func routePanelAnalyticsViews(w http.ResponseWriter, r *http.Request, user commo
headerVars.Stylesheets = append(headerVars.Stylesheets, "chartist/chartist.min.css") headerVars.Stylesheets = append(headerVars.Stylesheets, "chartist/chartist.min.css")
headerVars.Scripts = append(headerVars.Scripts, "chartist/chartist.min.js") headerVars.Scripts = append(headerVars.Scripts, "chartist/chartist.min.js")
var timeQuantity = 6
var timeUnit = "hour"
var timeSlices = 12
var sliceWidth = 60 * 30
var timeRange = "six-hours"
switch r.FormValue("timeRange") {
case "one-day":
timeQuantity = 1
timeUnit = "day"
timeSlices = 24
sliceWidth = 60 * 60
timeRange = "one-day"
case "twelve-hours":
timeQuantity = 12
timeSlices = 24
timeRange = "twelve-hours"
case "six-hours", "":
timeRange = "six-hours"
default:
return common.LocalError("Unknown time range", w, r, user)
}
var revLabelList []int64 var revLabelList []int64
var labelList []int64 var labelList []int64
var viewMap = make(map[int64]int64) var viewMap = make(map[int64]int64)
var currentTime = time.Now().Unix() var currentTime = time.Now().Unix()
for i := 1; i <= 12; i++ { for i := 1; i <= timeSlices; i++ {
var label = currentTime - int64(i*60*30) var label = currentTime - int64(i*sliceWidth)
revLabelList = append(revLabelList, label) revLabelList = append(revLabelList, label)
viewMap[label] = 0 viewMap[label] = 0
} }
@ -453,7 +528,7 @@ func routePanelAnalyticsViews(w http.ResponseWriter, r *http.Request, user commo
log.Print("in routePanelAnalyticsViews") log.Print("in routePanelAnalyticsViews")
acc := qgen.Builder.Accumulator() acc := qgen.Builder.Accumulator()
rows, err := acc.Select("viewchunks").Columns("count, createdAt").Where("route = ''").DateCutoff("createdAt", 6, "hour").Query() rows, err := acc.Select("viewchunks").Columns("count, createdAt").Where("route = ''").DateCutoff("createdAt", timeQuantity, timeUnit).Query()
if err != nil && err != ErrNoRows { if err != nil && err != ErrNoRows {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
@ -483,13 +558,15 @@ func routePanelAnalyticsViews(w http.ResponseWriter, r *http.Request, user commo
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
var viewItems []common.PanelAnalyticsItem
for _, value := range revLabelList { for _, value := range revLabelList {
viewList = append(viewList, viewMap[value]) viewList = append(viewList, viewMap[value])
viewItems = append(viewItems, common.PanelAnalyticsItem{Time: value, Count: viewMap[value]})
} }
graph := common.PanelTimeGraph{Series: viewList, Labels: labelList} graph := common.PanelTimeGraph{Series: viewList, Labels: labelList}
log.Printf("graph: %+v\n", graph) log.Printf("graph: %+v\n", graph)
pi := common.PanelAnalyticsPage{common.GetTitlePhrase("panel-analytics"), user, headerVars, stats, "analytics", graph} pi := common.PanelAnalyticsPage{common.GetTitlePhrase("panel-analytics"), user, headerVars, stats, "analytics", graph, viewItems, timeRange}
if common.PreRenderHooks["pre_render_panel_analytics"] != nil { if common.PreRenderHooks["pre_render_panel_analytics"] != nil {
if common.RunPreRenderHook("pre_render_panel_analytics", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_panel_analytics", w, r, &user, &pi) {
return nil return nil
@ -563,13 +640,36 @@ func routePanelAnalyticsRouteViews(w http.ResponseWriter, r *http.Request, user
headerVars.Stylesheets = append(headerVars.Stylesheets, "chartist/chartist.min.css") headerVars.Stylesheets = append(headerVars.Stylesheets, "chartist/chartist.min.css")
headerVars.Scripts = append(headerVars.Scripts, "chartist/chartist.min.js") headerVars.Scripts = append(headerVars.Scripts, "chartist/chartist.min.js")
var timeQuantity = 6
var timeUnit = "hour"
var timeSlices = 12
var sliceWidth = 60 * 30
var timeRange = "six-hours"
switch r.FormValue("timeRange") {
case "one-day":
timeQuantity = 1
timeUnit = "day"
timeSlices = 24
sliceWidth = 60 * 60
timeRange = "one-day"
case "twelve-hours":
timeQuantity = 12
timeSlices = 24
timeRange = "twelve-hours"
case "six-hours", "":
timeRange = "six-hours"
default:
return common.LocalError("Unknown time range", w, r, user)
}
var revLabelList []int64 var revLabelList []int64
var labelList []int64 var labelList []int64
var viewMap = make(map[int64]int64) var viewMap = make(map[int64]int64)
var currentTime = time.Now().Unix() var currentTime = time.Now().Unix()
for i := 1; i <= 12; i++ { for i := 1; i <= timeSlices; i++ {
var label = currentTime - int64(i*60*30) var label = currentTime - int64(i*sliceWidth)
revLabelList = append(revLabelList, label) revLabelList = append(revLabelList, label)
viewMap[label] = 0 viewMap[label] = 0
} }
@ -581,7 +681,7 @@ func routePanelAnalyticsRouteViews(w http.ResponseWriter, r *http.Request, user
log.Print("in routePanelAnalyticsRouteViews") log.Print("in routePanelAnalyticsRouteViews")
acc := qgen.Builder.Accumulator() acc := qgen.Builder.Accumulator()
rows, err := acc.Select("viewchunks").Columns("count, createdAt").Where("route = ?").DateCutoff("createdAt", 6, "hour").Query(route) rows, err := acc.Select("viewchunks").Columns("count, createdAt").Where("route = ?").DateCutoff("createdAt", timeQuantity, timeUnit).Query(route)
if err != nil && err != ErrNoRows { if err != nil && err != ErrNoRows {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
@ -617,7 +717,7 @@ func routePanelAnalyticsRouteViews(w http.ResponseWriter, r *http.Request, user
graph := common.PanelTimeGraph{Series: viewList, Labels: labelList} graph := common.PanelTimeGraph{Series: viewList, Labels: labelList}
log.Printf("graph: %+v\n", graph) log.Printf("graph: %+v\n", graph)
pi := common.PanelAnalyticsRoutePage{common.GetTitlePhrase("panel-analytics"), user, headerVars, stats, "analytics", html.EscapeString(route), graph} pi := common.PanelAnalyticsRoutePage{common.GetTitlePhrase("panel-analytics"), user, headerVars, stats, "analytics", html.EscapeString(route), graph, timeRange}
if common.PreRenderHooks["pre_render_panel_analytics_route_views"] != nil { if common.PreRenderHooks["pre_render_panel_analytics_route_views"] != nil {
if common.RunPreRenderHook("pre_render_panel_analytics_route_views", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_panel_analytics_route_views", w, r, &user, &pi) {
return nil return nil

View File

@ -248,15 +248,13 @@ $(document).ready(function(){
$(this).closest('.deletable_block').remove(); $(this).closest('.deletable_block').remove();
}); });
$(".edit_item").click(function(event) $(".edit_item").click(function(event){
{
event.preventDefault(); event.preventDefault();
let blockParent = $(this).closest('.editable_parent'); let blockParent = $(this).closest('.editable_parent');
let block = blockParent.find('.editable_block').eq(0); let block = blockParent.find('.editable_block').eq(0);
block.html("<textarea style='width: 99%;' name='edit_item'>" + block.html() + "</textarea><br /><a href='" + $(this).closest('a').attr("href") + "'><button class='submit_edit' type='submit'>Update</button></a>"); block.html("<textarea style='width: 99%;' name='edit_item'>" + block.html() + "</textarea><br /><a href='" + $(this).closest('a').attr("href") + "'><button class='submit_edit' type='submit'>Update</button></a>");
$(".submit_edit").click(function(event) $(".submit_edit").click(function(event){
{
event.preventDefault(); event.preventDefault();
let blockParent = $(this).closest('.editable_parent'); let blockParent = $(this).closest('.editable_parent');
let block = blockParent.find('.editable_block').eq(0); let block = blockParent.find('.editable_block').eq(0);
@ -270,6 +268,12 @@ $(document).ready(function(){
}); });
}); });
$("#forum_quick_perms").click(function(){
$(".submit_edit").click(function(event){
});
});
$(".edit_field").click(function(event) $(".edit_field").click(function(event)
{ {
event.preventDefault(); event.preventDefault();
@ -536,8 +540,16 @@ $(document).ready(function(){
}); });
}); });
// The time range selector for the time graphs in the Control Panel
$(".timeRangeSelector").change(function(){
console.log("Changed the time range to " + this.options[this.selectedIndex].getAttribute("val"));
window.location = this.form.getAttribute("action")+"?timeRange=" + this.options[this.selectedIndex].getAttribute("val"); // Do a redirect as a form submission refuses to work properly
});
this.onkeyup = function(event) { this.onkeyup = function(event) {
if(event.which == 37) this.querySelectorAll("#prevFloat a")[0].click(); if(event.which == 37) this.querySelectorAll("#prevFloat a")[0].click();
if(event.which == 39) this.querySelectorAll("#nextFloat a")[0].click(); if(event.which == 39) this.querySelectorAll("#nextFloat a")[0].click();
}; };
}); });

View File

@ -17,6 +17,7 @@ func createTables(adapter qgen.Adapter) error {
qgen.DBTableColumn{"createdAt", "createdAt", 0, false, false, ""}, qgen.DBTableColumn{"createdAt", "createdAt", 0, false, false, ""},
qgen.DBTableColumn{"lastActiveAt", "datetime", 0, false, false, ""}, qgen.DBTableColumn{"lastActiveAt", "datetime", 0, false, false, ""},
qgen.DBTableColumn{"session", "varchar", 200, false, false, "''"}, qgen.DBTableColumn{"session", "varchar", 200, false, false, "''"},
//qgen.DBTableColumn{"authToken", "varchar", 200, false, false, "''"},
qgen.DBTableColumn{"last_ip", "varchar", 200, false, false, "0.0.0.0.0"}, qgen.DBTableColumn{"last_ip", "varchar", 200, false, false, "0.0.0.0.0"},
qgen.DBTableColumn{"email", "varchar", 200, false, false, "''"}, qgen.DBTableColumn{"email", "varchar", 200, false, false, "''"},
qgen.DBTableColumn{"avatar", "varchar", 100, false, false, "''"}, qgen.DBTableColumn{"avatar", "varchar", 100, false, false, "''"},

View File

@ -66,6 +66,7 @@ func buildPanelRoutes() {
View("routePanelForumsEdit", "/panel/forums/edit/", "extraData"), View("routePanelForumsEdit", "/panel/forums/edit/", "extraData"),
Action("routePanelForumsEditSubmit", "/panel/forums/edit/submit/", "extraData"), Action("routePanelForumsEditSubmit", "/panel/forums/edit/submit/", "extraData"),
Action("routePanelForumsEditPermsSubmit", "/panel/forums/edit/perms/submit/", "extraData"), Action("routePanelForumsEditPermsSubmit", "/panel/forums/edit/perms/submit/", "extraData"),
View("routePanelForumsEditPermsAdvance", "/panel/forums/edit/perms/", "extraData"),
View("routePanelSettings", "/panel/settings/"), View("routePanelSettings", "/panel/settings/"),
View("routePanelSettingEdit", "/panel/settings/edit/", "extraData"), View("routePanelSettingEdit", "/panel/settings/edit/", "extraData"),
@ -89,7 +90,7 @@ func buildPanelRoutes() {
View("routePanelUsersEdit", "/panel/users/edit/", "extraData"), View("routePanelUsersEdit", "/panel/users/edit/", "extraData"),
Action("routePanelUsersEditSubmit", "/panel/users/edit/submit/", "extraData"), Action("routePanelUsersEditSubmit", "/panel/users/edit/submit/", "extraData"),
View("routePanelAnalyticsViews", "/panel/analytics/views/"), View("routePanelAnalyticsViews", "/panel/analytics/views/").Before("ParseForm"),
View("routePanelAnalyticsRoutes", "/panel/analytics/routes/"), View("routePanelAnalyticsRoutes", "/panel/analytics/routes/"),
View("routePanelAnalyticsRouteViews", "/panel/analytics/route/", "extraData"), View("routePanelAnalyticsRouteViews", "/panel/analytics/route/", "extraData"),

View File

@ -2,9 +2,18 @@
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel-menu.html" . }} {{template "panel-menu.html" . }}
<main id="panel_dashboard_right" class="colstack_right"> <main id="panel_dashboard_right" class="colstack_right">
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/route/{{.Route}}" method="get">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><a>{{.Route}} Views</a></div> <div class="rowitem">
<a>{{.Route}} Views</a>
<select class="timeRangeSelector to_right" name="timeRange">
<option val="one-day"{{if eq .TimeRange "one-day"}} selected{{end}}>1 day</option>
<option val="twelve-hours"{{if eq .TimeRange "twelve-hours"}} selected{{end}}>12 hours</option>
<option val="six-hours"{{if eq .TimeRange "six-hours"}} selected{{end}}>6 hours</option>
</select>
</div> </div>
</div>
</form>
<div id="panel_analytics_views" class="colstack_graph_holder"> <div id="panel_analytics_views" class="colstack_graph_holder">
<div class="ct-chart"></div> <div class="ct-chart"></div>
</div> </div>

View File

@ -2,12 +2,29 @@
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel-menu.html" . }} {{template "panel-menu.html" . }}
<main id="panel_dashboard_right" class="colstack_right"> <main id="panel_dashboard_right" class="colstack_right">
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/views/" method="get">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><a>Views</a></div> <div class="rowitem">
<a>Views</a>
<select class="timeRangeSelector to_right" name="timeRange">
<option val="one-day"{{if eq .TimeRange "one-day"}} selected{{end}}>1 day</option>
<option val="twelve-hours"{{if eq .TimeRange "twelve-hours"}} selected{{end}}>12 hours</option>
<option val="six-hours"{{if eq .TimeRange "six-hours"}} selected{{end}}>6 hours</option>
</select>
</div> </div>
</div>
</form>
<div id="panel_analytics_views" class="colstack_graph_holder"> <div id="panel_analytics_views" class="colstack_graph_holder">
<div class="ct-chart"></div> <div class="ct_chart"></div>
</div> </div>
{{/**}}<div id="panel_analytics_views_table" class="colstack_item rowlist">
{{range .ViewItems}}
<div class="rowitem panel_compactrow editable_parent">
<a class="panel_upshift unix_to_minute_time">{{.Time}}</a>
<span class="panel_compacttext to_right">{{.Count}} views</span>
</div>
{{end}}
</div>{{**/}}
</main> </main>
</div> </div>
<script> <script>
@ -30,7 +47,7 @@ let seriesData = [{{range .PrimaryGraph.Series}}
]; ];
seriesData = seriesData.reverse(); seriesData = seriesData.reverse();
Chartist.Line('.ct-chart', { Chartist.Line('.ct_chart', {
labels: labels, labels: labels,
series: [seriesData], series: [seriesData],
}, { }, {

View File

@ -0,0 +1,31 @@
{{template "header.html" . }}
<div class="colstack panel_stack">
{{template "panel-menu.html" . }}
<main class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{.Name}} Forum</h1></div>
</div>
<form action="/panel/groups/edit/perms/submit/{{.ID}}?session={{.CurrentUser.Session}}" method="post">
<div class="colstack_item rowlist formlist panel_forum_perms">
{{range .Perms}}
<div class="formrow">
<div class="formitem">
<a>{{.LangStr}}</a>
<div class="to_right">
<select name="forum-perm-{{.Name}}">
<option{{if .Toggle}} selected{{end}} value=1>Yes</option>
<option{{if not .Toggle}} selected{{end}} value=0>No</option>
</select>
</div>
</div>
</div>
{{end}}
<div class="formrow">
<div class="formitem"><button name="panel-button" class="formbutton form_middle_button">Update Forum</button></div>
</div>
</div>
</form>
</main>
</div>
{{template "footer.html" . }}

View File

@ -16,13 +16,13 @@
<div class="rowitem"><h1>{{.Name}} Group</h1></div> <div class="rowitem"><h1>{{.Name}} Group</h1></div>
</div> </div>
<form action="/panel/groups/edit/perms/submit/{{.ID}}?session={{.CurrentUser.Session}}" method="post"> <form action="/panel/groups/edit/perms/submit/{{.ID}}?session={{.CurrentUser.Session}}" method="post">
<div id="panel_group" class="colstack_item rowlist formlist">
{{if .CurrentUser.Perms.EditGroupLocalPerms}} {{if .CurrentUser.Perms.EditGroupLocalPerms}}
<div class="colstack_item rowlist formlist panel_group_perms">
{{range .LocalPerms}} {{range .LocalPerms}}
<div class="formrow"> <div class="formrow">
<div class="formitem"> <div class="formitem">
<a>{{.LangStr}}</a> <a>{{.LangStr}}</a>
<div style="float: right;"> <div class="to_right">
<select name="group-perm-{{.Name}}"> <select name="group-perm-{{.Name}}">
<option{{if .Toggle}} selected{{end}} value=1>Yes</option> <option{{if .Toggle}} selected{{end}} value=1>Yes</option>
<option{{if not .Toggle}} selected{{end}} value=0>No</option> <option{{if not .Toggle}} selected{{end}} value=0>No</option>
@ -31,21 +31,21 @@
</div> </div>
</div> </div>
{{end}} {{end}}
{{end}}
<div class="formrow"> <div class="formrow">
<div class="formitem"><button name="panel-button" class="formbutton form_middle_button">Update Group</button></div> <div class="formitem"><button name="panel-button" class="formbutton form_middle_button">Update Group</button></div>
</div> </div>
</div> </div>
{{end}}
{{if .CurrentUser.Perms.EditGroupGlobalPerms}}
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>Extended Permissions</h1></div> <div class="rowitem"><h1>Extended Permissions</h1></div>
</div> </div>
<div class="colstack_item rowlist formlist"> <div class="colstack_item rowlist formlist panel_group_perms">
{{if .CurrentUser.Perms.EditGroupGlobalPerms}}
{{range .GlobalPerms}} {{range .GlobalPerms}}
<div class="formrow"> <div class="formrow">
<div class="formitem"> <div class="formitem">
<a>{{.LangStr}}</a> <a>{{.LangStr}}</a>
<div style="float: right;"> <div class="to_right">
<select name="group-perm-{{.Name}}"> <select name="group-perm-{{.Name}}">
<option{{if .Toggle}} selected{{end}} value=1>Yes</option> <option{{if .Toggle}} selected{{end}} value=1>Yes</option>
<option{{if not .Toggle}} selected{{end}} value=0>No</option> <option{{if not .Toggle}} selected{{end}} value=0>No</option>
@ -54,11 +54,11 @@
</div> </div>
</div> </div>
{{end}} {{end}}
{{end}}
<div class="formrow"> <div class="formrow">
<div class="formitem"><button name="panel-button" class="formbutton form_middle_button">Update Group</button></div> <div class="formitem"><button name="panel-button" class="formbutton form_middle_button">Update Group</button></div>
</div> </div>
</div> </div>
{{end}}
</form> </form>
</main> </main>
</div> </div>

View File

@ -92,6 +92,10 @@
display: none; display: none;
} }
.panel_group_perms .formitem a {
margin-top: 5px;
}
#panel_word_filters .itemSeparator:before { #panel_word_filters .itemSeparator:before {
content: "|"; content: "|";
padding-left: 5px; padding-left: 5px;
@ -164,8 +168,11 @@
padding-top: 16px; padding-top: 16px;
} }
.ct-series-a .ct-bar, .ct-series-a .ct-line, .ct-series-a .ct-point, .ct-series-a .ct-slice-donut { .ct-series-a .ct-bar, .ct-series-a .ct-line, .ct-series-a .ct-point, .ct-series-a .ct-slice-donut {
stroke: hsl(359,98%,53%) !important;
}
.ct-point {
stroke: hsl(359,98%,33%) !important; stroke: hsl(359,98%,33%) !important;
} }
.ct-point {
stroke: hsl(359,98%,53%) !important;
}
.ct-point:hover {
stroke: hsl(359,98%,50%) !important;
}