1
0
Fork 0
mirror of https://github.com/Eggbertx/gochan.git synced 2025-08-01 22:26:24 -07:00

Use router package instead of GET parameter for manage pages

This commit is contained in:
Eggbertx 2023-01-04 15:33:50 -08:00
parent d94a0d8fcf
commit aba84ceed2
33 changed files with 105 additions and 121 deletions

View file

@ -13,7 +13,7 @@ Demo installation: https://gochan.org
3. If you're using nginx, copy gochan-http.nginx, or gochan-fastcgi.nginx if `UseFastCGI` is set to true to /etc/nginx/sites-enabled/, or the appropriate folder in Windows.
4. If you're using a Linux distribution with systemd, you can optionally copy gochan.service to /lib/systemd/system/gochan.service and run `systemctl enable gochan.service` to have it run on startup. Then run `systemctl start gochan.service` to start it as a background service.
1. If you aren't using a distro with systemd, you can start a screen session and run `/path/to/gochan`
5. Go to http://[gochan url]/manage?action=staff, log in (default username/password is admin/password), and create a new admin user (and any other staff users as necessary). Then delete the admin user for security.
5. Go to http://[gochan url]/manage/staff, log in (default username/password is admin/password), and create a new admin user (and any other staff users as necessary). Then delete the admin user for security.
## Installation using Docker
See [`docker/README.md`](docker/README.md)

View file

@ -366,7 +366,7 @@ def install(prefix="/usr", document_root="/srv/gochan", symlinks=False, js_only=
print(
"gochan was successfully installed. If you haven't already, you should copy\n",
"sample-configs/gochan.example.json to /etc/gochan/gochan.json (modify as needed)\n",
"You may also need to go to https://yourgochansite/manage?action=rebuildall to rebuild the javascript config")
"You may also need to go to https://yourgochansite/manage/rebuildall to rebuild the javascript config")
if gcos == "linux":
print(
"If your Linux distribution has systemd, you will also need to run the following commands:\n",

View file

@ -21,7 +21,7 @@ the README and/or the -h command line flag before you use it.
migrateCompleteTxt = `Database migration successful!
To migrate the uploads for each board, move or copy the uploads to /path/to/gochan/document/root/<boardname>/src/
Then copy the thumbnails to /path/to/gochan/documentroot/<boardname>/thumb/
Then start the gochan server and go to http://yoursite/manage?action=rebuildall to generate the html files
Then start the gochan server and go to http://yoursite/manage/rebuildall to generate the html files
for the threads and board pages`
allowedDirActions = "Valid values are noaction, copy, and move (defaults to noaction if unset)"

View file

@ -10,6 +10,8 @@ import (
"strconv"
"strings"
"github.com/uptrace/bunrouter"
"github.com/gochan-org/gochan/pkg/config"
"github.com/gochan-org/gochan/pkg/gcutil"
"github.com/gochan-org/gochan/pkg/manage"
@ -18,14 +20,10 @@ import (
)
var (
server *gochanServer
router *bunrouter.Router
)
type gochanServer struct {
namespaces map[string]func(http.ResponseWriter, *http.Request)
}
func (s gochanServer) serveFile(writer http.ResponseWriter, request *http.Request) {
func serveFile(writer http.ResponseWriter, request *http.Request) {
systemCritical := config.GetSystemCriticalConfig()
siteConfig := config.GetSiteConfig()
@ -56,7 +54,7 @@ func (s gochanServer) serveFile(writer http.ResponseWriter, request *http.Reques
return
}
}
s.setFileHeaders(filePath, writer)
setFileHeaders(filePath, writer)
// serve the requested file
fileBytes, _ = os.ReadFile(filePath)
@ -65,7 +63,7 @@ func (s gochanServer) serveFile(writer http.ResponseWriter, request *http.Reques
}
// set mime type/cache headers according to the file's extension
func (*gochanServer) setFileHeaders(filename string, writer http.ResponseWriter) {
func setFileHeaders(filename string, writer http.ResponseWriter) {
extension := strings.ToLower(path.Ext(filename))
switch extension {
case ".png":
@ -102,16 +100,6 @@ func (*gochanServer) setFileHeaders(filename string, writer http.ResponseWriter)
}
}
func (s gochanServer) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
for name, namespaceFunction := range s.namespaces {
if request.URL.Path == config.WebPath(name) {
namespaceFunction(writer, request)
return
}
}
s.serveFile(writer, request)
}
func initServer() {
systemCritical := config.GetSystemCriticalConfig()
siteConfig := config.GetSiteConfig()
@ -124,8 +112,9 @@ func initServer() {
Int("Port", systemCritical.Port).Send()
fmt.Printf("Failed listening on %s:%d: %s", systemCritical.ListenIP, systemCritical.Port, err.Error())
}
server = new(gochanServer)
server.namespaces = make(map[string]func(http.ResponseWriter, *http.Request))
router = bunrouter.New(
bunrouter.WithNotFoundHandler(bunrouter.HTTPHandlerFunc(serveFile)),
)
// Check if Akismet API key is usable at startup.
err = serverutil.CheckAkismetAPIKey(siteConfig.AkismetAPIKey)
@ -135,22 +124,21 @@ func initServer() {
fmt.Println("Got error when initializing Akismet spam protection, it will be disabled:", err)
}
server.namespaces["captcha"] = posting.ServeCaptcha
server.namespaces["manage"] = manage.CallManageFunction
server.namespaces["post"] = posting.MakePost
server.namespaces["util"] = utilHandler
server.namespaces["example"] = func(writer http.ResponseWriter, request *http.Request) {
if writer != nil {
http.Redirect(writer, request, "https://www.youtube.com/watch?v=dQw4w9WgXcQ", http.StatusFound)
}
}
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.POST(config.WebPath("/post"), bunrouter.HTTPHandlerFunc(posting.MakePost))
router.GET(config.WebPath("/util"), bunrouter.HTTPHandlerFunc(utilHandler))
router.POST(config.WebPath("/util"), bunrouter.HTTPHandlerFunc(utilHandler))
// Eventually plugins might be able to register new namespaces or they might be restricted to something
// like /plugin
if systemCritical.UseFastCGI {
err = fcgi.Serve(listener, server)
err = fcgi.Serve(listener, router)
} else {
err = http.Serve(listener, server)
err = http.Serve(listener, router)
}
if err != nil {

View file

@ -57,7 +57,7 @@ def gotoPage(driver: WebDriver, page: str):
def isLoggedIn(driver: WebDriver):
gotoPage(driver, "manage?action=login")
gotoPage(driver, "manage/login")
return driver.find_element(by=By.CSS_SELECTOR, value="h1#board-title").text == "Dashboard"
@ -177,7 +177,7 @@ class TestRunner(unittest.TestCase):
if boardExists("seleniumtesting"):
raise Exception("Board /seleniumtests/ already exists")
loginToStaff(self.driver)
gotoPage(self.driver, "manage?action=boards")
gotoPage(self.driver, "manage/boards")
# fill out the board creation form
self.driver.find_element(by=By.NAME, value="dir").\
@ -197,7 +197,7 @@ class TestRunner(unittest.TestCase):
makePostOnPage("seleniumtesting", self)
gotoPage(self.driver, "manage?action=boards")
gotoPage(self.driver, "manage/boards")
sel = Select(self.driver.find_element(by=By.ID, value="modifyboard"))
sel.select_by_visible_text("/seleniumtesting/ - Selenium testing")
self.driver.find_element(by=By.NAME, value="dodelete").click()
@ -221,7 +221,7 @@ class TestRunner(unittest.TestCase):
def test_moveThread(self):
if not boardExists("test2"):
loginToStaff(self.driver)
gotoPage(self.driver, "manage?action=boards")
gotoPage(self.driver, "manage/boards")
# fill out the board creation form
self.driver.find_element(by=By.NAME, value="dir").\

View file

@ -31,7 +31,7 @@ If you want, you can install [Sass](https://sass-lang.com/install) to streamline
To use sass, run `./build.py sass`. If you want to minify the created css files, use the `--minify` flag. If you want sass to watch the input directory for changes as you edit and save the files, use the `--watch` flag.
If you are upgading from gochan 2.2, delete your html/css directory unless you have made themes that you want to keep. Then rebuild the pages. (/manage?action=rebuildall)
If you are upgading from gochan 2.2, delete your html/css directory unless you have made themes that you want to keep. Then rebuild the pages. (/manage/rebuildall)
## Attribution
The BunkerChan, Clear, and Dark themes come from the imageboard BunkerChan. Burichan is based on the theme with the same name from Kusaba X. Photon comes from nol.ch (I think?) that as far as I know no longer exists. Pipes was created by a user (Mbyte?) on Lunachan (defunct). Yotsuba and Yotsuba B are based on the themes with the same names from 4chan.

View file

@ -157,7 +157,7 @@ function handleActions(action, postIDStr) {
// manage stuff
case "Posts from this IP":
getPostInfo(postID).then(info => {
window.open(`${webroot}manage?action=ipsearch&limit=100&ip=${info.ip}`);
window.open(`${webroot}manage/ipsearch?limit=100&ip=${info.ip}`);
}).catch(reason => {
alertLightbox(`Failed getting post IP: ${reason.statusText}`, "Error");
});

View file

@ -4,7 +4,7 @@ import "jquery-ui/ui/unique-id";
import "jquery-ui/ui/keycode";
import "jquery-ui/ui/widgets/tabs";
$(() => {
if(window.location.search.indexOf("?action=filebans") != 0)
if(window.location.pathname != webroot + "manage/filebans")
return;
$("div#fileban-tabs").tabs();
});

View file

@ -92,7 +92,7 @@ export function banFile(banType, filename, checksum, staffNote = "") {
}
return $.ajax({
method: "POST",
url: `${webroot}manage?action=filebans`,
url: `${webroot}manage/filebans`,
data: xhrFields
});
}
@ -100,10 +100,7 @@ export function banFile(banType, filename, checksum, staffNote = "") {
export async function initStaff() {
return $.ajax({
method: "GET",
url: `${webroot}manage`,
data: {
action: "actions"
},
url: `${webroot}manage/actions`,
async: true,
cache: false,
success: result => {
@ -138,10 +135,7 @@ export async function getStaffInfo() {
loginChecked = true;
return $.ajax({
method: "GET",
url: `${webroot}manage`,
data: {
action: "staffinfo",
},
url: `${webroot}manage/staffinfo`,
async: true,
cache: true,
dataType: "json"
@ -158,9 +152,8 @@ export async function getStaffInfo() {
export async function getPostInfo(id) {
return $.ajax({
method: "GET",
url: `${webroot}manage`,
url: `${webroot}manage/postinfo`,
data: {
action: "postinfo",
postid: id
},
async: true,
@ -191,7 +184,7 @@ export function banSelectedPost() {
break;
}
}
window.location = `${webroot}manage?action=bans&dir=${boardDir}&postid=${postID}`;
window.location = `${webroot}manage/bans?dir=${boardDir}&postid=${postID}`;
}
/**
@ -201,7 +194,7 @@ export function banSelectedPost() {
function menuItem(action, isCategory = false) {
return isCategory ? $("<div/>").append($("<b/>").text(action)) : $("<div/>").append(
$("<a/>").prop({
href: `${webroot}manage?action=${action.id}`
href: `${webroot}manage/${action.id}`
}).text(action.title)
);
}
@ -294,9 +287,8 @@ function updateReports(reports) {
function getReports() {
return $.ajax({
method: "GET",
url: `${webroot}manage`,
url: `${webroot}manage/reports`,
data: {
action: "reports",
json: "1"
},
async: true,

View file

@ -1,4 +1,4 @@
// Make the sections table on /manage?action=boardsections sortable to make changing the list order easier
// Make the sections table on /manage/boardsections sortable to make changing the list order easier
/* global webroot */
@ -28,7 +28,7 @@ function applyOrderChanges() {
let sectionhidden = $el.find(":nth-child(4)").html().toLowerCase() == "yes"?"on":"off";
$.ajax({
method: "POST",
url: webroot + "manage?action=boardsections",
url: webroot + "manage/boardsections",
data: {
updatesection: updatesection,
sectionname: sectionname,
@ -81,7 +81,7 @@ function addButtons() {
}
$(() => {
if(window.location.search.indexOf("?action=boardsections") != 0)
if(window.location.pathname != webroot + "manage/boardsections")
return;
$sectionsTable = $("table#sections");

View file

@ -91,7 +91,7 @@ declare interface ThreadPost {
}
/**
* An object representing a staff member retreived by requesting /manage?action=staffinfo
* An object representing a staff member retreived by requesting /manage/staffinfo
*/
interface StaffInfo {
/**
@ -117,7 +117,7 @@ declare interface ThreadPost {
*/
interface StaffAction {
/**
* The GET key used when requesting /manage?action=<id>
* The GET key used when requesting /manage/<id>
*/
id?:string;
/**
@ -142,7 +142,7 @@ interface StaffAction {
}
/**
* The result of requesting /manage?action=actions
* The result of requesting /manage/actions
*/
declare var staffActions: StaffAction[];

View file

@ -8440,9 +8440,9 @@
"dev": true
},
"node_modules/json5": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true,
"bin": {
"json5": "lib/cli.js"
@ -16345,9 +16345,9 @@
"dev": true
},
"json5": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true
},
"kleur": {

1
go.mod
View file

@ -13,6 +13,7 @@ require (
github.com/tdewolff/minify v2.3.6+incompatible
github.com/tdewolff/parse v2.3.4+incompatible // indirect
github.com/tdewolff/test v1.0.7 // indirect
github.com/uptrace/bunrouter v1.0.19 // indirect
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b

8
go.sum
View file

@ -4,6 +4,7 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/frustra/bbcode v0.0.0-20201127003707-6ef347fbe1c8 h1:sdIsYe6Vv7KIWZWp8KqSeTl+XlF17d+wHCC4lbxFcYs=
@ -20,15 +21,20 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY=
github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tdewolff/minify v2.3.6+incompatible h1:2hw5/9ZvxhWLvBUnHE06gElGYz+Jv9R4Eys0XUzItYo=
github.com/tdewolff/minify v2.3.6+incompatible/go.mod h1:9Ov578KJUmAWpS6NeZwRZyT56Uf6o3Mcz9CEsg8USYs=
github.com/tdewolff/parse v2.3.4+incompatible h1:x05/cnGwIMf4ceLuDMBOdQ1qGniMoxpP46ghf0Qzh38=
github.com/tdewolff/parse v2.3.4+incompatible/go.mod h1:8oBwCsVmUkgHO8M5iCzSIDtpzXOT0WXX9cWhz+bIzJQ=
github.com/tdewolff/test v1.0.7 h1:8Vs0142DmPFW/bQeHRP3MV19m1gvndjUb1sn8yy74LM=
github.com/tdewolff/test v1.0.7/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/uptrace/bunrouter v1.0.19 h1:kdN1Nl/9RDq9eBPnjS6GvNKcgiAeMxdb9DbpslLndFg=
github.com/uptrace/bunrouter v1.0.19/go.mod h1:TwT7Bc0ztF2Z2q/ZzMuSVkcb/Ig/d3MQeP2cxn3e1hI=
github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 h1:5mLPGnFdSsevFRFc9q3yYbBkB6tsm4aCwwQV/j1JQAQ=
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
@ -56,5 +62,7 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
layeh.com/gopher-luar v1.0.10 h1:55b0mpBhN9XSshEd2Nz6WsbYXctyBT35azk4POQNSXo=
layeh.com/gopher-luar v1.0.10/go.mod h1:TPnIVCZ2RJBndm7ohXyaqfhzjlZ+OA2SZR/YwL8tECk=

View file

@ -4,8 +4,8 @@
<title>gochan</title>
</head>
<body>
Welcome to gochan! The front page hasn't been created yet, so you will need to log in and build it. You can do so <a href="/manage?action=rebuildall">here</a>. The default username is "admin", and the default password is "password". For security reasons, you should probably change that befor you go public.<br />
Next, you should create a board, which you can do <a href="/manage?action=boards">here</a>.<br />
Welcome to gochan! The front page hasn't been created yet, so you will need to log in and build it. You can do so <a href="/manage/rebuildall">here</a>. The default username is "admin", and the default password is "password". For security reasons, you should probably change that befor you go public.<br />
Next, you should create a board, which you can do <a href="/manage/boards">here</a>.<br />
Thank you for using gochan.
</body>
</html>

View file

@ -161,7 +161,7 @@ type Post struct {
Subject string // sql: `subject`
Message template.HTML // sql: `message`
MessageRaw string // sql: `message_raw`
Password string // sql: `password`
Password string `json:"-"` // sql: `password`
DeletedAt time.Time // sql: `deleted_at`
IsDeleted bool // sql: `is_deleted`
BannedMessage string // sql: `banned_message`
@ -208,11 +208,11 @@ type LoginSession struct {
type Staff struct {
ID int // sql: `id`
Username string // sql: `username`
PasswordChecksum string // sql: `password_checksum`
PasswordChecksum string `json:"-"` // sql: `password_checksum`
Rank int // sql: `global_rank`
AddedOn time.Time // sql: `added_on`
LastLogin time.Time // sql: `last_login`
IsActive bool // sql: `is_active`
AddedOn time.Time `json:"-"` // sql: `added_on`
LastLogin time.Time `json:"-"` // sql: `last_login`
IsActive bool `json:"-"` // sql: `is_active`
}
// table: DBPREFIXthreads

View file

@ -46,9 +46,9 @@ var (
chopPortNumRegex = regexp.MustCompile(`(.+|\w+):(\d+)$`)
)
// Action represents the functions accessed by staff members at /manage?action=<functionname>.
// Action represents the functions accessed by staff members at /manage/<functionname>.
type Action struct {
// the string used when the user requests /manage?action=<id>
// the string used when the user requests /manage/<ID>
ID string `json:"id"`
// The text shown in the staff menu and the window title
@ -159,7 +159,7 @@ var actions = []Action{
}
outputStr += "Cleanup finished"
} else {
outputStr += `<form action="/manage?action=cleanup" method="post">` +
outputStr += `<form action="/manage/cleanup" method="post">` +
`<input name="run" id="run" type="submit" value="Run Cleanup" />` +
`</form>`
}
@ -781,7 +781,7 @@ var actions = []Action{
} else {
key := gcutil.Md5Sum(request.RemoteAddr + username + password + systemCritical.RandomSeed + gcutil.RandomString(3))[0:10]
createSession(key, username, password, request, writer)
http.Redirect(writer, request, path.Join(systemCritical.WebRoot, "manage?action="+request.FormValue("redirect")), http.StatusFound)
http.Redirect(writer, request, path.Join(systemCritical.WebRoot, "manage/"+request.FormValue("redirect")), http.StatusFound)
}
return
}},

View file

@ -10,6 +10,7 @@ import (
"github.com/gochan-org/gochan/pkg/gcsql"
"github.com/gochan-org/gochan/pkg/gcutil"
"github.com/gochan-org/gochan/pkg/serverutil"
"github.com/uptrace/bunrouter"
)
type ErrStaffAction struct {
@ -43,13 +44,12 @@ func CallManageFunction(writer http.ResponseWriter, request *http.Request) {
errEv.Str("IP", gcutil.GetRealIP(request))
if err = request.ParseForm(); err != nil {
errEv.
Str("IP", gcutil.GetRealIP(request)).
Caller().Msg("Error parsing form data")
errEv.Err(err).Caller().Msg("Error parsing form data")
serverutil.ServeError(writer, "Error parsing form data: "+err.Error(), wantsJSON, nil)
return
}
actionID := request.FormValue("action")
params := bunrouter.ParamsFromContext(request.Context())
actionID := params.ByName("action")
gcutil.LogStr("action", actionID, infoEv, accessEv, errEv)
var staff *gcsql.Staff

View file

@ -50,7 +50,7 @@ func ServeErrorPage(writer http.ResponseWriter, err string) {
// ServeNotFound shows an error page if a requested file is not found
func ServeNotFound(writer http.ResponseWriter, request *http.Request) {
writer.Header().Set("Content-Type", "text/html; charset=utf-8")
writer.WriteHeader(404)
writer.WriteHeader(http.StatusNotFound)
systemCritical := config.GetSystemCriticalConfig()
errorPage, err := os.ReadFile(systemCritical.DocumentRoot + "/error/404.html")
if err != nil {

View file

@ -1,7 +1,7 @@
<table border="1">
<tr><th>Action</th><th>Appeal Text</th><th>Banned IP</th></tr>
{{range $_,$appeal := $.appeals}}
<tr><td><a href="{{webPath "manage"}}?action=appeals&approve={{$appeal.ID}}">Approve</a> | <a href="{{webPath "manage"}}?action=appeals&deny={{$appeal.ID}}">Deny</a></td><td>{{$appeal.AppealText}}</td><td>{{getAppealBanIP $appeal.IPBanID}}</td></tr>
<tr><td><a href="{{webPath "manage/appeals"}}?approve={{$appeal.ID}}">Approve</a> | <a href="{{webPath "manage/appeals"}}?deny={{$appeal.ID}}">Deny</a></td><td>{{$appeal.AppealText}}</td><td>{{getAppealBanIP $appeal.IPBanID}}</td></tr>
<tr></tr>
{{end}}
</table>

View file

@ -1,4 +1,4 @@
<form method="POST" action="{{webPath "manage?action=bans"}}">
<form method="POST" action="{{webPath "manage/bans"}}">
<input type="hidden" name="do" value="add" />
<h2>Add IP ban</h2>
<table>
@ -20,13 +20,12 @@
<tr><th>Reason</th><td><textarea name="reason" style="width: 100%;" rows="6" placeholder="Message to be displayed to the banned user"></textarea></td></tr>
<tr><th>Staff note</th><td><textarea name="staffnote" style="width: 100%;" rows="6" placeholder="Private note that only staff can see"></textarea></td></tr>
</table>
<input type="submit" value="Ban user" /> <input type="button" name="docancel" value="Cancel" onclick="window.location = './manage?action=bans'; return false"/>
<input type="submit" value="Ban user" /> <input type="button" name="docancel" value="Cancel" onclick="window.location = './manage/bans'; return false"/>
</form>
<h2 id="banlist">Banlist</h2>
<form action="{{webPath "manage?action=bans"}}" method="get">
<input type="hidden" name="action" value="bans">
Filter board: <select name="filterboardid" id="filterboardid" onchange="window.location = '{{webPath "manage?action=bans&filterboardid="}}' + this.value + '#banlist'">
<form action="{{webPath "manage/bans"}}" method="get">
Filter board: <select name="filterboardid" id="filterboardid" onchange="window.location = '{{webPath "manage/bans?filterboardid="}}' + this.value + '#banlist'">
<option value="0">All boards</option>
{{- range $b, $board := $.allBoards -}}
<option value="{{$board.ID}}" {{if eq $.filterboardid $board.ID}}selected{{end}}>/{{$board.Dir}}/ - {{$board.Title}}</option>
@ -37,7 +36,7 @@ Filter board: <select name="filterboardid" id="filterboardid" onchange="window.l
<tr><th>Action</th><th>IP</th><th>Board</th><th>Reason</th><th>Staff</th><th>Staff note</th><th>Banned post text</th><th>Set</th><th>Expires</th><th>Appeal at</th></tr>
{{range $_, $ban := $.banlist -}}
<tr>
<td> <a href="{{webPath "manage?action=bans&edit="}}{{$ban.ID}}">Edit</a> | <a href="{{webPath "manage?action=bans&delete="}}{{$ban.ID}}">Delete</a> </td>
<td> <a href="{{webPath "manage/bans?edit="}}{{$ban.ID}}">Edit</a> | <a href="{{webPath "manage/bans?delete="}}{{$ban.ID}}">Delete</a> </td>
<td>{{$ban.IP}}</td>
<td>{{if not $ban.BoardID}}<i>all</i>{{else}}/{{getBoardDirFromID $ban.BoardID}}/{{end}}</td>
<td>{{$ban.Message}}</td>

View file

@ -1,5 +1,4 @@
<form action="{{$.webroot}}manage?action=boards" method="GET">
<input type="hidden" name="action" value="boards">
<form action="{{$.webroot}}manage/boards" method="GET">
{{with $.boards}}{{else}}
<input type="hidden" name="noboards" value="1">
{{end}}
@ -20,8 +19,7 @@
{{else}}
<h2>Create new board</h2>
{{end}}
<form action="{{$.webroot}}manage?action=boards" method="POST">
<input type="hidden" name="action" value="boards">
<form action="{{$.webroot}}manage/boards" method="POST">
<input type="hidden" name="board" value="{{$.board.ID}}"/>
<table>
<tr>
@ -127,7 +125,7 @@
</table>
{{- if $.editing -}}
<input type="submit" name="domodify" value="Save changes" onclick="return confirm('Click ok to confirm')"/>
<input type="submit" name="docancel" value="Cancel" onclick="window.location = './manage?action=boards'; return false"/>
<input type="submit" name="docancel" value="Cancel" onclick="window.location = './manage/boards'; return false"/>
{{- else -}}
<input type="submit" name="docreate" value="Create new board" onclick="return confirm('Click ok to confirm')"/>
{{- end -}}

View file

@ -2,7 +2,7 @@
Some fields omitted because they can not be (safely) edited from the web interface while Gochan is running.
Edit these directly in gochan.json, then restart Gochan.<br />
<span class="warning">This config editor isn't fully stable so MAKE BACKUPS!</span>
<form action="/manage?action=config" method="POST">
<form action="/manage/config" method="POST">
<input name="do" value="save" type="hidden" />
{{generateConfigTable}}<br />
<input type="submit" />

View file

@ -23,7 +23,7 @@
<fieldset><legend>Staff actions (role: {{$.rankString}})</legend>
<ul>
{{range $a, $action := $.actions}}
{{if ne $action.Title "Dashboard"}}<li><a href="{{$.webroot}}manage?action={{$action.ID}}">{{$action.Title}}</a> </li>{{end}}
{{if ne $action.Title "Dashboard"}}<li><a href="{{$.webroot}}manage/{{$action.ID}}">{{$action.Title}}</a> </li>{{end}}
{{end}}
</ul>
</fieldset>

View file

@ -5,7 +5,7 @@
</ul>
<div id="filename-bans">
<h2>Create new filename ban</h2>
<form id="filenamebanform" action="{{.webroot}}manage?action=filebans" method="POST">
<form id="filenamebanform" action="{{.webroot}}manage/filebans" method="POST">
<input type="hidden" name="bantype" value="filename">
<table>
<tr><td>Filename:</td><td><input type="text" name="filename" id="filename"></td></tr>
@ -34,7 +34,7 @@
<td>{{$staff := (getStaffNameFromID $ban.StaffID)}}{{if eq $staff ""}}<i>?</i>{{else}}{{$staff}}{{end}}</td>
<td>{{$ban.StaffNote}}</td>
<td><a href="{{$.webroot}}manage?action=filebans&delfnb={{$ban.ID}}">Delete</a></td>
<td><a href="{{$.webroot}}manage/filebans?delfnb={{$ban.ID}}">Delete</a></td>
</tr>
{{end -}}
</table>
@ -42,7 +42,7 @@
</div>
<div id="checksum-bans">
<h2>Create new file checksum ban</h2>
<form id="checksumbanform" action="{{.webroot}}manage?action=filebans#checksum-bans" method="POST">
<form id="checksumbanform" action="{{.webroot}}manage/filebans#checksum-bans" method="POST">
<input type="hidden" name="bantype" value="checksum">
<table>
<tr><td>Checksum</td><td><input type="text" name="checksum"></td></tr>
@ -68,7 +68,7 @@
<td>{{$uri := (intPtrToBoardDir $ban.BoardID "" "?")}}{{if eq $uri ""}}<i>All boards</i>{{else}}/{{$uri}}/{{end}}</td>
<td>{{$staff := (getStaffNameFromID $ban.StaffID)}}{{if eq $staff ""}}<i>?</i>{{else}}{{$staff}}{{end}}</td>
<td>{{$ban.StaffNote}}</td>
<td><a href="{{$.webroot}}manage?action=filebans&delcsb={{$ban.ID}}#checksum-bans">Delete</a></td>
<td><a href="{{$.webroot}}manage/filebans?delcsb={{$ban.ID}}#checksum-bans">Delete</a></td>
</tr>
{{- end -}}
</table>

View file

@ -5,8 +5,7 @@
{{- end -}}
<fieldset>
<legend>Search</legend>
<form method="GET" action="{{.webroot}}manage" class="staff-form">
<input type="hidden" name="action" value="ipsearch"/>
<form method="GET" action="{{.webroot}}manage/ipsearch" class="staff-form">
<label for="ip">IP Address</label>
<input type="text" name="ip" id="ipquery" value="{{.ipQuery}}"><br />
<label for="number">Max results</label>

View file

@ -1,4 +1,4 @@
<form method="POST" action="{{.webroot}}manage?action=login" id="login-box" class="staff-form">
<form method="POST" action="{{.webroot}}manage/login" id="login-box" class="staff-form">
<input type="hidden" name="redirect" value="{{.redirect}}" />
<table>
<tr><td>Login</td><td><input type="text" name="username" class="logindata" /><br /></td></tr>

View file

@ -1,5 +1,5 @@
<h2>Create a new name/tripcode ban</h2>
<form id="namebanform" action="{{.webroot}}manage?action=namebans" method="post">
<form id="namebanform" action="{{.webroot}}manage/namebans" method="post">
<table>
<tr><td>Name/Tripcode:</td><td><input type="text" name="name" id="name"> (ex: "Name", "Name!Tripcode", "!Tripcode, etc)</td></tr>
<tr><td>Regular expression:</td><td><input type="checkbox" name="isregex" id="isregex"/></td></tr>
@ -25,7 +25,7 @@
<td>{{$uri := (intPtrToBoardDir $ban.BoardID "" "?")}}{{if eq $uri ""}}<i>All boards</i>{{else}}/{{$uri}}/{{end}}</td>
<td>{{$staff := (getStaffNameFromID $ban.StaffID)}}{{if eq $staff ""}}<i>?</i>{{else}}{{$staff}}{{end}}</td>
<td>{{$ban.StaffNote}}</td>
<td><a href="{{$.webroot}}manage?action=namebans&del={{$ban.ID}}">Delete</a></td>
<td><a href="{{$.webroot}}manage/namebans?del={{$ban.ID}}">Delete</a></td>
{{end -}}
</table>
{{end}}

View file

@ -1,5 +1,4 @@
<form action="{{$.webroot}}manage" method="GET">
<input type="hidden" name="action" value="recentposts">
<form action="{{$.webroot}}manage/recentposts" method="GET">
<label for="boardid">Board:</label>
<select name="boardid" id="boardid">
<option value="0">All boards</option>

View file

@ -9,10 +9,10 @@
{{$report.staff_user}}
{{- end -}}
</td><td>
<a href="{{webPath "manage?action=reports&dismiss="}}{{$report.id}}">Dismiss</a>
<a href="{{webPath "manage/reports?dismiss="}}{{$report.id}}">Dismiss</a>
{{if eq $.staff.Rank 3 -}}
|
<a href="{{webPath "manage?action=reports&dismiss="}}{{$report.id}}&block=1" title="Prevent future reports of this post, regardless of report reason">Make post unreportable</a>
<a href="{{webPath "manage/reports?dismiss="}}{{$report.id}}&block=1" title="Prevent future reports of this post, regardless of report reason">Make post unreportable</a>
{{- end}}
</td></tr>
{{end}}

View file

@ -1,4 +1,4 @@
<form action="{{.webroot}}manage?action=boardsections" method="POST" id="sectionform">
<form action="{{.webroot}}manage/boardsections" method="POST" id="sectionform">
{{with .edit_section}}<input type="hidden" name="updatesection" value="{{.ID}}" />{{end}}
<h2>{{with .edit_section}}Edit{{else}}New{{end}} section</h2>
<table>
@ -9,7 +9,7 @@
</table>
<input type="submit" name="save_section" value="{{with .edit_section}}Save{{else}}Create{{end}} section">
{{with .edit_section}}
<input type="button" onclick="window.location='{{$.webroot}}manage?action=boardsections'" value="Cancel">
<input type="button" onclick="window.location='{{$.webroot}}manage/boardsections'" value="Cancel">
{{else}}
<input type="button" onclick="document.getElementById('sectionform').reset()" value="Reset"/>
{{end}}
@ -24,8 +24,8 @@
<td>{{$section.Abbreviation}}</td>
<td>{{$section.Position}}</td>
<td>{{if eq $section.Hidden true}}Yes{{else}}No{{end}}</td>
<td><a href="{{$.webroot}}manage?action=boardsections&edit={{$section.ID}}">Edit</a> |
<a href="{{$.webroot}}manage?action=boardsections&delete={{$section.ID}}" onclick="return confirm('Are you sure you want to delete this section?')">Delete</a></td>
<td><a href="{{$.webroot}}manage/boardsections?edit={{$section.ID}}">Edit</a> |
<a href="{{$.webroot}}manage/boardsections?delete={{$section.ID}}" onclick="return confirm('Are you sure you want to delete this section?')">Delete</a></td>
</tr>
{{end}}
</table>

View file

@ -12,16 +12,16 @@
<td>{{formatTimestamp $staff.AddedOn}}</td>
<td>
<a {{if eq $staff.Username $.currentUsername -}}
href="{{$.webroot}}manage?action=staff" title="Cannot self terminate" style="color: black;"
href="{{$.webroot}}manage/staff" title="Cannot self terminate" style="color: black;"
{{- else -}}
href="{{$.webroot}}manage?action=staff&do=del&username={{$staff.Username}}" title="Delete {{$staff.Username}}" style="color:red;"
href="{{$.webroot}}manage/staff?do=del&username={{$staff.Username}}" title="Delete {{$staff.Username}}" style="color:red;"
{{end}}>Delete</a>
</td>
</tr>
{{end}}
</table><hr />
<h2>Add new staff</h2>
<form action="{{.webroot}}manage?action=staff" onsubmit="return makeNewStaff();" method="POST">
<form action="{{.webroot}}manage/staff" onsubmit="return makeNewStaff();" method="POST">
<input type="hidden" name="do" value="add" />
<table>
<tr><td>Username:</td><td><input id="username" name="username" type="text"/></td></tr>

View file

@ -1,5 +1,5 @@
<h2>{{with $.edit}}Edit filter{{else}}Create new{{end}}</h2>
<form id="wordfilterform" action="{{.webroot}}manage?action=wordfilters{{with $.edit}}&edit={{$.edit.ID}}{{end}}" method="POST">
<form id="wordfilterform" action="{{.webroot}}manage/wordfilters{{with $.edit}}?edit={{$.edit.ID}}{{end}}" method="POST">
<table>
<tr><td>Search for:</td><td><input type="text" name="find" id="findfilter" value="{{with $.edit}}{{$.edit.Search}}{{end}}"/></td></tr>
<tr><td>Replace with:</td><td><input type="text" name="replace" id="replacefilter" value="{{with $.edit}}{{$.edit.ChangeTo}}{{end}}"/></td></tr>
@ -9,7 +9,7 @@
<tr><td>
<input type="submit" name="dowordfilter" value="{{with $.edit}}Edit{{else}}Create new{{end}} wordfilter"/>
<input type="button" onclick="document.getElementById('wordfilterform').reset()" value="Reset"/>
{{with $.edit}}<input type="button" onclick="window.location='{{$.webroot}}manage?action=wordfilters'" value="Cancel"/>{{end}}
{{with $.edit}}<input type="button" onclick="window.location='{{$.webroot}}manage/wordfilters'" value="Cancel"/>{{end}}
</td></tr>
</table>
</form>
@ -21,7 +21,7 @@
<tr><th>Actions</th><th>Search</th><th>Replace with</th><th>Is regex</th><th>Dirs</th><th>Created by</th><th>Staff note</th></tr>
{{- range $f,$filter := .wordfilters}}
<tr>
<td><a href="{{$.webroot}}manage?action=wordfilters&edit={{$filter.ID}}">Edit</a> | <a href="{{$.webroot}}manage?action=wordfilters&delete={{$filter.ID}}" onclick="return confirm('Are you sure you want to delete this wordfilter?')">Delete</a> </td>
<td><a href="{{$.webroot}}manage/wordfilters?edit={{$filter.ID}}">Edit</a> | <a href="{{$.webroot}}manage/wordfilters?delete={{$filter.ID}}" onclick="return confirm('Are you sure you want to delete this wordfilter?')">Delete</a> </td>
<td>{{$filter.Search}}</td>
<td>{{$filter.ChangeTo}}</td>
<td>{{if $filter.IsRegex}}yes{{else}}no{{end}}</td>