Maintenance Mode + Blockable Admin Scope
This commit is contained in:
parent
5f5fe37350
commit
bf71ed421c
|
@ -23,8 +23,10 @@ const (
|
||||||
// Website administration
|
// Website administration
|
||||||
// - Forum: ability to manage available forums
|
// - Forum: ability to manage available forums
|
||||||
// - Scopes: ability to manage admin groups & scopes
|
// - Scopes: ability to manage admin groups & scopes
|
||||||
|
// - Maintenance mode
|
||||||
ScopeForumAdmin = "admin.forum.manage"
|
ScopeForumAdmin = "admin.forum.manage"
|
||||||
ScopeAdminScopeAdmin = "admin.scope.manage"
|
ScopeAdminScopeAdmin = "admin.scope.manage"
|
||||||
|
ScopeMaintenance = "admin.maintenance"
|
||||||
|
|
||||||
// User account admin
|
// User account admin
|
||||||
// - Impersonate: ability to log in as a user account
|
// - Impersonate: ability to log in as a user account
|
||||||
|
@ -35,6 +37,9 @@ const (
|
||||||
ScopeUserPromote = "admin.user.promote"
|
ScopeUserPromote = "admin.user.promote"
|
||||||
ScopeUserDelete = "admin.user.delete"
|
ScopeUserDelete = "admin.user.delete"
|
||||||
|
|
||||||
|
// Admins with this scope can not be blocked by users.
|
||||||
|
ScopeUnblockable = "admin.unblockable"
|
||||||
|
|
||||||
// Special scope to mark an admin automagically in the Inner Circle
|
// Special scope to mark an admin automagically in the Inner Circle
|
||||||
ScopeIsInnerCircle = "admin.override.inner-circle"
|
ScopeIsInnerCircle = "admin.override.inner-circle"
|
||||||
)
|
)
|
||||||
|
@ -61,6 +66,7 @@ func ListAdminScopes() []string {
|
||||||
ScopeUserBan,
|
ScopeUserBan,
|
||||||
ScopeUserDelete,
|
ScopeUserDelete,
|
||||||
ScopeUserPromote,
|
ScopeUserPromote,
|
||||||
|
ScopeUnblockable,
|
||||||
ScopeIsInnerCircle,
|
ScopeIsInnerCircle,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,11 +11,16 @@ import (
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Version of the config format - when new fields are added, it will attempt
|
||||||
|
// to write the settings.toml to disk so new defaults populate.
|
||||||
|
var currentVersion = 1
|
||||||
|
|
||||||
// Current loaded settings.json
|
// Current loaded settings.json
|
||||||
var Current = DefaultVariable()
|
var Current = DefaultVariable()
|
||||||
|
|
||||||
// Variable configuration attributes (loaded from settings.json).
|
// Variable configuration attributes (loaded from settings.json).
|
||||||
type Variable struct {
|
type Variable struct {
|
||||||
|
Version int
|
||||||
BaseURL string
|
BaseURL string
|
||||||
AdminEmail string
|
AdminEmail string
|
||||||
CronAPIKey string
|
CronAPIKey string
|
||||||
|
@ -23,6 +28,7 @@ type Variable struct {
|
||||||
Redis Redis
|
Redis Redis
|
||||||
Database Database
|
Database Database
|
||||||
BareRTC BareRTC
|
BareRTC BareRTC
|
||||||
|
Maintenance Maintenance
|
||||||
UseXForwardedFor bool
|
UseXForwardedFor bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,15 +71,7 @@ func LoadSettings() {
|
||||||
|
|
||||||
Current = v
|
Current = v
|
||||||
} else {
|
} else {
|
||||||
var buf bytes.Buffer
|
WriteSettings()
|
||||||
enc := json.NewEncoder(&buf)
|
|
||||||
enc.SetIndent("", " ")
|
|
||||||
err := enc.Encode(DefaultVariable())
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("LoadSettings: couldn't marshal default settings: %s", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
ioutil.WriteFile(SettingsPath, buf.Bytes(), 0600)
|
|
||||||
log.Warn("NOTICE: Created default settings.json file - review it and configure mail servers and database!")
|
log.Warn("NOTICE: Created default settings.json file - review it and configure mail servers and database!")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,6 +80,30 @@ func LoadSettings() {
|
||||||
log.Error("No database configured in settings.json. Choose SQLite or Postgres and update the DB connector string!")
|
log.Error("No database configured in settings.json. Choose SQLite or Postgres and update the DB connector string!")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Have we added new config fields? Save the settings.json.
|
||||||
|
if Current.Version != currentVersion {
|
||||||
|
log.Warn("New options are available for your settings.json file. Your settings will be re-saved now.")
|
||||||
|
Current.Version = currentVersion
|
||||||
|
if err := WriteSettings(); err != nil {
|
||||||
|
log.Error("Couldn't write your settings.json file: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteSettings will commit the settings.json to disk.
|
||||||
|
func WriteSettings() error {
|
||||||
|
log.Error("Note: initial settings.json was written to disk.")
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
enc := json.NewEncoder(&buf)
|
||||||
|
enc.SetIndent("", " ")
|
||||||
|
err := enc.Encode(Current)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("WriteSettings: couldn't marshal settings: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return ioutil.WriteFile(SettingsPath, buf.Bytes(), 0600)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mail settings.
|
// Mail settings.
|
||||||
|
@ -114,3 +136,11 @@ type BareRTC struct {
|
||||||
JWTSecret string
|
JWTSecret string
|
||||||
URL string
|
URL string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Maintenance mode settings.
|
||||||
|
type Maintenance struct {
|
||||||
|
PauseSignup bool
|
||||||
|
PauseLogin bool
|
||||||
|
PauseChat bool
|
||||||
|
PauseInteraction bool
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
|
|
||||||
"code.nonshy.com/nonshy/website/pkg/config"
|
"code.nonshy.com/nonshy/website/pkg/config"
|
||||||
"code.nonshy.com/nonshy/website/pkg/log"
|
"code.nonshy.com/nonshy/website/pkg/log"
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/middleware"
|
||||||
"code.nonshy.com/nonshy/website/pkg/models"
|
"code.nonshy.com/nonshy/website/pkg/models"
|
||||||
"code.nonshy.com/nonshy/website/pkg/ratelimit"
|
"code.nonshy.com/nonshy/website/pkg/ratelimit"
|
||||||
"code.nonshy.com/nonshy/website/pkg/session"
|
"code.nonshy.com/nonshy/website/pkg/session"
|
||||||
|
@ -70,6 +71,11 @@ func Login() http.HandlerFunc {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Maintenance mode check.
|
||||||
|
if middleware.LoginMaintenance(user, w, r) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// OK. Log in the user's session.
|
// OK. Log in the user's session.
|
||||||
session.LoginUser(w, r, user)
|
session.LoginUser(w, r, user)
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/middleware"
|
||||||
"code.nonshy.com/nonshy/website/pkg/models"
|
"code.nonshy.com/nonshy/website/pkg/models"
|
||||||
"code.nonshy.com/nonshy/website/pkg/session"
|
"code.nonshy.com/nonshy/website/pkg/session"
|
||||||
"code.nonshy.com/nonshy/website/pkg/templates"
|
"code.nonshy.com/nonshy/website/pkg/templates"
|
||||||
|
@ -51,6 +52,11 @@ func Profile() http.HandlerFunc {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Is the site under a Maintenance Mode restriction?
|
||||||
|
if middleware.MaintenanceMode(currentUser, w, r) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Forcing an external view? (preview of logged-out profile view for visibility=external accounts)
|
// Forcing an external view? (preview of logged-out profile view for visibility=external accounts)
|
||||||
// You must be logged-in actually to see this.
|
// You must be logged-in actually to see this.
|
||||||
if r.FormValue("view") == "external" {
|
if r.FormValue("view") == "external" {
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"code.nonshy.com/nonshy/website/pkg/config"
|
"code.nonshy.com/nonshy/website/pkg/config"
|
||||||
"code.nonshy.com/nonshy/website/pkg/log"
|
"code.nonshy.com/nonshy/website/pkg/log"
|
||||||
"code.nonshy.com/nonshy/website/pkg/mail"
|
"code.nonshy.com/nonshy/website/pkg/mail"
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/middleware"
|
||||||
"code.nonshy.com/nonshy/website/pkg/models"
|
"code.nonshy.com/nonshy/website/pkg/models"
|
||||||
"code.nonshy.com/nonshy/website/pkg/redis"
|
"code.nonshy.com/nonshy/website/pkg/redis"
|
||||||
"code.nonshy.com/nonshy/website/pkg/session"
|
"code.nonshy.com/nonshy/website/pkg/session"
|
||||||
|
@ -35,6 +36,11 @@ func Signup() http.HandlerFunc {
|
||||||
tmpl := templates.Must("account/signup.html")
|
tmpl := templates.Must("account/signup.html")
|
||||||
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Maintenance mode?
|
||||||
|
if middleware.SignupMaintenance(w, r) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Template vars.
|
// Template vars.
|
||||||
var vars = map[string]interface{}{
|
var vars = map[string]interface{}{
|
||||||
"SignupToken": "", // non-empty if user has clicked verification link
|
"SignupToken": "", // non-empty if user has clicked verification link
|
||||||
|
|
73
pkg/controller/admin/maintenance.go
Normal file
73
pkg/controller/admin/maintenance.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/config"
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/session"
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/templates"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Maintenance controller (/admin/maintenance)
|
||||||
|
func Maintenance() http.HandlerFunc {
|
||||||
|
tmpl := templates.Must("admin/maintenance.html")
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Query parameters.
|
||||||
|
var (
|
||||||
|
intent = r.FormValue("intent")
|
||||||
|
)
|
||||||
|
|
||||||
|
currentUser, err := session.CurrentUser(r)
|
||||||
|
if err != nil {
|
||||||
|
session.FlashError(w, r, "Couldn't get your current user: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = currentUser
|
||||||
|
|
||||||
|
// POST event handlers.
|
||||||
|
if r.Method == http.MethodPost {
|
||||||
|
// Collect the form parameters.
|
||||||
|
var (
|
||||||
|
pauseSignup = r.PostFormValue("signup") == "true"
|
||||||
|
pauseLogin = r.PostFormValue("login") == "true"
|
||||||
|
pauseChat = r.PostFormValue("chat") == "true"
|
||||||
|
pauseInteraction = r.PostFormValue("interaction") == "true"
|
||||||
|
)
|
||||||
|
|
||||||
|
switch intent {
|
||||||
|
case "everything", "nothing":
|
||||||
|
pauseSignup = intent == "everything"
|
||||||
|
pauseLogin = pauseSignup
|
||||||
|
pauseChat = pauseSignup
|
||||||
|
pauseInteraction = pauseSignup
|
||||||
|
intent = "save"
|
||||||
|
fallthrough
|
||||||
|
case "save":
|
||||||
|
// Update and save the site settings.
|
||||||
|
config.Current.Maintenance.PauseSignup = pauseSignup
|
||||||
|
config.Current.Maintenance.PauseLogin = pauseLogin
|
||||||
|
config.Current.Maintenance.PauseChat = pauseChat
|
||||||
|
config.Current.Maintenance.PauseInteraction = pauseInteraction
|
||||||
|
if err := config.WriteSettings(); err != nil {
|
||||||
|
session.FlashError(w, r, "Couldn't write settings.json: %s", err)
|
||||||
|
} else {
|
||||||
|
session.Flash(w, r, "Maintenance settings updated!")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
session.FlashError(w, r, "Unsupported intent: %s", intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
templates.Redirect(w, r.URL.Path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var vars = map[string]interface{}{
|
||||||
|
"Intent": intent,
|
||||||
|
"Maint": config.Current.Maintenance,
|
||||||
|
}
|
||||||
|
if err := tmpl.Execute(w, r, vars); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -108,8 +108,8 @@ func BlockUser() http.HandlerFunc {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Can't block admins.
|
// Can't block admins who have the unblockable scope.
|
||||||
if user.IsAdmin {
|
if user.IsAdmin && user.HasAdminScope(config.ScopeUnblockable) {
|
||||||
// For curiosity's sake, log a report.
|
// For curiosity's sake, log a report.
|
||||||
fb := &models.Feedback{
|
fb := &models.Feedback{
|
||||||
Intent: "report",
|
Intent: "report",
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"code.nonshy.com/nonshy/website/pkg/config"
|
"code.nonshy.com/nonshy/website/pkg/config"
|
||||||
"code.nonshy.com/nonshy/website/pkg/geoip"
|
"code.nonshy.com/nonshy/website/pkg/geoip"
|
||||||
"code.nonshy.com/nonshy/website/pkg/log"
|
"code.nonshy.com/nonshy/website/pkg/log"
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/middleware"
|
||||||
"code.nonshy.com/nonshy/website/pkg/models"
|
"code.nonshy.com/nonshy/website/pkg/models"
|
||||||
"code.nonshy.com/nonshy/website/pkg/photo"
|
"code.nonshy.com/nonshy/website/pkg/photo"
|
||||||
"code.nonshy.com/nonshy/website/pkg/session"
|
"code.nonshy.com/nonshy/website/pkg/session"
|
||||||
|
@ -67,6 +68,11 @@ func Landing() http.HandlerFunc {
|
||||||
isShy = currentUser.IsShy()
|
isShy = currentUser.IsShy()
|
||||||
)
|
)
|
||||||
if intent == "join" {
|
if intent == "join" {
|
||||||
|
// Maintenance mode?
|
||||||
|
if middleware.ChatMaintenance(currentUser, w, r) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// If we are shy, block chat for now.
|
// If we are shy, block chat for now.
|
||||||
if isShy {
|
if isShy {
|
||||||
session.FlashError(w, r,
|
session.FlashError(w, r,
|
||||||
|
|
|
@ -40,6 +40,11 @@ func LoginRequired(handler http.Handler) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Is the site under a Maintenance Mode restriction?
|
||||||
|
if MaintenanceMode(user, w, r) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Ping LastLoginAt for long lived sessions, but not if impersonated.
|
// Ping LastLoginAt for long lived sessions, but not if impersonated.
|
||||||
if time.Since(user.LastLoginAt) > config.LastLoginAtCooldown && !session.Impersonated(r) {
|
if time.Since(user.LastLoginAt) > config.LastLoginAtCooldown && !session.Impersonated(r) {
|
||||||
user.LastLoginAt = time.Now()
|
user.LastLoginAt = time.Now()
|
||||||
|
@ -123,6 +128,11 @@ func CertRequired(handler http.Handler) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Is the site under a Maintenance Mode restriction?
|
||||||
|
if MaintenanceMode(currentUser, w, r) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// User must be certified.
|
// User must be certified.
|
||||||
if !currentUser.Certified || currentUser.ProfilePhoto.ID == 0 {
|
if !currentUser.Certified || currentUser.ProfilePhoto.ID == 0 {
|
||||||
log.Error("CertRequired: user is not certified")
|
log.Error("CertRequired: user is not certified")
|
||||||
|
|
71
pkg/middleware/maintenance.go
Normal file
71
pkg/middleware/maintenance.go
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/config"
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/models"
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/templates"
|
||||||
|
)
|
||||||
|
|
||||||
|
var tmplMaint = templates.Must("errors/maintenance.html")
|
||||||
|
|
||||||
|
// MaintenanceMode check at the middleware level, e.g. to block
|
||||||
|
// LoginRequired and CertificationRequired if site-wide interaction
|
||||||
|
// is currently on hold. Returns true if handled.
|
||||||
|
func MaintenanceMode(currentUser *models.User, w http.ResponseWriter, r *http.Request) bool {
|
||||||
|
// Is the site under a Maintenance Mode restriction?
|
||||||
|
if config.Current.Maintenance.PauseInteraction && !currentUser.IsAdmin {
|
||||||
|
var vars = map[string]interface{}{
|
||||||
|
"Reason": "interaction",
|
||||||
|
}
|
||||||
|
if err := tmplMaint.Execute(w, r, vars); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignupMaintenance may handle maintenance mode requests for signup gating.
|
||||||
|
func SignupMaintenance(w http.ResponseWriter, r *http.Request) bool {
|
||||||
|
if config.Current.Maintenance.PauseSignup {
|
||||||
|
var vars = map[string]interface{}{
|
||||||
|
"Reason": "signup",
|
||||||
|
}
|
||||||
|
if err := tmplMaint.Execute(w, r, vars); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoginMaintenance may handle maintenance mode requests for login gating.
|
||||||
|
func LoginMaintenance(currentUser *models.User, w http.ResponseWriter, r *http.Request) bool {
|
||||||
|
if config.Current.Maintenance.PauseLogin && !currentUser.IsAdmin {
|
||||||
|
var vars = map[string]interface{}{
|
||||||
|
"Reason": "login",
|
||||||
|
}
|
||||||
|
if err := tmplMaint.Execute(w, r, vars); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChatMaintenance may handle maintenance mode requests for chat room gating.
|
||||||
|
func ChatMaintenance(currentUser *models.User, w http.ResponseWriter, r *http.Request) bool {
|
||||||
|
if config.Current.Maintenance.PauseChat && !currentUser.IsAdmin {
|
||||||
|
var vars = map[string]interface{}{
|
||||||
|
"Reason": "chat",
|
||||||
|
}
|
||||||
|
if err := tmplMaint.Execute(w, r, vars); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -87,6 +87,7 @@ func New() http.Handler {
|
||||||
mux.Handle("/admin/photo/certification", middleware.AdminRequired("", photo.AdminCertification()))
|
mux.Handle("/admin/photo/certification", middleware.AdminRequired("", photo.AdminCertification()))
|
||||||
mux.Handle("/admin/feedback", middleware.AdminRequired("", admin.Feedback()))
|
mux.Handle("/admin/feedback", middleware.AdminRequired("", admin.Feedback()))
|
||||||
mux.Handle("/admin/user-action", middleware.AdminRequired("", admin.UserActions()))
|
mux.Handle("/admin/user-action", middleware.AdminRequired("", admin.UserActions()))
|
||||||
|
mux.Handle("/admin/maintenance", middleware.AdminRequired(config.ScopeMaintenance, admin.Maintenance()))
|
||||||
mux.Handle("/forum/admin", middleware.AdminRequired(config.ScopeForumAdmin, forum.Manage()))
|
mux.Handle("/forum/admin", middleware.AdminRequired(config.ScopeForumAdmin, forum.Manage()))
|
||||||
mux.Handle("/forum/admin/edit", middleware.AdminRequired(config.ScopeForumAdmin, forum.AddEdit()))
|
mux.Handle("/forum/admin/edit", middleware.AdminRequired(config.ScopeForumAdmin, forum.AddEdit()))
|
||||||
mux.Handle("/inner-circle/remove", middleware.AdminRequired(config.ScopeCircleModerator, account.RemoveCircle()))
|
mux.Handle("/inner-circle/remove", middleware.AdminRequired(config.ScopeCircleModerator, account.RemoveCircle()))
|
||||||
|
|
|
@ -148,6 +148,12 @@
|
||||||
Admin Permissions Management
|
Admin Permissions Management
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/admin/maintenance">
|
||||||
|
<i class="fa fa-wrench mr-2"></i>
|
||||||
|
Maintenance Mode
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
108
web/templates/admin/maintenance.html
Normal file
108
web/templates/admin/maintenance.html
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
{{define "title"}}Admin - Maintenance Mode{{end}}
|
||||||
|
{{define "content"}}
|
||||||
|
{{$Root := .}}
|
||||||
|
<div class="container">
|
||||||
|
<section class="hero is-danger is-bold">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="title">
|
||||||
|
Maintenance Mode
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<form method="POST" action="{{.Request.URL.Path}}">
|
||||||
|
{{InputCSRF}}
|
||||||
|
<div class="p-4">
|
||||||
|
|
||||||
|
<p class="block">
|
||||||
|
This page allows you to set various Maintenance Mode flags that can pause or
|
||||||
|
disable website features if needed.
|
||||||
|
In an emergency, click on the Lock Down Everything button that will enable ALL maintenance
|
||||||
|
mode flags and basically disable the whole website for everybody except admin user accounts.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="block">
|
||||||
|
<button type="submit" class="button is-danger mr-2"
|
||||||
|
name="intent" value="everything"
|
||||||
|
onclick="return confirm('Do you want to lock down EVERYTHING?')">
|
||||||
|
<i class="fa fa-exclamation-triangle mr-2"></i> Lock Down Everything
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button type="submit" class="button is-success"
|
||||||
|
name="intent" value="nothing"
|
||||||
|
onclick="return confirm('Do you want to RESTORE ALL site functionality?')">
|
||||||
|
<i class="fa fa-exclamation-triangle mr-2"></i> Restore Everything
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<label class="label">Maintenance Mode Settings</label>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox"
|
||||||
|
name="signup"
|
||||||
|
value="true"
|
||||||
|
{{if .Maint.PauseSignup}}checked{{end}}>
|
||||||
|
Pause new account signups
|
||||||
|
</label>
|
||||||
|
<p class="help">
|
||||||
|
New account signups are paused and a maintenance page is shown in its place.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox"
|
||||||
|
name="login"
|
||||||
|
value="true"
|
||||||
|
{{if .Maint.PauseLogin}}checked{{end}}>
|
||||||
|
Pause new logins
|
||||||
|
</label>
|
||||||
|
<p class="help">
|
||||||
|
The login page is disabled (except for admin user login). Already logged-in
|
||||||
|
users can remain logged in.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox"
|
||||||
|
name="chat"
|
||||||
|
value="true"
|
||||||
|
{{if .Maint.PauseChat}}checked{{end}}>
|
||||||
|
Pause chat room entry
|
||||||
|
</label>
|
||||||
|
<p class="help">
|
||||||
|
No new entries into the chat room are allowed.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox"
|
||||||
|
name="interaction"
|
||||||
|
value="true"
|
||||||
|
{{if .Maint.PauseInteraction}}checked{{end}}>
|
||||||
|
Pause <strong>all</strong> interactions
|
||||||
|
</label>
|
||||||
|
<p class="help">
|
||||||
|
Every site feature becomes basically 'admin required' and users are given an error
|
||||||
|
page where their only option is to log out or come back later.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="block">
|
||||||
|
<button type="submit" class="button is-primary"
|
||||||
|
name="intent" value="save">
|
||||||
|
Save Settings
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{{end}}
|
61
web/templates/errors/maintenance.html
Normal file
61
web/templates/errors/maintenance.html
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
{{define "title"}}Not Available{{end}}
|
||||||
|
{{define "content"}}
|
||||||
|
<div class="container">
|
||||||
|
<section class="hero block is-warning is-bold">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="title">Not Available</h1>
|
||||||
|
<h2 class="subtitle">The website is currently unavailable</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="block content p-4 mb-0">
|
||||||
|
<h1>{{PrettyTitle}} is currently not available</h1>
|
||||||
|
<p>
|
||||||
|
We regret to inform you that {{PrettyTitle}} is currently not available.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The website is currently in "maintenance mode" and the feature you requested
|
||||||
|
is currently on pause. Please check back again later.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- More information? -->
|
||||||
|
{{if .Reason}}
|
||||||
|
<h2>More Information</h2>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if eq .Reason "interaction"}}
|
||||||
|
<p>
|
||||||
|
All user interaction on the website is currently on pause. You are currently
|
||||||
|
logged in to an account (username: {{.CurrentUser.Username}}) and you may
|
||||||
|
remain logged-in if you want, but all actions that require a logged-in account
|
||||||
|
are currently disabled.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
If you'd like, you may <a href="/logout">log out</a> or just come back later
|
||||||
|
and see if the maintenance mode of the website has been lifted.
|
||||||
|
</p>
|
||||||
|
{{else if eq .Reason "signup"}}
|
||||||
|
<p>
|
||||||
|
All new account signups are currently on pause. We are not accepting any new
|
||||||
|
members at this time. Please check back again later.
|
||||||
|
</p>
|
||||||
|
{{else if eq .Reason "login"}}
|
||||||
|
<p>
|
||||||
|
All new account logins are currently on pause. Please try again later.
|
||||||
|
</p>
|
||||||
|
{{else if eq .Reason "chat"}}
|
||||||
|
<p>
|
||||||
|
The chat room is currently offline for maintenance. Please try again some other time.
|
||||||
|
</p>
|
||||||
|
{{else}}
|
||||||
|
<p>
|
||||||
|
No further information is available at this time.
|
||||||
|
</p>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
Loading…
Reference in New Issue
Block a user