1
0
Fork 0
mirror of https://github.com/Eggbertx/gochan.git synced 2025-09-13 09:26:23 -07:00

Add filter hits page, refactor manage action resolving to use the router

This commit is contained in:
Eggbertx 2024-08-25 16:59:25 -07:00
parent a50845ee87
commit 69c8cc4765
16 changed files with 292 additions and 340 deletions

View file

@ -10,6 +10,7 @@ import (
"github.com/gochan-org/gochan/pkg/config"
"github.com/gochan-org/gochan/pkg/events"
"github.com/gochan-org/gochan/pkg/manage"
"github.com/gochan-org/gochan/pkg/gcutil"
@ -110,6 +111,7 @@ func main() {
os.Exit(1) // skipcq: CRT-D0011
}
defer events.TriggerEvent("shutdown")
manage.InitManagePages()
go initServer()
<-sc
}

View file

@ -15,7 +15,6 @@ import (
"github.com/gochan-org/gochan/pkg/config"
"github.com/gochan-org/gochan/pkg/gcutil"
"github.com/gochan-org/gochan/pkg/manage"
"github.com/gochan-org/gochan/pkg/posting"
"github.com/gochan-org/gochan/pkg/server"
"github.com/gochan-org/gochan/pkg/server/serverutil"
@ -30,9 +29,6 @@ func initServer() {
router := server.GetRouter()
router.GET(config.WebPath("/captcha"), bunrouter.HTTPHandlerFunc(posting.ServeCaptcha))
router.POST(config.WebPath("/captcha"), bunrouter.HTTPHandlerFunc(posting.ServeCaptcha))
router.GET(config.WebPath("/manage"), bunrouter.HTTPHandlerFunc(manage.CallManageFunction))
router.GET(config.WebPath("/manage/:action"), bunrouter.HTTPHandlerFunc(manage.CallManageFunction))
router.POST(config.WebPath("/manage/:action"), bunrouter.HTTPHandlerFunc(manage.CallManageFunction))
router.GET(config.WebPath("/post"), bunrouter.HTTPHandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, config.WebPath("/"), http.StatusFound)
}))

View file

@ -29,6 +29,10 @@ table.mgmt-table {
text-align: center;
width: 7%;
}
col.filter-actions, td.filter-actions {
text-align: center;
width: 10%;
}
col.row-date {
width: 20%;
}

View file

@ -328,6 +328,10 @@ table.mgmt-table td.table-actions, table.mgmt-table col.row-actions, table.mgmt-
text-align: center;
width: 7%;
}
table.mgmt-table col.filter-actions, table.mgmt-table td.filter-actions {
text-align: center;
width: 10%;
}
table.mgmt-table col.row-date {
width: 20%;
}

View file

@ -626,3 +626,26 @@ func ApplyFilter(filter *Filter, conditions []FilterCondition, boards []int) err
}
return tx.Commit()
}
// GetFilterHits returns an array of incidents where an attempted post matched a filter (excluding wordfilters)
func GetFilterHits(filterID int) ([]FilterHit, error) {
const querySQL = `SELECT id, match_time, post_data FROM DBPREFIXfilter_hits WHERE filter_id = ?`
rows, cancel, err := QueryTimeoutSQL(nil, querySQL, filterID)
defer cancel()
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
} else if err != nil {
return nil, err
}
defer rows.Close()
var hits []FilterHit
for rows.Next() {
hit := FilterHit{FilterID: filterID}
if err = rows.Scan(&hit.ID, &hit.MatchTime, &hit.PostData); err != nil {
return nil, err
}
hits = append(hits, hit)
}
return hits, rows.Close()
}

View file

@ -163,10 +163,10 @@ func GetStaffBySession(session string) (*Staff, error) {
JOIN DBPREFIXsessions as sessions ON sessions.staff_id = staff.id
WHERE sessions.data = ?`
staff := new(Staff)
var staff Staff
err := QueryRowTimeoutSQL(nil, query, []any{session}, []any{
&staff.ID, &staff.Username, &staff.PasswordChecksum, &staff.Rank, &staff.AddedOn, &staff.LastLogin})
return staff, err
return &staff, err
}
func GetStaffByUsername(username string, onlyActive bool) (*Staff, error) {

View file

@ -25,26 +25,25 @@ const (
ManageBans = "manage_bans.html"
ManageBoards = "manage_boards.html"
ManageDashboard = "manage_dashboard.html"
// ManageFileBans = "manage_filebans.html"
ManageFilters = "manage_filters.html"
ManageFilterHits = "manage_filter_hits.html"
ManageFixThumbnails = "manage_fixthumbnails.html"
ManageIPSearch = "manage_ipsearch.html"
ManageLogin = "manage_login.html"
// ManageNameBans = "manage_namebans.html"
ManageRecentPosts = "manage_recentposts.html"
ManageReports = "manage_reports.html"
ManageSections = "manage_sections.html"
ManageStaff = "manage_staff.html"
ManageTemplates = "manage_templateoverride.html"
ManageThreadAttrs = "manage_threadattrs.html"
ManageViewLog = "manage_viewlog.html"
ManageWordfilters = "manage_wordfilters.html"
MoveThreadPage = "movethreadpage.html"
PageFooter = "page_footer.html"
PageHeader = "page_header.html"
PostEdit = "post_edit.html"
PostFlag = "flag.html"
ThreadPage = "threadpage.html"
ManageRecentPosts = "manage_recentposts.html"
ManageReports = "manage_reports.html"
ManageSections = "manage_sections.html"
ManageStaff = "manage_staff.html"
ManageTemplates = "manage_templateoverride.html"
ManageThreadAttrs = "manage_threadattrs.html"
ManageViewLog = "manage_viewlog.html"
ManageWordfilters = "manage_wordfilters.html"
MoveThreadPage = "movethreadpage.html"
PageFooter = "page_footer.html"
PageHeader = "page_header.html"
PostEdit = "post_edit.html"
PostFlag = "flag.html"
ThreadPage = "threadpage.html"
)
var (
@ -90,12 +89,12 @@ var (
ManageDashboard: {
files: []string{"manage_dashboard.html"},
},
// ManageFileBans: {
// files: []string{"manage_filebans.html"},
// },
ManageFilters: {
files: []string{"manage_filters.html"},
},
ManageFilterHits: {
files: []string{"manage_filter_hits.html"},
},
ManageFixThumbnails: {
files: []string{"manage_fixthumbnails.html"},
},
@ -105,9 +104,6 @@ var (
ManageLogin: {
files: []string{"manage_login.html"},
},
// ManageNameBans: {
// files: []string{"manage_namebans.html"},
// },
ManageRecentPosts: {
files: []string{"manage_recentposts.html"},
},
@ -226,6 +222,7 @@ func GetTemplateList() []string {
return templateList
}
// ParseTemplate initializes a new template with the given name and parses it
func ParseTemplate(name, tmplStr string) (*template.Template, error) {
return template.New(name).Funcs(funcMap).Parse(tmplStr)
}

View file

@ -2,9 +2,13 @@ package manage
import (
"net/http"
"path"
"github.com/gochan-org/gochan/pkg/config"
"github.com/gochan-org/gochan/pkg/gcsql"
"github.com/gochan-org/gochan/pkg/server"
"github.com/rs/zerolog"
"github.com/uptrace/bunrouter"
)
const (
@ -76,12 +80,18 @@ func getAction(id string, rank int) *Action {
}
func RegisterManagePage(id string, title string, permissions int, jsonOutput int, callback CallbackFunction) {
actions = append(actions, Action{
action := Action{
ID: id,
Title: title,
Permissions: permissions,
JSONoutput: jsonOutput,
Callback: callback,
}
actions = append(actions, action)
server.GetRouter().WithGroup(config.WebPath("/manage"), func(g *bunrouter.Group) {
groupPath := path.Join("/", id, "/*actionArgs")
g.GET(groupPath, setupManageFunction(&action))
g.POST(groupPath, setupManageFunction(&action))
})
}

View file

@ -680,78 +680,15 @@ func viewLogCallback(_ http.ResponseWriter, _ *http.Request, _ *gcsql.Staff, _ b
}
func registerAdminPages() {
actions = append(actions,
Action{
ID: "updateannouncements",
Title: "Update staff announcements",
Permissions: AdminPerms,
JSONoutput: NoJSON,
Callback: updateAnnouncementsCallback,
},
Action{
ID: "boards",
Title: "Boards",
Permissions: AdminPerms,
JSONoutput: NoJSON,
Callback: boardsCallback,
},
Action{
ID: "boardsections",
Title: "Board sections",
Permissions: AdminPerms,
JSONoutput: OptionalJSON,
Callback: boardSectionsCallback,
},
Action{
ID: "cleanup",
Title: "Cleanup",
Permissions: AdminPerms,
Callback: cleanupCallback,
},
Action{
ID: "fixthumbnails",
Title: "Regenerate thumbnails",
Permissions: AdminPerms,
Callback: fixThumbnailsCallback,
},
Action{
ID: "templates",
Title: "Override templates",
Permissions: AdminPerms,
Callback: templatesCallback,
},
Action{
ID: "rebuildfront",
Title: "Rebuild front page",
Permissions: AdminPerms,
JSONoutput: OptionalJSON,
Callback: rebuildFrontCallback,
},
Action{
ID: "rebuildall",
Title: "Rebuild everything",
Permissions: AdminPerms,
JSONoutput: OptionalJSON,
Callback: rebuildAllCallback,
},
Action{
ID: "rebuildboards",
Title: "Rebuild boards",
Permissions: AdminPerms,
JSONoutput: OptionalJSON,
Callback: rebuildBoardsCallback,
},
Action{
ID: "reparsehtml",
Title: "Reparse HTML",
Permissions: AdminPerms,
Callback: reparseHTMLCallback,
},
Action{
ID: "viewlog",
Title: "View log",
Permissions: AdminPerms,
Callback: viewLogCallback,
},
)
RegisterManagePage("updateannouncements", "Update staff announcements", AdminPerms, NoJSON, updateAnnouncementsCallback)
RegisterManagePage("boards", "Boards", AdminPerms, NoJSON, boardsCallback)
RegisterManagePage("boardsections", "Board sections", AdminPerms, OptionalJSON, boardSectionsCallback)
RegisterManagePage("cleanup", "Cleanup", AdminPerms, NoJSON, cleanupCallback)
RegisterManagePage("fixthumbnails", "Regenerate thumbnails", AdminPerms, NoJSON, fixThumbnailsCallback)
RegisterManagePage("templates", "Override templates", AdminPerms, NoJSON, templatesCallback)
RegisterManagePage("rebuildfront", "Rebuild front page", AdminPerms, OptionalJSON, rebuildFrontCallback)
RegisterManagePage("rebuildboards", "Rebuild boards", AdminPerms, OptionalJSON, rebuildBoardsCallback)
RegisterManagePage("rebuildall", "Rebuild everything", AdminPerms, OptionalJSON, rebuildAllCallback)
RegisterManagePage("reparsehtml", "Reparse HTML", AdminPerms, NoJSON, reparseHTMLCallback)
RegisterManagePage("viewlog", "View log", AdminPerms, NoJSON, viewLogCallback)
}

View file

@ -217,40 +217,9 @@ func staffCallback(writer http.ResponseWriter, request *http.Request, staff *gcs
}
func registerJanitorPages() {
actions = append(actions,
Action{
ID: "logout",
Title: "Logout",
Permissions: JanitorPerms,
Callback: logoutCallback,
},
Action{
ID: "clearmysessions",
Title: "Log me out everywhere",
Permissions: JanitorPerms,
JSONoutput: OptionalJSON,
Callback: clearMySessionsCallback,
},
Action{
ID: "recentposts",
Title: "Recent posts",
Permissions: JanitorPerms,
JSONoutput: OptionalJSON,
Callback: recentPostsCallback,
},
Action{
ID: "announcements",
Title: "Announcements",
Permissions: JanitorPerms,
JSONoutput: AlwaysJSON,
Callback: announcementsCallback,
},
Action{
ID: "staff",
Title: "Staff",
Permissions: JanitorPerms,
JSONoutput: OptionalJSON,
Callback: staffCallback,
},
)
RegisterManagePage("logout", "Logout", JanitorPerms, NoJSON, logoutCallback)
RegisterManagePage("clearmysessions", "Log me out everywhere", JanitorPerms, OptionalJSON, clearMySessionsCallback)
RegisterManagePage("recentposts", "Recent posts", JanitorPerms, OptionalJSON, recentPostsCallback)
RegisterManagePage("announcements", "Announcements", JanitorPerms, AlwaysJSON, announcementsCallback)
RegisterManagePage("staff", "Staff", JanitorPerms, OptionalJSON, staffCallback)
}

View file

@ -3,6 +3,7 @@ package manage
import (
"bytes"
"database/sql"
"encoding/json"
"errors"
"fmt"
"net"
@ -17,8 +18,10 @@ import (
"github.com/gochan-org/gochan/pkg/gctemplates"
"github.com/gochan-org/gochan/pkg/gcutil"
"github.com/gochan-org/gochan/pkg/posting/uploads"
"github.com/gochan-org/gochan/pkg/server"
"github.com/gochan-org/gochan/pkg/server/serverutil"
"github.com/rs/zerolog"
"github.com/uptrace/bunrouter"
)
// manage actions that require moderator-level permission go here
@ -199,6 +202,46 @@ func appealsCallback(_ http.ResponseWriter, request *http.Request, staff *gcsql.
return manageAppealsBuffer.String(), err
}
func filterHitsCallback(writer http.ResponseWriter, request *http.Request, staff *gcsql.Staff, wantsJSON bool, _, errEv *zerolog.Event) (output any, err error) {
params, _ := request.Context().Value("actionParams").(bunrouter.Params)
filterIDStr := params.ByName("filterID")
filterID, err := strconv.Atoi(filterIDStr)
if err != nil {
errEv.Err(err).Caller().Str("filterID", filterIDStr).Msg("Filter ID is not a valid integer")
return nil, err
}
errEv.Int("filterID", filterID)
hits, err := gcsql.GetFilterHits(filterID)
if err != nil {
errEv.Err(err).Caller().Msg("Unable to get filter hits")
return nil, errors.New("unable to get list of filter hits")
}
m := make(map[string]any)
var ba []byte
for h := range hits {
// un-minify the JSON data to make it more readable
if err = json.Unmarshal([]byte(hits[h].PostData), &m); err != nil {
errEv.Err(err).Caller().Msg("Unable to unmarshal post data for filter hit")
return nil, err
}
if ba, err = json.MarshalIndent(m, "", " "); err != nil {
errEv.Err(err).Caller().RawJSON("postData", []byte(hits[h].PostData)).Msg("Unable to marshal un-minified post data")
return nil, err
}
hits[h].PostData = string(ba)
}
var buf bytes.Buffer
if err = serverutil.MinifyTemplate(gctemplates.ManageFilterHits, map[string]any{
"hits": hits,
}, &buf, "text/html"); err != nil {
errEv.Err(err).Caller().Str("template", gctemplates.ManageFilterHits).Msg("Unable to render template")
return nil, errors.New("unable to render filter hits page")
}
return buf.String(), nil
}
type filterField struct {
Value string
Text string
@ -206,7 +249,7 @@ type filterField struct {
hasSearchbox bool
}
func filtersCallback(_ http.ResponseWriter, request *http.Request, staff *gcsql.Staff, wantsJSON bool, infoEv, errEv *zerolog.Event) (output any, err error) {
func filtersCallback(writer http.ResponseWriter, request *http.Request, staff *gcsql.Staff, wantsJSON bool, infoEv, errEv *zerolog.Event) (output any, err error) {
var boards []int
data := map[string]any{
"allBoards": gcsql.AllBoards,
@ -909,67 +952,18 @@ func wordfiltersCallback(_ http.ResponseWriter, request *http.Request, staff *gc
}
func registerModeratorPages() {
actions = append(actions,
Action{
ID: "bans",
Title: "Bans",
Permissions: ModPerms,
Callback: bansCallback,
},
Action{
ID: "appeals",
Title: "Ban appeals",
Permissions: ModPerms,
JSONoutput: OptionalJSON,
Callback: appealsCallback,
},
Action{
ID: "filters",
Title: "Post filters",
Permissions: ModPerms,
JSONoutput: NoJSON,
Callback: filtersCallback,
},
Action{
ID: "ipsearch",
Title: "IP Search",
Permissions: ModPerms,
JSONoutput: NoJSON,
Callback: ipSearchCallback,
},
Action{
ID: "reports",
Title: "Reports",
Permissions: ModPerms,
JSONoutput: OptionalJSON,
Callback: reportsCallback,
},
Action{
ID: "threadattrs",
Title: "View/Update Thread Attributes",
Permissions: ModPerms,
JSONoutput: OptionalJSON,
Callback: threadAttrsCallback,
},
Action{
ID: "postinfo",
Title: "Post info",
Permissions: ModPerms,
JSONoutput: AlwaysJSON,
Callback: postInfoCallback,
},
Action{
ID: "fingerprint",
Title: "Get image/thumbnail fingerprint",
Permissions: ModPerms,
JSONoutput: AlwaysJSON,
Callback: fingerprintCallback,
},
Action{
ID: "wordfilters",
Title: "Wordfilters",
Permissions: ModPerms,
Callback: wordfiltersCallback,
},
)
RegisterManagePage("bans", "Bans", ModPerms, NoJSON, bansCallback)
RegisterManagePage("appeals", "Ban appeals", ModPerms, OptionalJSON, appealsCallback)
RegisterManagePage("filters", "Post filters", ModPerms, NoJSON, filtersCallback)
server.GetRouter().GET(config.WebPath("/manage/filters/hits/:filterID"), setupManageFunction(&Action{
ID: "filters/hits",
Title: "Filter hits",
Callback: filterHitsCallback,
}))
RegisterManagePage("ipsearch", "IP Search", ModPerms, NoJSON, ipSearchCallback)
RegisterManagePage("reports", "Reports", ModPerms, OptionalJSON, reportsCallback)
RegisterManagePage("threadattrs", "View/Update Thread Attributes", ModPerms, OptionalJSON, threadAttrsCallback)
RegisterManagePage("postinfo", "Post info", ModPerms, AlwaysJSON, postInfoCallback)
RegisterManagePage("fingerprint", "Get image/thumbnail fingerprint", ModPerms, AlwaysJSON, fingerprintCallback)
RegisterManagePage("wordfilters", "Wordfilters", ModPerms, NoJSON, wordfiltersCallback)
}

View file

@ -95,12 +95,5 @@ func staffInfoCallback(_ http.ResponseWriter, _ *http.Request, staff *gcsql.Staf
}
func registerNoPermPages() {
actions = append(actions, loginAction,
Action{
ID: "staffinfo",
Permissions: NoPerms,
JSONoutput: AlwaysJSON,
Callback: staffInfoCallback,
},
)
RegisterManagePage("staffinfo", "", NoPerms, AlwaysJSON, staffInfoCallback)
}

View file

@ -2,8 +2,7 @@ package manage
import (
"bytes"
"database/sql"
"errors"
"context"
"fmt"
"net/http"
"runtime/debug"
@ -28,134 +27,129 @@ func (esa *ErrStaffAction) Error() string {
}
func serveError(writer http.ResponseWriter, field string, action string, message string, isJSON bool) {
server.ServeError(writer, message, isJSON, map[string]interface{}{
server.ServeError(writer, message, isJSON, map[string]any{
"error": field,
"action": action,
"message": message,
})
}
// CallManageFunction is called when a user accesses /manage to use manage tools
// or log in to a staff account
func CallManageFunction(writer http.ResponseWriter, request *http.Request) {
var err error
wantsJSON := serverutil.IsRequestingJSON(request)
accessEv := gcutil.LogAccess(request)
infoEv, errEv := gcutil.LogRequest(request)
defer gcutil.LogDiscard(infoEv, accessEv, errEv)
// setupManageFunction returns a function used by the HTTP router to serve the action callback's return value
func setupManageFunction(action *Action) bunrouter.HandlerFunc {
return func(writer http.ResponseWriter, req bunrouter.Request) (err error) {
request := req.Request
wantsJSON := serverutil.IsRequestingJSON(request)
accessEv := gcutil.LogAccess(request)
infoEv, errEv := gcutil.LogRequest(request)
defer gcutil.LogDiscard(infoEv, accessEv, errEv)
if err = request.ParseForm(); err != nil {
errEv.Err(err).Caller().Msg("Error parsing form data")
server.ServeError(writer, "Error parsing form data: "+err.Error(), wantsJSON, nil)
return
}
params := bunrouter.ParamsFromContext(request.Context())
actionID := params.ByName("action")
gcutil.LogStr("action", actionID, infoEv, accessEv, errEv)
var staff *gcsql.Staff
staff, err = GetStaffFromRequest(request)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
errEv.Err(err).Caller().
Str("request", "getCurrentFullStaff").Send()
server.ServeError(writer, "Error getting staff info from request: "+err.Error(), wantsJSON, nil)
return
}
if actionID == "" {
if staff.Rank == NoPerms {
// no action requested and user is not logged in, have them go to login page
actionID = "login"
} else {
actionID = "dashboard"
}
}
gcutil.LogStr("staff", staff.Username, infoEv, accessEv, errEv)
var managePageBuffer bytes.Buffer
action := getAction(actionID, staff.Rank)
if action == nil {
if wantsJSON {
serveError(writer, "notfound", actionID, "action not found", wantsJSON)
} else {
server.ServeNotFound(writer, request)
}
return
}
if staff.Rank < action.Permissions {
writer.WriteHeader(http.StatusForbidden)
errEv.
Int("rank", staff.Rank).
Int("requiredRank", action.Permissions).
Msg("Insufficient permissions")
serveError(writer, "permission", actionID, "You do not have permission to access this page", wantsJSON || (action.JSONoutput == AlwaysJSON))
return
}
var output interface{}
if wantsJSON && action.JSONoutput == NoJSON {
output = nil
err = &ErrStaffAction{
ErrorField: "nojson",
Action: actionID,
Message: "Requested mod page does not have a JSON output option",
}
} else {
defer func() {
if a := recover(); a != nil {
serveError(writer, "actionerror", actionID, "Internal server error", wantsJSON)
errEv.Caller().
Str("action", actionID).
Interface("recover", a).
Bytes("stack", debug.Stack()).
Msg("Recovered from panic while calling manage function")
}
}()
if action.Callback == nil {
output = ""
err = fmt.Errorf("action %q exists but has no defined callback", action.ID)
} else {
output, err = action.Callback(writer, request, staff, wantsJSON, infoEv, errEv)
}
}
if err != nil {
writer.WriteHeader(http.StatusInternalServerError)
serveError(writer, "actionerror", actionID, err.Error(), wantsJSON || (action.JSONoutput == AlwaysJSON))
return
}
if action.JSONoutput == AlwaysJSON || (action.JSONoutput > NoJSON && wantsJSON) {
writer.Header().Add("Content-Type", "application/json")
writer.Header().Add("Cache-Control", "max-age=5, must-revalidate")
outputJSON, err := gcutil.MarshalJSON(output, true)
if err != nil {
serveError(writer, "error", actionID, err.Error(), true)
gcutil.LogError(err).
Str("action", actionID).Send()
gcutil.LogStr("action", action.ID, infoEv, accessEv, errEv)
if err = req.Request.ParseForm(); err != nil {
errEv.Err(err).Caller().Msg("Error parsing form data")
server.ServeError(writer, "Error parsing form data: "+err.Error(), wantsJSON, map[string]any{
"action": action.ID,
})
return
}
serverutil.MinifyWriter(writer, []byte(outputJSON), "application/json")
return
}
headerMap := map[string]interface{}{
"page_type": "manage",
var staff *gcsql.Staff
staff, err = GetStaffFromRequest(request)
if err != nil {
errEv.Err(err).Caller().Msg("Unable to get staff from request")
server.ServeError(writer, "Error getting staff info from request", wantsJSON, nil)
return
}
gcutil.LogStr("staff", staff.Username, infoEv, accessEv, errEv)
actionCB := action.Callback
if staff.Username == "" && action.Permissions > NoPerms {
// action with permissions requested and user is not logged in, have them go to login page
actionCB = loginCallback
}
if staff.Rank < action.Permissions {
writer.WriteHeader(http.StatusForbidden)
gcutil.LogWarning().
Str("ip", gcutil.GetRealIP(request)).
Str("userAgent", request.UserAgent()).
Int("rank", staff.Rank).
Int("requiredRank", action.Permissions).
Msg("Staff requested page with insufficient permissions")
serveError(writer, "permission", action.ID, "You do not have permission to access this page", wantsJSON || (action.JSONoutput == AlwaysJSON))
return
}
var output any
if wantsJSON && action.JSONoutput == NoJSON {
output = nil
err = &ErrStaffAction{
ErrorField: "nojson",
Action: action.ID,
Message: "Requested mod page does not have a JSON output option",
}
} else {
defer func() {
if a := recover(); a != nil {
serveError(writer, "actionerror", action.ID, "Internal server error", wantsJSON)
gcutil.LogError(nil).
Str("ip", gcutil.GetRealIP(request)).
Str("userAgent", req.UserAgent()).
Interface("recover", a).
Bytes("stack", debug.Stack()).
Msg("Recovered from panic while calling manage function")
}
}()
if actionCB == nil {
output = ""
err = fmt.Errorf("action %q exists but has no defined callback", action.ID)
} else {
output, err = actionCB(writer,
request.WithContext(context.WithValue(request.Context(), "actionParams", req.Params())),
staff, wantsJSON, infoEv, errEv)
}
}
if err != nil {
writer.WriteHeader(http.StatusInternalServerError)
serveError(writer, "actionerror", action.ID, err.Error(), wantsJSON || (action.JSONoutput == AlwaysJSON))
return
}
if action.JSONoutput == AlwaysJSON || (action.JSONoutput > NoJSON && wantsJSON) {
writer.Header().Add("Content-Type", "application/json")
writer.Header().Add("Cache-Control", "max-age=5, must-revalidate")
var outputJSON string
if outputJSON, err = gcutil.MarshalJSON(output, true); err != nil {
serveError(writer, "error", action.ID, err.Error(), true)
errEv.Err(err).Caller().Send()
return
}
serverutil.MinifyWriter(writer, []byte(outputJSON), "application/json")
return
}
headerMap := map[string]any{
"page_type": "manage",
}
if action.ID != "dashboard" && action.ID != "login" && action.ID != "logout" {
headerMap["includeDashboardLink"] = true
}
var buf bytes.Buffer
if err = building.BuildPageHeader(&buf, action.Title, "", headerMap); err != nil {
gcutil.LogError(err).
Str("action", action.ID).
Str("staff", "pageHeader").Send()
serveError(writer, "error", action.ID, "Failed writing page header: "+err.Error(), false)
return
}
buf.WriteString("<br />" + fmt.Sprint(output) + "<br /><br />")
if err = building.BuildPageFooter(&buf); err != nil {
gcutil.LogError(err).
Str("action", action.ID).
Str("staff", "pageFooter").Send()
serveError(writer, "error", action.ID, "Failed writing page footer: "+err.Error(), false)
return
}
writer.Write(buf.Bytes())
return nil
}
if action.ID != "dashboard" && action.ID != "login" && action.ID != "logout" {
headerMap["includeDashboardLink"] = true
}
if err = building.BuildPageHeader(&managePageBuffer, action.Title, "", headerMap); err != nil {
gcutil.LogError(err).
Str("action", actionID).
Str("staff", "pageHeader").Send()
serveError(writer, "error", actionID, "Failed writing page header: "+err.Error(), false)
return
}
managePageBuffer.WriteString("<br />" + fmt.Sprint(output) + "<br /><br />")
if err = building.BuildPageFooter(&managePageBuffer); err != nil {
gcutil.LogError(err).
Str("action", actionID).
Str("staff", "pageFooter").Send()
serveError(writer, "error", actionID, "Failed writing page footer: "+err.Error(), false)
return
}
writer.Write(managePageBuffer.Bytes())
}

View file

@ -14,6 +14,7 @@ import (
"github.com/gochan-org/gochan/pkg/gcsql"
"github.com/gochan-org/gochan/pkg/gctemplates"
"github.com/gochan-org/gochan/pkg/gcutil"
"github.com/gochan-org/gochan/pkg/server"
"github.com/gochan-org/gochan/pkg/server/serverutil"
"github.com/rs/zerolog"
"golang.org/x/crypto/bcrypt"
@ -25,6 +26,12 @@ var (
ErrBadCredentials = errors.New("invalid username or password")
ErrUnableToCreateSession = errors.New("unable to create login session")
ErrInvalidSession = errors.New("invalid staff session")
dashboardAction = Action{
ID: "dashboard",
Title: "Dashboard",
Permissions: JanitorPerms,
Callback: dashboardCallback,
}
)
func createSession(key, username, password string, request *http.Request, writer http.ResponseWriter) error {
@ -88,7 +95,7 @@ func createSession(key, username, password string, request *http.Request, writer
return nil
}
func getCurrentStaff(request *http.Request) (string, error) { //TODO after refactor, check if still used
func getCurrentStaff(request *http.Request) (string, error) {
staff, err := GetStaffFromRequest(request)
if err != nil {
return "", err
@ -104,10 +111,12 @@ func GetStaffFromRequest(request *http.Request) (*gcsql.Staff, error) {
return &gcsql.Staff{Rank: 0}, nil
}
staff, err := gcsql.GetStaffBySession(sessionCookie.Value)
if err != nil {
staff = &gcsql.Staff{Rank: 0}
if errors.Is(err, sql.ErrNoRows) {
return &gcsql.Staff{Rank: 0}, nil
} else if err != nil {
return nil, err
}
return staff, err
return staff, nil
}
// GetStaffRank returns the rank number of the staff referenced in the request
@ -119,9 +128,10 @@ func GetStaffRank(request *http.Request) int {
return staff.Rank
}
func init() {
func InitManagePages() {
RegisterManagePage("actions", "Staff actions", JanitorPerms, AlwaysJSON, getStaffActions)
RegisterManagePage("dashboard", "Dashboard", JanitorPerms, NoJSON, dashboardCallback)
RegisterManagePage(dashboardAction.ID, dashboardAction.Title, dashboardAction.Permissions, dashboardAction.JSONoutput, dashboardAction.Callback)
server.GetRouter().GET(config.WebPath("/manage"), setupManageFunction(&dashboardAction))
registerNoPermPages()
registerJanitorPages()
registerModeratorPages()

View file

@ -0,0 +1,15 @@
<a href="{{webPath `/manage/filters/`}}">Back to filter list</a><br/>
{{with .hits}}
<table class="mgmt-table filterlist">
<tr><th>Filter ID</th><th>Match time</th><th>Post data</th></tr>
{{range $_, $hit := .}}
<tr>
<td><a href="{{webPath `/manage/filters`}}?edit={{$hit.FilterID}}">{{$hit.FilterID}}</a></td>
<td>{{formatTimestamp $hit.MatchTime}}</td>
<td><textarea rows="12" cols="80">{{$hit.PostData}}</textarea></td>
</tr>
{{end}}
</table>
{{else}}
<i>No hits</i>
{{end}}

View file

@ -110,7 +110,7 @@ For wordfilters go <a href="{{webPath `/manage/wordfilters`}}">here</a>.
</form>
<table class="mgmt-table filterlist">
<colgroup>
<col class="row-actions">
<col class="filter-actions">
<col class="filter-action">
<col class="filter-conditions">
<col class="staff-boards">
@ -125,7 +125,11 @@ For wordfilters go <a href="{{webPath `/manage/wordfilters`}}">here</a>.
{{- range $f, $filter := $.filters -}}
{{$boardsCell := index $.filterTableBoards $f}}
<tr>
<td><a href="{{webPath `/manage/filters`}}?edit={{$filter.ID}}">Edit</a> | <a href="{{webPath `/manage/filters`}}?{{if $filter.IsActive}}disable{{else}}enable{{end}}={{$filter.ID}}">{{if $filter.IsActive}}Disable{{else}}Enable{{end}}</a></td>
<td class="filter-actions">
<a href="{{webPath `/manage/filters`}}?edit={{$filter.ID}}">Edit</a> |
<a href="{{webPath `/manage/filters`}}?{{if $filter.IsActive}}disable{{else}}enable{{end}}={{$filter.ID}}">{{if $filter.IsActive}}Disable{{else}}Enable{{end}}</a> |
<a href="{{webPathDir `/manage/filters/hits/`}}{{$filter.ID}}">Hits</a>
</td>
<td>{{index $.actions $filter.MatchAction}}</td>
<td>{{index $.conditions $f}}</td>
<td>{{if eq $boardsCell ""}}<i>all boards</i>{{else}}{{$boardsCell}}{{end}}</td>