Renamed the pre_render_panel_mod_log pre-render hook to pre_render_panel_modlogs.

Added the half-second task type, you'll why later ;)
Reduced the amount of code duplication in the panel routes (saved a hundred lines).
Added the two day time range option for graphs.
We now track the discord, lynx and blank user agents.
Renamed some template files for consistency and to help stamp out some duplicate code.

Began work on topic move.
This commit is contained in:
Azareal 2018-01-11 08:03:17 +00:00
parent 5f5994726e
commit 5ba7aa74f7
34 changed files with 414 additions and 335 deletions

View File

@ -112,7 +112,7 @@ var PreRenderHooks = map[string][]func(http.ResponseWriter, *http.Request, *User
"pre_render_panel_edit_group": nil, "pre_render_panel_edit_group": nil,
"pre_render_panel_edit_group_perms": nil, "pre_render_panel_edit_group_perms": nil,
"pre_render_panel_themes": nil, "pre_render_panel_themes": nil,
"pre_render_panel_mod_log": nil, "pre_render_panel_modlogs": nil,
"pre_render_error": nil, // Note: This hook isn't run for a few errors whose templates are computed at startup and reused, such as InternalError. This hook is also not available in JS mode. "pre_render_error": nil, // Note: This hook isn't run for a few errors whose templates are computed at startup and reused, such as InternalError. This hook is also not available in JS mode.
"pre_render_security_error": nil, "pre_render_security_error": nil,

View File

@ -19,6 +19,7 @@ type TaskStmts struct {
getSync *sql.Stmt getSync *sql.Stmt
} }
var ScheduledHalfSecondTasks []func() error
var ScheduledSecondTasks []func() error var ScheduledSecondTasks []func() error
var ScheduledFifteenMinuteTasks []func() error var ScheduledFifteenMinuteTasks []func() error
var ShutdownTasks []func() error var ShutdownTasks []func() error
@ -37,6 +38,11 @@ func init() {
}) })
} }
// AddScheduledHalfSecondTask is not concurrency safe
func AddScheduledHalfSecondTask(task func() error) {
ScheduledHalfSecondTasks = append(ScheduledHalfSecondTasks, task)
}
// AddScheduledSecondTask is not concurrency safe // AddScheduledSecondTask is not concurrency safe
func AddScheduledSecondTask(task func() error) { func AddScheduledSecondTask(task func() error) {
ScheduledSecondTasks = append(ScheduledSecondTasks, task) ScheduledSecondTasks = append(ScheduledSecondTasks, task)

View File

@ -15,6 +15,7 @@ type Stmts struct {
isThemeDefault *sql.Stmt isThemeDefault *sql.Stmt
getModlogs *sql.Stmt getModlogs *sql.Stmt
getModlogsOffset *sql.Stmt getModlogsOffset *sql.Stmt
getAdminlogsOffset *sql.Stmt
getReplyTID *sql.Stmt getReplyTID *sql.Stmt
getTopicFID *sql.Stmt getTopicFID *sql.Stmt
getUserReplyUID *sql.Stmt getUserReplyUID *sql.Stmt
@ -122,6 +123,13 @@ func _gen_mssql() (err error) {
return err return err
} }
log.Print("Preparing getAdminlogsOffset statement.")
stmts.getAdminlogsOffset, err = db.Prepare("SELECT [action],[elementID],[elementType],[ipaddress],[actorID],[doneAt] FROM [administration_logs] ORDER BY doneAt DESC OFFSET ?1 ROWS FETCH NEXT ?2 ROWS ONLY")
if err != nil {
log.Print("Bad Query: ","SELECT [action],[elementID],[elementType],[ipaddress],[actorID],[doneAt] FROM [administration_logs] ORDER BY doneAt DESC OFFSET ?1 ROWS FETCH NEXT ?2 ROWS ONLY")
return err
}
log.Print("Preparing getReplyTID statement.") log.Print("Preparing getReplyTID statement.")
stmts.getReplyTID, err = db.Prepare("SELECT [tid] FROM [replies] WHERE [rid] = ?1") stmts.getReplyTID, err = db.Prepare("SELECT [tid] FROM [replies] WHERE [rid] = ?1")
if err != nil { if err != nil {

View File

@ -17,6 +17,7 @@ type Stmts struct {
isThemeDefault *sql.Stmt isThemeDefault *sql.Stmt
getModlogs *sql.Stmt getModlogs *sql.Stmt
getModlogsOffset *sql.Stmt getModlogsOffset *sql.Stmt
getAdminlogsOffset *sql.Stmt
getReplyTID *sql.Stmt getReplyTID *sql.Stmt
getTopicFID *sql.Stmt getTopicFID *sql.Stmt
getUserReplyUID *sql.Stmt getUserReplyUID *sql.Stmt
@ -118,6 +119,12 @@ func _gen_mysql() (err error) {
return err return err
} }
log.Print("Preparing getAdminlogsOffset statement.")
stmts.getAdminlogsOffset, err = db.Prepare("SELECT `action`,`elementID`,`elementType`,`ipaddress`,`actorID`,`doneAt` FROM `administration_logs` ORDER BY doneAt DESC LIMIT ?,?")
if err != nil {
return err
}
log.Print("Preparing getReplyTID statement.") log.Print("Preparing getReplyTID statement.")
stmts.getReplyTID, err = db.Prepare("SELECT `tid` FROM `replies` WHERE `rid` = ?") stmts.getReplyTID, err = db.Prepare("SELECT `tid` FROM `replies` WHERE `rid` = ?")
if err != nil { if err != nil {

View File

@ -233,6 +233,9 @@ var agentMapEnum = map[string]int{
"bing": 9, "bing": 9,
"baidu": 10, "baidu": 10,
"duckduckgo": 11, "duckduckgo": 11,
"discord": 12,
"lynx": 13,
"blank": 14,
} }
var reverseAgentMapEnum = map[int]string{ var reverseAgentMapEnum = map[int]string{
0: "unknown", 0: "unknown",
@ -247,6 +250,9 @@ var reverseAgentMapEnum = map[int]string{
9: "bing", 9: "bing",
10: "baidu", 10: "baidu",
11: "duckduckgo", 11: "duckduckgo",
12: "discord",
13: "lynx",
14: "blank",
} }
// TODO: Stop spilling these into the package scope? // TODO: Stop spilling these into the package scope?
@ -344,7 +350,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// Track the user agents. Unfortunately, everyone pretends to be Mozilla, so this'll be a little less efficient than I would like. // Track the user agents. Unfortunately, everyone pretends to be Mozilla, so this'll be a little less efficient than I would like.
// TODO: Add a setting to disable this? // TODO: Add a setting to disable this?
// TODO: Use a more efficient detector instead of smashing every possible combination in // TODO: Use a more efficient detector instead of smashing every possible combination in
ua := strings.TrimSuffix(strings.TrimPrefix(req.UserAgent(),"Mozilla/5.0 ")," Safari/537.36") // Noise, no one's going to be running this and it complicates implementing an efficient UA parser, particularly the more efficient right-to-left one I have in mind ua := strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(req.UserAgent(),"Mozilla/5.0 ")," Safari/537.36")) // Noise, no one's going to be running this and it complicates implementing an efficient UA parser, particularly the more efficient right-to-left one I have in mind
switch { switch {
case strings.Contains(ua,"Google"): case strings.Contains(ua,"Google"):
common.AgentViewCounter.Bump(7) common.AgentViewCounter.Bump(7)
@ -368,6 +374,12 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
common.AgentViewCounter.Bump(10) common.AgentViewCounter.Bump(10)
case strings.Contains(ua,"DuckDuckBot"): case strings.Contains(ua,"DuckDuckBot"):
common.AgentViewCounter.Bump(11) common.AgentViewCounter.Bump(11)
case strings.Contains(ua,"Discordbot"):
common.AgentViewCounter.Bump(12)
case strings.Contains(ua,"Lynx"):
common.AgentViewCounter.Bump(13)
case ua == "":
common.AgentViewCounter.Bump(14)
default: default:
common.AgentViewCounter.Bump(0) common.AgentViewCounter.Bump(0)
if common.Dev.DebugMode { if common.Dev.DebugMode {

View File

@ -69,23 +69,24 @@
"register":"Registration", "register":"Registration",
"ip-search":"IP Search", "ip-search":"IP Search",
"panel-dashboard":"Control Panel Dashboard", "panel_dashboard":"Control Panel Dashboard",
"panel-forums":"Forum Manager", "panel_forums":"Forum Manager",
"panel-delete-forum":"Delete Forum", "panel_delete_forum":"Delete Forum",
"panel-edit-forum":"Forum Editor", "panel_edit_forum":"Forum Editor",
"panel-analytics":"Analytics", "panel_analytics":"Analytics",
"panel-settings":"Setting Manager", "panel_settings":"Setting Manager",
"panel-edit-setting":"Edit Setting", "panel_edit_setting":"Edit Setting",
"panel-word-filters":"Word Filter Manager", "panel_word_filters":"Word Filter Manager",
"panel-edit-word-filter":"Edit Word Filter", "panel_edit_word_filter":"Edit Word Filter",
"panel-plugins":"Plugin Manager", "panel_plugins":"Plugin Manager",
"panel-users":"User Manager", "panel_users":"User Manager",
"panel-edit-user":"User Editor", "panel_edit_user":"User Editor",
"panel-groups":"Group Manager", "panel_groups":"Group Manager",
"panel-edit-group":"Group Editor", "panel_edit_group":"Group Editor",
"panel-themes":"Theme Manager", "panel_themes":"Theme Manager",
"panel-backups":"Backups", "panel_backups":"Backups",
"panel-mod-logs":"Moderation Logs", "panel_mod_logs":"Moderation Logs",
"panel-debug":"Debug" "panel_admin_logs":"Administration Logs",
"panel_debug":"Debug"
} }
} }

13
main.go
View File

@ -246,13 +246,18 @@ func main() {
} }
} }
// Run this goroutine once a second // Run this goroutine once every half second
halfSecondTicker := time.NewTicker(time.Second / 2)
secondTicker := time.NewTicker(1 * time.Second) secondTicker := time.NewTicker(1 * time.Second)
fifteenMinuteTicker := time.NewTicker(15 * time.Minute) fifteenMinuteTicker := time.NewTicker(15 * time.Minute)
//hourTicker := time.NewTicker(1 * time.Hour) //hourTicker := time.NewTicker(1 * time.Hour)
go func() { go func() {
for { for {
select { select {
case <-halfSecondTicker.C:
// TODO: Add a plugin hook here
runTasks(common.ScheduledHalfSecondTasks)
// TODO: Add a plugin hook here
case <-secondTicker.C: case <-secondTicker.C:
// TODO: Add a plugin hook here // TODO: Add a plugin hook here
runTasks(common.ScheduledSecondTasks) runTasks(common.ScheduledSecondTasks)
@ -291,13 +296,14 @@ func main() {
} }
}() }()
// TODO: Move these routes into the new routes list
log.Print("Initialising the router") log.Print("Initialising the router")
router = NewGenRouter(http.FileServer(http.Dir("./uploads"))) router = NewGenRouter(http.FileServer(http.Dir("./uploads")))
router.HandleFunc("/topic/create/submit/", routeTopicCreateSubmit) router.HandleFunc("/topic/create/submit/", routeTopicCreateSubmit)
router.HandleFunc("/topic/", routeTopicID) router.HandleFunc("/topic/", routeTopicID)
router.HandleFunc("/reply/create/", routeCreateReply) router.HandleFunc("/reply/create/", routeCreateReply)
//router.HandleFunc("/reply/edit/", routeReplyEdit) //router.HandleFunc("/reply/edit/", routeReplyEdit) // No js fallback
//router.HandleFunc("/reply/delete/", routeReplyDelete) //router.HandleFunc("/reply/delete/", routeReplyDelete) // No js confirmation page? We could have a confirmation modal for the JS case
router.HandleFunc("/reply/edit/submit/", routeReplyEditSubmit) router.HandleFunc("/reply/edit/submit/", routeReplyEditSubmit)
router.HandleFunc("/reply/delete/submit/", routeReplyDeleteSubmit) router.HandleFunc("/reply/delete/submit/", routeReplyDeleteSubmit)
router.HandleFunc("/reply/like/submit/", routeReplyLikeSubmit) router.HandleFunc("/reply/like/submit/", routeReplyLikeSubmit)
@ -307,6 +313,7 @@ func main() {
router.HandleFunc("/topic/unstick/submit/", routeUnstickTopic) router.HandleFunc("/topic/unstick/submit/", routeUnstickTopic)
router.HandleFunc("/topic/lock/submit/", routeLockTopic) router.HandleFunc("/topic/lock/submit/", routeLockTopic)
router.HandleFunc("/topic/unlock/submit/", routeUnlockTopic) router.HandleFunc("/topic/unlock/submit/", routeUnlockTopic)
router.HandleFunc("/topic/move/submit/", routeMoveTopic)
router.HandleFunc("/topic/like/submit/", routeLikeTopic) router.HandleFunc("/topic/like/submit/", routeLikeTopic)
// Accounts // Accounts

View File

@ -312,6 +312,48 @@ func routeUnlockTopic(w http.ResponseWriter, r *http.Request, user common.User)
return nil return nil
} }
func routeMoveTopic(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
return common.NoPermissions(w, r, user)
tid, err := strconv.Atoi(r.URL.Path[len("/topic/move/submit/"):])
if err != nil {
return common.PreError("The provided TopicID is not a valid number.", w, r)
}
topic, err := common.Topics.Get(tid)
if err == ErrNoRows {
return common.PreError("The topic you tried to move doesn't exist.", w, r)
} else if err != nil {
return common.InternalError(err, w, r)
}
// TODO: Add hooks to make use of headerLite
_, ferr := common.SimpleForumUserCheck(w, r, &user, topic.ParentID)
if ferr != nil {
return ferr
}
if !user.Perms.ViewTopic { // TODO: MoveTopic permission?
return common.NoPermissions(w, r, user)
}
err = topic.Unlock()
if err != nil {
return common.InternalError(err, w, r)
}
err = common.ModLogs.Create("move", tid, "topic", user.LastIP, user.ID)
if err != nil {
return common.InternalError(err, w, r)
}
err = topic.CreateActionReply("move", user.LastIP, user)
if err != nil {
return common.InternalError(err, w, r)
}
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
return nil
}
// TODO: Disable stat updates in posts handled by plugin_guilds // TODO: Disable stat updates in posts handled by plugin_guilds
// TODO: Update the stats after edits so that we don't under or over decrement stats during deletes // TODO: Update the stats after edits so that we don't under or over decrement stats during deletes
func routeReplyEditSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { func routeReplyEditSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {

View File

@ -30,20 +30,18 @@ func panelSuccessRedirect(dest string, w http.ResponseWriter, r *http.Request, i
} }
return nil return nil
} }
func panelRenderTemplate(tmplName string, w http.ResponseWriter, r *http.Request, user common.User, pi interface{}) common.RouteError {
// TODO: Implement this properly
/*func panelRenderTemplate(tmplName string, w http.ResponseWriter, r *http.Request, user common.User, pi interface{}) common.RouteError {
if common.PreRenderHooks["pre_render_"+tmplName] != nil { if common.PreRenderHooks["pre_render_"+tmplName] != nil {
if common.RunPreRenderHook("pre_render_"+tmplName, w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_"+tmplName, w, r, &user, pi) {
return nil return nil
} }
} }
err = common.Templates.ExecuteTemplate(w, tmplName+".html", pi) err := common.Templates.ExecuteTemplate(w, tmplName+".html", pi)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
return nil return nil
}*/ }
func routePanel(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { func routePanel(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)
@ -170,18 +168,8 @@ func routePanel(w http.ResponseWriter, r *http.Request, user common.User) common
gridElements = append(gridElements, common.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, common.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 := common.PanelDashboardPage{common.GetTitlePhrase("panel-dashboard"), user, headerVars, stats, "dashboard", gridElements} pi := common.PanelDashboardPage{common.GetTitlePhrase("panel_dashboard"), user, headerVars, stats, "dashboard", gridElements}
if common.PreRenderHooks["pre_render_panel_dashboard"] != nil { return panelRenderTemplate("panel_dashboard", w, r, user, &pi)
if common.RunPreRenderHook("pre_render_panel_dashboard", w, r, &user, &pi) {
return nil
}
}
err = common.Templates.ExecuteTemplate(w, "panel_dashboard.html", pi)
if err != nil {
return common.InternalError(err, w, r)
}
return nil
//return panelRenderTemplate("panel_dashboard",w,r,user,pi)
} }
func routePanelForums(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { func routePanelForums(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
@ -210,18 +198,8 @@ func routePanelForums(w http.ResponseWriter, r *http.Request, user common.User)
forumList = append(forumList, fadmin) forumList = append(forumList, fadmin)
} }
} }
pi := common.PanelPage{common.GetTitlePhrase("panel-forums"), user, headerVars, stats, "forums", forumList, nil} pi := common.PanelPage{common.GetTitlePhrase("panel_forums"), user, headerVars, stats, "forums", forumList, nil}
if common.PreRenderHooks["pre_render_panel_forums"] != nil { return panelRenderTemplate("panel_forums", w, r, user, &pi)
if common.RunPreRenderHook("pre_render_panel_forums", w, r, &user, &pi) {
return nil
}
}
err = common.Templates.ExecuteTemplate(w, "panel-forums.html", pi)
if err != nil {
return common.InternalError(err, w, r)
}
return nil
} }
func routePanelForumsCreateSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { func routePanelForumsCreateSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
@ -274,7 +252,7 @@ func routePanelForumsDelete(w http.ResponseWriter, r *http.Request, user common.
confirmMsg := "Are you sure you want to delete the '" + forum.Name + "' forum?" confirmMsg := "Are you sure you want to delete the '" + forum.Name + "' forum?"
yousure := common.AreYouSure{"/panel/forums/delete/submit/" + strconv.Itoa(fid), confirmMsg} yousure := common.AreYouSure{"/panel/forums/delete/submit/" + strconv.Itoa(fid), confirmMsg}
pi := common.PanelPage{common.GetTitlePhrase("panel-delete-forum"), user, headerVars, stats, "forums", tList, yousure} pi := common.PanelPage{common.GetTitlePhrase("panel_delete_forum"), user, headerVars, stats, "forums", tList, yousure}
if common.PreRenderHooks["pre_render_panel_delete_forum"] != nil { if common.PreRenderHooks["pre_render_panel_delete_forum"] != nil {
if common.RunPreRenderHook("pre_render_panel_delete_forum", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_panel_delete_forum", w, r, &user, &pi) {
return nil return nil
@ -351,7 +329,7 @@ func routePanelForumsEdit(w http.ResponseWriter, r *http.Request, user common.Us
gplist = append(gplist, common.GroupForumPermPreset{group, common.ForumPermsToGroupForumPreset(group.Forums[fid])}) 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} 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.PreRenderHooks["pre_render_panel_edit_forum"] != nil {
if common.RunPreRenderHook("pre_render_panel_edit_forum", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_panel_edit_forum", w, r, &user, &pi) {
return nil return nil
@ -513,7 +491,7 @@ func routePanelForumsEditPermsAdvance(w http.ResponseWriter, r *http.Request, us
addNameLangToggle("PinTopic", forumPerms.PinTopic) addNameLangToggle("PinTopic", forumPerms.PinTopic)
addNameLangToggle("CloseTopic", forumPerms.CloseTopic) addNameLangToggle("CloseTopic", forumPerms.CloseTopic)
pi := common.PanelEditForumGroupPage{common.GetTitlePhrase("panel-edit-forum"), user, headerVars, stats, "forums", forum.ID, gid, forum.Name, forum.Desc, forum.Active, forum.Preset, formattedPermList} pi := common.PanelEditForumGroupPage{common.GetTitlePhrase("panel_edit_forum"), user, headerVars, stats, "forums", forum.ID, gid, forum.Name, forum.Desc, forum.Active, forum.Preset, formattedPermList}
if common.PreRenderHooks["pre_render_panel_edit_forum"] != nil { if common.PreRenderHooks["pre_render_panel_edit_forum"] != nil {
if common.RunPreRenderHook("pre_render_panel_edit_forum", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_panel_edit_forum", w, r, &user, &pi) {
return nil return nil
@ -596,6 +574,12 @@ func routePanelAnalyticsViews(w http.ResponseWriter, r *http.Request, user commo
var timeRange = "six-hours" var timeRange = "six-hours"
switch r.FormValue("timeRange") { switch r.FormValue("timeRange") {
case "two-days": // Two days is experimental
timeQuantity = 2
timeUnit = "day"
timeSlices = 24
sliceWidth = 60 * 60 * 2
timeRange = "two-days"
case "one-day": case "one-day":
timeQuantity = 1 timeQuantity = 1
timeUnit = "day" timeUnit = "day"
@ -668,7 +652,7 @@ func routePanelAnalyticsViews(w http.ResponseWriter, r *http.Request, user commo
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, viewItems, timeRange} 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
@ -696,6 +680,12 @@ func routePanelAnalyticsRouteViews(w http.ResponseWriter, r *http.Request, user
var timeRange = "six-hours" var timeRange = "six-hours"
switch r.FormValue("timeRange") { switch r.FormValue("timeRange") {
case "two-days": // Two days is experimental
timeQuantity = 2
timeUnit = "day"
timeSlices = 24
sliceWidth = 60 * 60 * 2
timeRange = "two-days"
case "one-day": case "one-day":
timeQuantity = 1 timeQuantity = 1
timeUnit = "day" timeUnit = "day"
@ -767,17 +757,8 @@ 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, timeRange} 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 { return panelRenderTemplate("panel_analytics_route_views", w, r, user, &pi)
if common.RunPreRenderHook("pre_render_panel_analytics_route_views", w, r, &user, &pi) {
return nil
}
}
err = common.Templates.ExecuteTemplate(w, "panel-analytics-route-views.html", pi)
if err != nil {
return common.InternalError(err, w, r)
}
return nil
} }
func routePanelAnalyticsAgentViews(w http.ResponseWriter, r *http.Request, user common.User, agent string) common.RouteError { func routePanelAnalyticsAgentViews(w http.ResponseWriter, r *http.Request, user common.User, agent string) common.RouteError {
@ -795,6 +776,12 @@ func routePanelAnalyticsAgentViews(w http.ResponseWriter, r *http.Request, user
var timeRange = "six-hours" var timeRange = "six-hours"
switch r.FormValue("timeRange") { switch r.FormValue("timeRange") {
case "two-days": // Two days is experimental
timeQuantity = 2
timeUnit = "day"
timeSlices = 24
sliceWidth = 60 * 60 * 2
timeRange = "two-days"
case "one-day": case "one-day":
timeQuantity = 1 timeQuantity = 1
timeUnit = "day" timeUnit = "day"
@ -866,17 +853,8 @@ func routePanelAnalyticsAgentViews(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.PanelAnalyticsAgentPage{common.GetTitlePhrase("panel-analytics"), user, headerVars, stats, "analytics", html.EscapeString(agent), graph, timeRange} pi := common.PanelAnalyticsAgentPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", html.EscapeString(agent), graph, timeRange}
if common.PreRenderHooks["pre_render_panel_analytics_agent_views"] != nil { return panelRenderTemplate("panel_analytics_agent_views", w, r, user, &pi)
if common.RunPreRenderHook("pre_render_panel_analytics_agent_views", w, r, &user, &pi) {
return nil
}
}
err = common.Templates.ExecuteTemplate(w, "panel-analytics-agent-views.html", pi)
if err != nil {
return common.InternalError(err, w, r)
}
return nil
} }
func routePanelAnalyticsRoutes(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { func routePanelAnalyticsRoutes(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
@ -919,17 +897,8 @@ func routePanelAnalyticsRoutes(w http.ResponseWriter, r *http.Request, user comm
}) })
} }
pi := common.PanelAnalyticsRoutesPage{common.GetTitlePhrase("panel-analytics"), user, headerVars, stats, "analytics", routeItems} pi := common.PanelAnalyticsRoutesPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", routeItems}
if common.PreRenderHooks["pre_render_panel_analytics_routes"] != nil { return panelRenderTemplate("panel_analytics_routes", w, r, user, &pi)
if common.RunPreRenderHook("pre_render_panel_analytics_routes", w, r, &user, &pi) {
return nil
}
}
err = common.Templates.ExecuteTemplate(w, "panel-analytics-routes.html", pi)
if err != nil {
return common.InternalError(err, w, r)
}
return nil
} }
func routePanelAnalyticsAgents(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { func routePanelAnalyticsAgents(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
@ -972,17 +941,8 @@ func routePanelAnalyticsAgents(w http.ResponseWriter, r *http.Request, user comm
}) })
} }
pi := common.PanelAnalyticsAgentsPage{common.GetTitlePhrase("panel-analytics"), user, headerVars, stats, "analytics", agentItems} pi := common.PanelAnalyticsAgentsPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", agentItems}
if common.PreRenderHooks["pre_render_panel_analytics_agents"] != nil { return panelRenderTemplate("panel_analytics_agents", w, r, user, &pi)
if common.RunPreRenderHook("pre_render_panel_analytics_agents", w, r, &user, &pi) {
return nil
}
}
err = common.Templates.ExecuteTemplate(w, "panel-analytics-agents.html", pi)
if err != nil {
return common.InternalError(err, w, r)
}
return nil
} }
func routePanelSettings(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { func routePanelSettings(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
@ -1021,17 +981,8 @@ func routePanelSettings(w http.ResponseWriter, r *http.Request, user common.User
settingList[setting.Name] = setting.Content settingList[setting.Name] = setting.Content
} }
pi := common.PanelPage{common.GetTitlePhrase("panel-settings"), user, headerVars, stats, "settings", tList, settingList} pi := common.PanelPage{common.GetTitlePhrase("panel_settings"), user, headerVars, stats, "settings", tList, settingList}
if common.PreRenderHooks["pre_render_panel_settings"] != nil { return panelRenderTemplate("panel_settings", w, r, user, &pi)
if common.RunPreRenderHook("pre_render_panel_settings", w, r, &user, &pi) {
return nil
}
}
err = common.Templates.ExecuteTemplate(w, "panel-settings.html", pi)
if err != nil {
return common.InternalError(err, w, r)
}
return nil
} }
func routePanelSettingEdit(w http.ResponseWriter, r *http.Request, user common.User, sname string) common.RouteError { func routePanelSettingEdit(w http.ResponseWriter, r *http.Request, user common.User, sname string) common.RouteError {
@ -1067,17 +1018,8 @@ func routePanelSettingEdit(w http.ResponseWriter, r *http.Request, user common.U
} }
} }
pi := common.PanelPage{common.GetTitlePhrase("panel-edit-setting"), user, headerVars, stats, "settings", itemList, setting} pi := common.PanelPage{common.GetTitlePhrase("panel_edit_setting"), user, headerVars, stats, "settings", itemList, setting}
if common.PreRenderHooks["pre_render_panel_setting"] != nil { return panelRenderTemplate("panel_setting", w, r, user, &pi)
if common.RunPreRenderHook("pre_render_panel_setting", w, r, &user, &pi) {
return nil
}
}
err = common.Templates.ExecuteTemplate(w, "panel-setting.html", pi)
if err != nil {
return common.InternalError(err, w, r)
}
return nil
} }
func routePanelSettingEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, sname string) common.RouteError { func routePanelSettingEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, sname string) common.RouteError {
@ -1112,17 +1054,8 @@ func routePanelWordFilters(w http.ResponseWriter, r *http.Request, user common.U
} }
var filterList = common.WordFilterBox.Load().(common.WordFilterMap) var filterList = common.WordFilterBox.Load().(common.WordFilterMap)
pi := common.PanelPage{common.GetTitlePhrase("panel-word-filters"), user, headerVars, stats, "word-filters", tList, filterList} pi := common.PanelPage{common.GetTitlePhrase("panel_word_filters"), user, headerVars, stats, "word-filters", tList, filterList}
if common.PreRenderHooks["pre_render_panel_word_filters"] != nil { return panelRenderTemplate("panel_word_filters", w, r, user, &pi)
if common.RunPreRenderHook("pre_render_panel_word_filters", w, r, &user, &pi) {
return nil
}
}
err := common.Templates.ExecuteTemplate(w, "panel-word-filters.html", pi)
if err != nil {
return common.InternalError(err, w, r)
}
return nil
} }
func routePanelWordFiltersCreate(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { func routePanelWordFiltersCreate(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
@ -1156,6 +1089,7 @@ func routePanelWordFiltersCreate(w http.ResponseWriter, r *http.Request, user co
return panelSuccessRedirect("/panel/settings/word-filters/", w, r, isJs) return panelSuccessRedirect("/panel/settings/word-filters/", w, r, isJs)
} }
// TODO: Implement this as a non-JS fallback
func routePanelWordFiltersEdit(w http.ResponseWriter, r *http.Request, user common.User, wfid string) common.RouteError { func routePanelWordFiltersEdit(w http.ResponseWriter, r *http.Request, user common.User, wfid string) common.RouteError {
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user) headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
if ferr != nil { if ferr != nil {
@ -1167,17 +1101,8 @@ func routePanelWordFiltersEdit(w http.ResponseWriter, r *http.Request, user comm
_ = wfid _ = wfid
pi := common.PanelPage{common.GetTitlePhrase("panel-edit-word-filter"), user, headerVars, stats, "word-filters", tList, nil} pi := common.PanelPage{common.GetTitlePhrase("panel_edit_word_filter"), user, headerVars, stats, "word-filters", tList, nil}
if common.PreRenderHooks["pre_render_panel_word_filters_edit"] != nil { return panelRenderTemplate("panel_word_filters_edit", w, r, user, &pi)
if common.RunPreRenderHook("pre_render_panel_word_filters_edit", w, r, &user, &pi) {
return nil
}
}
err := common.Templates.ExecuteTemplate(w, "panel-word-filters-edit.html", pi)
if err != nil {
return common.InternalError(err, w, r)
}
return nil
} }
func routePanelWordFiltersEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, wfid string) common.RouteError { func routePanelWordFiltersEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, wfid string) common.RouteError {
@ -1260,17 +1185,8 @@ func routePanelPlugins(w http.ResponseWriter, r *http.Request, user common.User)
pluginList = append(pluginList, plugin) pluginList = append(pluginList, plugin)
} }
pi := common.PanelPage{common.GetTitlePhrase("panel-plugins"), user, headerVars, stats, "plugins", pluginList, nil} pi := common.PanelPage{common.GetTitlePhrase("panel_plugins"), user, headerVars, stats, "plugins", pluginList, nil}
if common.PreRenderHooks["pre_render_panel_plugins"] != nil { return panelRenderTemplate("panel_plugins", w, r, user, &pi)
if common.RunPreRenderHook("pre_render_panel_plugins", w, r, &user, &pi) {
return nil
}
}
err := common.Templates.ExecuteTemplate(w, "panel-plugins.html", pi)
if err != nil {
return common.InternalError(err, w, r)
}
return nil
} }
func routePanelPluginsActivate(w http.ResponseWriter, r *http.Request, user common.User, uname string) common.RouteError { func routePanelPluginsActivate(w http.ResponseWriter, r *http.Request, user common.User, uname string) common.RouteError {
@ -1481,17 +1397,8 @@ func routePanelUsers(w http.ResponseWriter, r *http.Request, user common.User) c
} }
pageList := common.Paginate(stats.Users, perPage, 5) pageList := common.Paginate(stats.Users, perPage, 5)
pi := common.PanelUserPage{common.GetTitlePhrase("panel-users"), user, headerVars, stats, "users", userList, pageList, page, lastPage} pi := common.PanelUserPage{common.GetTitlePhrase("panel_users"), user, headerVars, stats, "users", userList, pageList, page, lastPage}
if common.PreRenderHooks["pre_render_panel_users"] != nil { return panelRenderTemplate("panel_users", w, r, user, &pi)
if common.RunPreRenderHook("pre_render_panel_users", w, r, &user, &pi) {
return nil
}
}
err = common.Templates.ExecuteTemplate(w, "panel-users.html", pi)
if err != nil {
return common.InternalError(err, w, r)
}
return nil
} }
func routePanelUsersEdit(w http.ResponseWriter, r *http.Request, user common.User, suid string) common.RouteError { func routePanelUsersEdit(w http.ResponseWriter, r *http.Request, user common.User, suid string) common.RouteError {
@ -1536,7 +1443,7 @@ func routePanelUsersEdit(w http.ResponseWriter, r *http.Request, user common.Use
groupList = append(groupList, group) groupList = append(groupList, group)
} }
pi := common.PanelPage{common.GetTitlePhrase("panel-edit-user"), user, headerVars, stats, "users", groupList, targetUser} pi := common.PanelPage{common.GetTitlePhrase("panel_edit_user"), user, headerVars, stats, "users", groupList, targetUser}
if common.PreRenderHooks["pre_render_panel_edit_user"] != nil { if common.PreRenderHooks["pre_render_panel_edit_user"] != nil {
if common.RunPreRenderHook("pre_render_panel_edit_user", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_panel_edit_user", w, r, &user, &pi) {
return nil return nil
@ -1679,18 +1586,8 @@ func routePanelGroups(w http.ResponseWriter, r *http.Request, user common.User)
//log.Printf("groupList: %+v\n", groupList) //log.Printf("groupList: %+v\n", groupList)
pageList := common.Paginate(stats.Groups, perPage, 5) pageList := common.Paginate(stats.Groups, perPage, 5)
pi := common.PanelGroupPage{common.GetTitlePhrase("panel-groups"), user, headerVars, stats, "groups", groupList, pageList, page, lastPage} pi := common.PanelGroupPage{common.GetTitlePhrase("panel_groups"), user, headerVars, stats, "groups", groupList, pageList, page, lastPage}
if common.PreRenderHooks["pre_render_panel_groups"] != nil { return panelRenderTemplate("panel_groups", w, r, user, &pi)
if common.RunPreRenderHook("pre_render_panel_groups", w, r, &user, &pi) {
return nil
}
}
err := common.Templates.ExecuteTemplate(w, "panel-groups.html", pi)
if err != nil {
return common.InternalError(err, w, r)
}
return nil
} }
func routePanelGroupsEdit(w http.ResponseWriter, r *http.Request, user common.User, sgid string) common.RouteError { func routePanelGroupsEdit(w http.ResponseWriter, r *http.Request, user common.User, sgid string) common.RouteError {
@ -1738,7 +1635,7 @@ func routePanelGroupsEdit(w http.ResponseWriter, r *http.Request, user common.Us
disableRank := !user.Perms.EditGroupGlobalPerms || (group.ID == 6) disableRank := !user.Perms.EditGroupGlobalPerms || (group.ID == 6)
pi := common.PanelEditGroupPage{common.GetTitlePhrase("panel-edit-group"), user, headerVars, stats, "groups", group.ID, group.Name, group.Tag, rank, disableRank} pi := common.PanelEditGroupPage{common.GetTitlePhrase("panel_edit_group"), user, headerVars, stats, "groups", group.ID, group.Name, group.Tag, rank, disableRank}
if common.PreRenderHooks["pre_render_panel_edit_group"] != nil { if common.PreRenderHooks["pre_render_panel_edit_group"] != nil {
if common.RunPreRenderHook("pre_render_panel_edit_group", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_panel_edit_group", w, r, &user, &pi) {
return nil return nil
@ -1825,7 +1722,7 @@ func routePanelGroupsEditPerms(w http.ResponseWriter, r *http.Request, user comm
addGlobalPerm("ViewIPs", group.Perms.ViewIPs) addGlobalPerm("ViewIPs", group.Perms.ViewIPs)
addGlobalPerm("UploadFiles", group.Perms.UploadFiles) addGlobalPerm("UploadFiles", group.Perms.UploadFiles)
pi := common.PanelEditGroupPermsPage{common.GetTitlePhrase("panel-edit-group"), user, headerVars, stats, "groups", group.ID, group.Name, localPerms, globalPerms} pi := common.PanelEditGroupPermsPage{common.GetTitlePhrase("panel_edit_group"), user, headerVars, stats, "groups", group.ID, group.Name, localPerms, globalPerms}
if common.PreRenderHooks["pre_render_panel_edit_group_perms"] != nil { if common.PreRenderHooks["pre_render_panel_edit_group_perms"] != nil {
if common.RunPreRenderHook("pre_render_panel_edit_group_perms", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_panel_edit_group_perms", w, r, &user, &pi) {
return nil return nil
@ -2055,17 +1952,8 @@ func routePanelThemes(w http.ResponseWriter, r *http.Request, user common.User)
} }
pi := common.PanelThemesPage{common.GetTitlePhrase("panel-themes"), user, headerVars, stats, "themes", pThemeList, vThemeList} pi := common.PanelThemesPage{common.GetTitlePhrase("panel_themes"), user, headerVars, stats, "themes", pThemeList, vThemeList}
if common.PreRenderHooks["pre_render_panel_themes"] != nil { return panelRenderTemplate("panel_themes", w, r, user, &pi)
if common.RunPreRenderHook("pre_render_panel_themes", w, r, &user, &pi) {
return nil
}
}
err := common.Templates.ExecuteTemplate(w, "panel-themes.html", pi)
if err != nil {
return common.InternalError(err, w, r)
}
return nil
} }
func routePanelThemesSetDefault(w http.ResponseWriter, r *http.Request, user common.User, uname string) common.RouteError { func routePanelThemesSetDefault(w http.ResponseWriter, r *http.Request, user common.User, uname string) common.RouteError {
@ -2172,12 +2060,8 @@ func routePanelBackups(w http.ResponseWriter, r *http.Request, user common.User,
backupList = append(backupList, common.BackupItem{backupFile.Name(), backupFile.ModTime()}) backupList = append(backupList, common.BackupItem{backupFile.Name(), backupFile.ModTime()})
} }
pi := common.PanelBackupPage{common.GetTitlePhrase("panel-backups"), user, headerVars, stats, "backups", backupList} pi := common.PanelBackupPage{common.GetTitlePhrase("panel_backups"), user, headerVars, stats, "backups", backupList}
err = common.Templates.ExecuteTemplate(w, "panel-backups.html", pi) return panelRenderTemplate("panel_backups", w, r, user, &pi)
if err != nil {
return common.InternalError(err, w, r)
}
return nil
} }
// TODO: Log errors when something really screwy is going on? // TODO: Log errors when something really screwy is going on?
@ -2270,17 +2154,48 @@ func routePanelLogsMod(w http.ResponseWriter, r *http.Request, user common.User)
} }
pageList := common.Paginate(logCount, perPage, 5) pageList := common.Paginate(logCount, perPage, 5)
pi := common.PanelLogsPage{common.GetTitlePhrase("panel-mod-logs"), user, headerVars, stats, "logs", logs, pageList, page, lastPage} pi := common.PanelLogsPage{common.GetTitlePhrase("panel_mod_logs"), user, headerVars, stats, "logs", logs, pageList, page, lastPage}
if common.PreRenderHooks["pre_render_panel_mod_log"] != nil { return panelRenderTemplate("panel_modlogs", w, r, user, &pi)
if common.RunPreRenderHook("pre_render_panel_mod_log", w, r, &user, &pi) { }
return nil
func routePanelLogsAdmin(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
if ferr != nil {
return ferr
} }
}
err = common.Templates.ExecuteTemplate(w, "panel-modlogs.html", pi) logCount := common.ModLogs.GlobalCount()
page, _ := strconv.Atoi(r.FormValue("page"))
perPage := 10
offset, page, lastPage := common.PageOffset(logCount, page, perPage)
rows, err := stmts.getAdminlogsOffset.Query(offset, perPage)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
return nil defer rows.Close()
var logs []common.LogItem
var action, elementType, ipaddress, doneAt string
var elementID, actorID int
for rows.Next() {
err := rows.Scan(&action, &elementID, &elementType, &ipaddress, &actorID, &doneAt)
if err != nil {
return common.InternalError(err, w, r)
}
actor := handleUnknownUser(common.Users.Get(actorID))
action = modlogsElementType(action, elementType, elementID, actor)
logs = append(logs, common.LogItem{Action: template.HTML(action), IPAddress: ipaddress, DoneAt: doneAt})
}
err = rows.Err()
if err != nil {
return common.InternalError(err, w, r)
}
pageList := common.Paginate(logCount, perPage, 5)
pi := common.PanelLogsPage{common.GetTitlePhrase("panel_admin_logs"), user, headerVars, stats, "logs", logs, pageList, page, lastPage}
return panelRenderTemplate("panel_adminlogs", w, r, user, &pi)
} }
func routePanelDebug(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { func routePanelDebug(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
@ -2294,10 +2209,6 @@ func routePanelDebug(w http.ResponseWriter, r *http.Request, user common.User) c
openConnCount := dbStats.OpenConnections openConnCount := dbStats.OpenConnections
// Disk I/O? // Disk I/O?
pi := common.PanelDebugPage{common.GetTitlePhrase("panel-debug"), user, headerVars, stats, "debug", uptime, openConnCount, dbAdapter} pi := common.PanelDebugPage{common.GetTitlePhrase("panel_debug"), user, headerVars, stats, "debug", uptime, openConnCount, dbAdapter}
err := common.Templates.ExecuteTemplate(w, "panel-debug.html", pi) return panelRenderTemplate("panel_debug", w, r, user, &pi)
if err != nil {
return common.InternalError(err, w, r)
}
return nil
} }

View File

@ -507,6 +507,14 @@ $(document).ready(function(){
let action = optionNode.getAttribute("val"); let action = optionNode.getAttribute("val");
//console.log("action",action); //console.log("action",action);
// Handle these specially
switch(action) {
case "move":
console.log("move action");
$("#mod_topic_mover").removeClass("auto_hide");
return;
}
let url = "/topic/"+action+"/submit/"; let url = "/topic/"+action+"/submit/";
//console.log("JSON.stringify(selectedTopics) ", JSON.stringify(selectedTopics)); //console.log("JSON.stringify(selectedTopics) ", JSON.stringify(selectedTopics));
$.ajax({ $.ajax({

View File

@ -230,6 +230,8 @@ func writeSelects(adapter qgen.Adapter) error {
build.Select("getModlogsOffset").Table("moderation_logs").Columns("action, elementID, elementType, ipaddress, actorID, doneAt").Orderby("doneAt DESC").Limit("?,?").Parse() build.Select("getModlogsOffset").Table("moderation_logs").Columns("action, elementID, elementType, ipaddress, actorID, doneAt").Orderby("doneAt DESC").Limit("?,?").Parse()
build.Select("getAdminlogsOffset").Table("administration_logs").Columns("action, elementID, elementType, ipaddress, actorID, doneAt").Orderby("doneAt DESC").Limit("?,?").Parse()
build.Select("getReplyTID").Table("replies").Columns("tid").Where("rid = ?").Parse() build.Select("getReplyTID").Table("replies").Columns("tid").Where("rid = ?").Parse()
build.Select("getTopicFID").Table("topics").Columns("parentID").Where("tid = ?").Parse() build.Select("getTopicFID").Table("topics").Columns("parentID").Where("tid = ?").Parse()

View File

@ -171,6 +171,9 @@ func main() {
"bing", "bing",
"baidu", "baidu",
"duckduckgo", "duckduckgo",
"discord",
"lynx",
"blank",
} }
tmplVars.AllAgentMap = make(map[string]int) tmplVars.AllAgentMap = make(map[string]int)
@ -307,7 +310,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// Track the user agents. Unfortunately, everyone pretends to be Mozilla, so this'll be a little less efficient than I would like. // Track the user agents. Unfortunately, everyone pretends to be Mozilla, so this'll be a little less efficient than I would like.
// TODO: Add a setting to disable this? // TODO: Add a setting to disable this?
// TODO: Use a more efficient detector instead of smashing every possible combination in // TODO: Use a more efficient detector instead of smashing every possible combination in
ua := strings.TrimSuffix(strings.TrimPrefix(req.UserAgent(),"Mozilla/5.0 ")," Safari/537.36") // Noise, no one's going to be running this and it complicates implementing an efficient UA parser, particularly the more efficient right-to-left one I have in mind ua := strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(req.UserAgent(),"Mozilla/5.0 ")," Safari/537.36")) // Noise, no one's going to be running this and it complicates implementing an efficient UA parser, particularly the more efficient right-to-left one I have in mind
switch { switch {
case strings.Contains(ua,"Google"): case strings.Contains(ua,"Google"):
common.AgentViewCounter.Bump({{.AllAgentMap.googlebot}}) common.AgentViewCounter.Bump({{.AllAgentMap.googlebot}})
@ -331,6 +334,12 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
common.AgentViewCounter.Bump({{.AllAgentMap.baidu}}) common.AgentViewCounter.Bump({{.AllAgentMap.baidu}})
case strings.Contains(ua,"DuckDuckBot"): case strings.Contains(ua,"DuckDuckBot"):
common.AgentViewCounter.Bump({{.AllAgentMap.duckduckgo}}) common.AgentViewCounter.Bump({{.AllAgentMap.duckduckgo}})
case strings.Contains(ua,"Discordbot"):
common.AgentViewCounter.Bump({{.AllAgentMap.discord}})
case strings.Contains(ua,"Lynx"):
common.AgentViewCounter.Bump({{.AllAgentMap.lynx}})
case ua == "":
common.AgentViewCounter.Bump({{.AllAgentMap.blank}})
default: default:
common.AgentViewCounter.Bump({{.AllAgentMap.unknown}}) common.AgentViewCounter.Bump({{.AllAgentMap.unknown}})
if common.Dev.DebugMode { if common.Dev.DebugMode {

View File

@ -811,6 +811,7 @@ var topics_8 = []byte(`
<select class="mod_floater_options"> <select class="mod_floater_options">
<option val="delete">Delete them</option> <option val="delete">Delete them</option>
<option val="lock">Lock them</option> <option val="lock">Lock them</option>
<option val="move">Move them</option>
</select> </select>
<button class="mod_floater_submit">Run</button> <button class="mod_floater_submit">Run</button>
</div> </div>
@ -819,21 +820,40 @@ var topics_8 = []byte(`
`) `)
var topics_9 = []byte(` var topics_9 = []byte(`
<div id="mod_topic_mover" class="modal_pane auto_hide">
<form action="/topic/move/submit/" method="post">
<div class="pane_header">
<h3>Move Topics (3)</h3>
</div>
<div class="pane_body">
<div class="pane_table">
<div class="pane_row"></div>
`)
var topics_10 = []byte(`<div class="pane_row">`)
var topics_11 = []byte(`</div>`)
var topics_12 = []byte(`
</div>
</div>
<div class="pane_buttons">
<button>Move Topics</button>
</div>
</form>
</div>
<div class="rowblock topic_create_form quick_create_form" style="display: none;" aria-label="Quick Topic Form"> <div class="rowblock topic_create_form quick_create_form" style="display: none;" aria-label="Quick Topic Form">
<form name="topic_create_form_form" id="topic_create_form_form" enctype="multipart/form-data" action="/topic/create/submit/" method="post"></form> <form name="topic_create_form_form" id="topic_create_form_form" enctype="multipart/form-data" action="/topic/create/submit/" method="post"></form>
<img class="little_row_avatar" src="`) <img class="little_row_avatar" src="`)
var topics_10 = []byte(`" height="64" alt="Your Avatar" title="Your Avatar" /> var topics_13 = []byte(`" height="64" alt="Your Avatar" title="Your Avatar" />
<div class="main_form"> <div class="main_form">
<div class="topic_meta"> <div class="topic_meta">
<div class="formrow topic_board_row real_first_child"> <div class="formrow topic_board_row real_first_child">
<div class="formitem"><select form="topic_create_form_form" id="topic_board_input" name="topic-board"> <div class="formitem"><select form="topic_create_form_form" id="topic_board_input" name="topic-board">
`) `)
var topics_11 = []byte(`<option `) var topics_14 = []byte(`<option `)
var topics_12 = []byte(`selected`) var topics_15 = []byte(`selected`)
var topics_13 = []byte(` value="`) var topics_16 = []byte(` value="`)
var topics_14 = []byte(`">`) var topics_17 = []byte(`">`)
var topics_15 = []byte(`</option>`) var topics_18 = []byte(`</option>`)
var topics_16 = []byte(` var topics_19 = []byte(`
</select></div> </select></div>
</div> </div>
<div class="formrow topic_name_row"> <div class="formrow topic_name_row">
@ -851,77 +871,77 @@ var topics_16 = []byte(`
<div class="formitem"> <div class="formitem">
<button form="topic_create_form_form" class="formbutton">Create Topic</button> <button form="topic_create_form_form" class="formbutton">Create Topic</button>
`) `)
var topics_17 = []byte(` var topics_20 = []byte(`
<input name="upload_files" form="topic_create_form_form" id="upload_files" multiple type="file" style="display: none;" /> <input name="upload_files" form="topic_create_form_form" id="upload_files" multiple type="file" style="display: none;" />
<label for="upload_files" class="formbutton add_file_button">Add File</label> <label for="upload_files" class="formbutton add_file_button">Add File</label>
<div id="upload_file_dock"></div>`) <div id="upload_file_dock"></div>`)
var topics_18 = []byte(` var topics_21 = []byte(`
<button class="formbutton close_form">Cancel</button> <button class="formbutton close_form">Cancel</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
`) `)
var topics_19 = []byte(` var topics_22 = []byte(`
<div id="topic_list" class="rowblock topic_list" aria-label="A list containing topics from every forum"> <div id="topic_list" class="rowblock topic_list" aria-label="A list containing topics from every forum">
`) `)
var topics_20 = []byte(`<div class="topic_row" data-tid="`) var topics_23 = []byte(`<div class="topic_row" data-tid="`)
var topics_21 = []byte(`">
<div class="rowitem topic_left passive datarow `)
var topics_22 = []byte(`topic_sticky`)
var topics_23 = []byte(`topic_closed`)
var topics_24 = []byte(`"> var topics_24 = []byte(`">
<div class="rowitem topic_left passive datarow `)
var topics_25 = []byte(`topic_sticky`)
var topics_26 = []byte(`topic_closed`)
var topics_27 = []byte(`">
<span class="selector"></span> <span class="selector"></span>
<a href="`) <a href="`)
var topics_25 = []byte(`"><img src="`) var topics_28 = []byte(`"><img src="`)
var topics_26 = []byte(`" height="64" alt="`) var topics_29 = []byte(`" height="64" alt="`)
var topics_27 = []byte(`'s Avatar" title="`) var topics_30 = []byte(`'s Avatar" title="`)
var topics_28 = []byte(`'s Avatar" /></a> var topics_31 = []byte(`'s Avatar" /></a>
<span class="topic_inner_left"> <span class="topic_inner_left">
<a class="rowtopic" href="`) <a class="rowtopic" href="`)
var topics_29 = []byte(`" itemprop="itemListElement"><span>`) var topics_32 = []byte(`" itemprop="itemListElement"><span>`)
var topics_30 = []byte(`</span></a> `) var topics_33 = []byte(`</span></a> `)
var topics_31 = []byte(`<a class="rowsmall parent_forum" href="`) var topics_34 = []byte(`<a class="rowsmall parent_forum" href="`)
var topics_32 = []byte(`">`)
var topics_33 = []byte(`</a>`)
var topics_34 = []byte(`
<br /><a class="rowsmall starter" href="`)
var topics_35 = []byte(`">`) var topics_35 = []byte(`">`)
var topics_36 = []byte(`</a> var topics_36 = []byte(`</a>`)
var topics_37 = []byte(`
<br /><a class="rowsmall starter" href="`)
var topics_38 = []byte(`">`)
var topics_39 = []byte(`</a>
`) `)
var topics_37 = []byte(`<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>`) var topics_40 = []byte(`<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>`)
var topics_38 = []byte(`<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>`) var topics_41 = []byte(`<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>`)
var topics_39 = []byte(` var topics_42 = []byte(`
</span> </span>
<span class="topic_inner_right rowsmall" style="float: right;"> <span class="topic_inner_right rowsmall" style="float: right;">
<span class="replyCount">`) <span class="replyCount">`)
var topics_40 = []byte(`</span><br /> var topics_43 = []byte(`</span><br />
<span class="likeCount">`) <span class="likeCount">`)
var topics_41 = []byte(`</span> var topics_44 = []byte(`</span>
</span> </span>
</div> </div>
<div class="rowitem topic_right passive datarow `) <div class="rowitem topic_right passive datarow `)
var topics_42 = []byte(`topic_sticky`) var topics_45 = []byte(`topic_sticky`)
var topics_43 = []byte(`topic_closed`) var topics_46 = []byte(`topic_closed`)
var topics_44 = []byte(`"> var topics_47 = []byte(`">
<a href="`) <a href="`)
var topics_45 = []byte(`"><img src="`) var topics_48 = []byte(`"><img src="`)
var topics_46 = []byte(`" height="64" alt="`) var topics_49 = []byte(`" height="64" alt="`)
var topics_47 = []byte(`'s Avatar" title="`) var topics_50 = []byte(`'s Avatar" title="`)
var topics_48 = []byte(`'s Avatar" /></a> var topics_51 = []byte(`'s Avatar" /></a>
<span> <span>
<a href="`) <a href="`)
var topics_49 = []byte(`" class="lastName" style="font-size: 14px;">`) var topics_52 = []byte(`" class="lastName" style="font-size: 14px;">`)
var topics_50 = []byte(`</a><br> var topics_53 = []byte(`</a><br>
<span class="rowsmall lastReplyAt">`) <span class="rowsmall lastReplyAt">`)
var topics_51 = []byte(`</span> var topics_54 = []byte(`</span>
</span> </span>
</div> </div>
</div>`) </div>`)
var topics_52 = []byte(`<div class="rowitem passive">There aren't any topics yet.`) var topics_55 = []byte(`<div class="rowitem passive">There aren't any topics yet.`)
var topics_53 = []byte(` <a href="/topics/create/">Start one?</a>`) var topics_56 = []byte(` <a href="/topics/create/">Start one?</a>`)
var topics_54 = []byte(`</div>`) var topics_57 = []byte(`</div>`)
var topics_55 = []byte(` var topics_58 = []byte(`
</div> </div>
</main> </main>
@ -973,6 +993,7 @@ var forum_18 = []byte(`
<select class="mod_floater_options"> <select class="mod_floater_options">
<option val="delete">Delete them</option> <option val="delete">Delete them</option>
<option val="lock">Lock them</option> <option val="lock">Lock them</option>
<option val="move">Move them</option>
</select> </select>
<button>Run</button> <button>Run</button>
</div> </div>

View File

@ -3,9 +3,9 @@
// Code generated by Gosora. More below: // Code generated by Gosora. More below:
/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */ /* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */
package main package main
import "net/http"
import "./common" import "./common"
import "strconv" import "strconv"
import "net/http"
// nolint // nolint
func init() { func init() {
@ -99,108 +99,116 @@ if tmpl_topics_vars.CurrentUser.ID != 0 {
w.Write(topics_8) w.Write(topics_8)
if len(tmpl_topics_vars.ForumList) != 0 { if len(tmpl_topics_vars.ForumList) != 0 {
w.Write(topics_9) w.Write(topics_9)
w.Write([]byte(tmpl_topics_vars.CurrentUser.Avatar))
w.Write(topics_10)
if len(tmpl_topics_vars.ForumList) != 0 { if len(tmpl_topics_vars.ForumList) != 0 {
for _, item := range tmpl_topics_vars.ForumList { for _, item := range tmpl_topics_vars.ForumList {
w.Write(topics_11) w.Write(topics_10)
if item.ID == tmpl_topics_vars.DefaultForum {
w.Write(topics_12)
}
w.Write(topics_13)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topics_14)
w.Write([]byte(item.Name)) w.Write([]byte(item.Name))
w.Write(topics_11)
}
}
w.Write(topics_12)
w.Write([]byte(tmpl_topics_vars.CurrentUser.Avatar))
w.Write(topics_13)
if len(tmpl_topics_vars.ForumList) != 0 {
for _, item := range tmpl_topics_vars.ForumList {
w.Write(topics_14)
if item.ID == tmpl_topics_vars.DefaultForum {
w.Write(topics_15) w.Write(topics_15)
} }
}
w.Write(topics_16) w.Write(topics_16)
if tmpl_topics_vars.CurrentUser.Perms.UploadFiles { w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topics_17) w.Write(topics_17)
} w.Write([]byte(item.Name))
w.Write(topics_18) w.Write(topics_18)
} }
} }
w.Write(topics_19) w.Write(topics_19)
if tmpl_topics_vars.CurrentUser.Perms.UploadFiles {
w.Write(topics_20)
}
w.Write(topics_21)
}
}
w.Write(topics_22)
if len(tmpl_topics_vars.TopicList) != 0 { if len(tmpl_topics_vars.TopicList) != 0 {
for _, item := range tmpl_topics_vars.TopicList { for _, item := range tmpl_topics_vars.TopicList {
w.Write(topics_20)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topics_21)
if item.Sticky {
w.Write(topics_22)
} else {
if item.IsClosed {
w.Write(topics_23) w.Write(topics_23)
} w.Write([]byte(strconv.Itoa(item.ID)))
}
w.Write(topics_24) w.Write(topics_24)
w.Write([]byte(item.Creator.Link)) if item.Sticky {
w.Write(topics_25) w.Write(topics_25)
w.Write([]byte(item.Creator.Avatar))
w.Write(topics_26)
w.Write([]byte(item.Creator.Name))
w.Write(topics_27)
w.Write([]byte(item.Creator.Name))
w.Write(topics_28)
w.Write([]byte(item.Link))
w.Write(topics_29)
w.Write([]byte(item.Title))
w.Write(topics_30)
if item.ForumName != "" {
w.Write(topics_31)
w.Write([]byte(item.ForumLink))
w.Write(topics_32)
w.Write([]byte(item.ForumName))
w.Write(topics_33)
}
w.Write(topics_34)
w.Write([]byte(item.Creator.Link))
w.Write(topics_35)
w.Write([]byte(item.Creator.Name))
w.Write(topics_36)
if item.IsClosed {
w.Write(topics_37)
}
if item.Sticky {
w.Write(topics_38)
}
w.Write(topics_39)
w.Write([]byte(strconv.Itoa(item.PostCount)))
w.Write(topics_40)
w.Write([]byte(strconv.Itoa(item.LikeCount)))
w.Write(topics_41)
if item.Sticky {
w.Write(topics_42)
} else { } else {
if item.IsClosed { if item.IsClosed {
w.Write(topics_26)
}
}
w.Write(topics_27)
w.Write([]byte(item.Creator.Link))
w.Write(topics_28)
w.Write([]byte(item.Creator.Avatar))
w.Write(topics_29)
w.Write([]byte(item.Creator.Name))
w.Write(topics_30)
w.Write([]byte(item.Creator.Name))
w.Write(topics_31)
w.Write([]byte(item.Link))
w.Write(topics_32)
w.Write([]byte(item.Title))
w.Write(topics_33)
if item.ForumName != "" {
w.Write(topics_34)
w.Write([]byte(item.ForumLink))
w.Write(topics_35)
w.Write([]byte(item.ForumName))
w.Write(topics_36)
}
w.Write(topics_37)
w.Write([]byte(item.Creator.Link))
w.Write(topics_38)
w.Write([]byte(item.Creator.Name))
w.Write(topics_39)
if item.IsClosed {
w.Write(topics_40)
}
if item.Sticky {
w.Write(topics_41)
}
w.Write(topics_42)
w.Write([]byte(strconv.Itoa(item.PostCount)))
w.Write(topics_43) w.Write(topics_43)
} w.Write([]byte(strconv.Itoa(item.LikeCount)))
}
w.Write(topics_44) w.Write(topics_44)
w.Write([]byte(item.LastUser.Link)) if item.Sticky {
w.Write(topics_45) w.Write(topics_45)
w.Write([]byte(item.LastUser.Avatar)) } else {
if item.IsClosed {
w.Write(topics_46) w.Write(topics_46)
w.Write([]byte(item.LastUser.Name)) }
}
w.Write(topics_47) w.Write(topics_47)
w.Write([]byte(item.LastUser.Name))
w.Write(topics_48)
w.Write([]byte(item.LastUser.Link)) w.Write([]byte(item.LastUser.Link))
w.Write(topics_48)
w.Write([]byte(item.LastUser.Avatar))
w.Write(topics_49) w.Write(topics_49)
w.Write([]byte(item.LastUser.Name)) w.Write([]byte(item.LastUser.Name))
w.Write(topics_50) w.Write(topics_50)
w.Write([]byte(item.RelativeLastReplyAt)) w.Write([]byte(item.LastUser.Name))
w.Write(topics_51) w.Write(topics_51)
} w.Write([]byte(item.LastUser.Link))
} else {
w.Write(topics_52) w.Write(topics_52)
if tmpl_topics_vars.CurrentUser.Perms.CreateTopic { w.Write([]byte(item.LastUser.Name))
w.Write(topics_53) w.Write(topics_53)
} w.Write([]byte(item.RelativeLastReplyAt))
w.Write(topics_54) w.Write(topics_54)
} }
} else {
w.Write(topics_55) w.Write(topics_55)
if tmpl_topics_vars.CurrentUser.Perms.CreateTopic {
w.Write(topics_56)
}
w.Write(topics_57)
}
w.Write(topics_58)
w.Write(footer_0) w.Write(footer_0)
w.Write([]byte(common.BuildWidget("footer",tmpl_topics_vars.Header))) w.Write([]byte(common.BuildWidget("footer",tmpl_topics_vars.Header)))
w.Write(footer_1) w.Write(footer_1)

View File

@ -31,6 +31,7 @@
<select class="mod_floater_options"> <select class="mod_floater_options">
<option val="delete">Delete them</option> <option val="delete">Delete them</option>
<option val="lock">Lock them</option> <option val="lock">Lock them</option>
<option val="move">Move them</option>
</select> </select>
<button>Run</button> <button>Run</button>
</div> </div>

View File

@ -7,6 +7,7 @@
<div class="rowitem"> <div class="rowitem">
<a>Views</a> <a>Views</a>
<select class="timeRangeSelector to_right" name="timeRange"> <select class="timeRangeSelector to_right" name="timeRange">
<option val="two-days"{{if eq .TimeRange "two-days"}} selected{{end}}>2 days</option>
<option val="one-day"{{if eq .TimeRange "one-day"}} selected{{end}}>1 day</option> <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="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> <option val="six-hours"{{if eq .TimeRange "six-hours"}} selected{{end}}>6 hours</option>

View File

@ -7,6 +7,7 @@
<div class="rowitem"> <div class="rowitem">
<a>{{.Agent}} Views</a> <a>{{.Agent}} Views</a>
<select class="timeRangeSelector to_right" name="timeRange"> <select class="timeRangeSelector to_right" name="timeRange">
<option val="two-days"{{if eq .TimeRange "two-days"}} selected{{end}}>2 days</option>
<option val="one-day"{{if eq .TimeRange "one-day"}} selected{{end}}>1 day</option> <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="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> <option val="six-hours"{{if eq .TimeRange "six-hours"}} selected{{end}}>6 hours</option>

View File

@ -7,6 +7,7 @@
<div class="rowitem"> <div class="rowitem">
<a>{{.Route}} Views</a> <a>{{.Route}} Views</a>
<select class="timeRangeSelector to_right" name="timeRange"> <select class="timeRangeSelector to_right" name="timeRange">
<option val="two-days"{{if eq .TimeRange "two-days"}} selected{{end}}>2 days</option>
<option val="one-day"{{if eq .TimeRange "one-day"}} selected{{end}}>1 day</option> <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="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> <option val="six-hours"{{if eq .TimeRange "six-hours"}} selected{{end}}>6 hours</option>

View File

@ -17,6 +17,7 @@
</div> </div>
{{if ne .CurrentUser.ID 0}} {{if ne .CurrentUser.ID 0}}
{{/** TODO: Hide these from unauthorised users? **/}}
<div class="mod_floater auto_hide"> <div class="mod_floater auto_hide">
<form method="post"> <form method="post">
<div class="mod_floater_head"> <div class="mod_floater_head">
@ -26,6 +27,7 @@
<select class="mod_floater_options"> <select class="mod_floater_options">
<option val="delete">Delete them</option> <option val="delete">Delete them</option>
<option val="lock">Lock them</option> <option val="lock">Lock them</option>
<option val="move">Move them</option>
</select> </select>
<button class="mod_floater_submit">Run</button> <button class="mod_floater_submit">Run</button>
</div> </div>
@ -33,6 +35,24 @@
</div> </div>
{{if .ForumList}} {{if .ForumList}}
{{/** TODO: Have a seperate forum list for moving topics? Maybe an AJAX forum search compatible with plugin_guilds? **/}}
{{/** TODO: Add ARIA attributes for this **/}}
<div id="mod_topic_mover" class="modal_pane auto_hide">
<form action="/topic/move/submit/" method="post">
<div class="pane_header">
<h3>Move Topics (3)</h3>
</div>
<div class="pane_body">
<div class="pane_table">
<div class="pane_row"></div>
{{range .ForumList}}<div class="pane_row">{{.Name}}</div>{{end}}
</div>
</div>
<div class="pane_buttons">
<button>Move Topics</button>
</div>
</form>
</div>
<div class="rowblock topic_create_form quick_create_form" style="display: none;" aria-label="Quick Topic Form"> <div class="rowblock topic_create_form quick_create_form" style="display: none;" aria-label="Quick Topic Form">
<form name="topic_create_form_form" id="topic_create_form_form" enctype="multipart/form-data" action="/topic/create/submit/" method="post"></form> <form name="topic_create_form_form" id="topic_create_form_form" enctype="multipart/form-data" action="/topic/create/submit/" method="post"></form>
<img class="little_row_avatar" src="{{.CurrentUser.Avatar}}" height="64" alt="Your Avatar" title="Your Avatar" /> <img class="little_row_avatar" src="{{.CurrentUser.Avatar}}" height="64" alt="Your Avatar" title="Your Avatar" />

View File

@ -220,16 +220,18 @@ ul {
font-size: 20px; font-size: 20px;
font-weight: normal; font-weight: normal;
color: var(--primary-text-color); color: var(--primary-text-color);
-webkit-margin-before: 0;
-webkit-margin-after: 0;
margin-block-start: 0;
margin-block-end: 0;
display: inline-block; display: inline-block;
} }
.colstack_head a h1 { .colstack_head a h1 {
font-size: 16px; font-size: 16px;
color: var(--primary-link-color); color: var(--primary-link-color);
} }
h1, h3 {
-webkit-margin-before: 0;
-webkit-margin-after: 0;
margin-block-start: 0;
margin-block-end: 0;
}
.colstack { .colstack {
display: flex; display: flex;
@ -261,9 +263,9 @@ ul {
position: fixed; position: fixed;
bottom: 15px; bottom: 15px;
right: 15px; right: 15px;
background-color: var(--inverse-primary-text-color);
width: 200px; width: 200px;
height: 115px; height: 115px;
background-color: var(--inverse-primary-text-color);
border: 1px solid var(--header-border-color); border: 1px solid var(--header-border-color);
border-bottom: 2px solid var(--header-border-color); border-bottom: 2px solid var(--header-border-color);
z-index: 9999; z-index: 9999;
@ -308,6 +310,17 @@ ul {
margin-top: -2px; margin-top: -2px;
} }
.modal_pane {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
background-color: var(--inverse-primary-text-color);
border: 1px solid var(--header-border-color);
border-bottom: 2px solid var(--header-border-color);
padding: 8px;
}
@keyframes fadein { @keyframes fadein {
from { opacity: 0; } from { opacity: 0; }
to { opacity: 1; } to { opacity: 1; }

View File

@ -174,7 +174,7 @@
stroke: hsl(359,98%,23%) !important; stroke: hsl(359,98%,23%) !important;
} }
.ct-point:hover { .ct-point:hover {
stroke: hsl(359,98%,20%) !important; stroke: hsl(359,98%,30%) !important;
} }
.timeRangeSelector { .timeRangeSelector {
margin-top: -5px; margin-top: -5px;