Let admins change and revoke user avatars.
Linkify avatars in the user manager too. Add the panel_user_avatar phrase.
This commit is contained in:
parent
199a841bc3
commit
ee3c29b136
|
@ -7,6 +7,9 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"os"
|
||||
"io"
|
||||
"regexp"
|
||||
|
||||
"github.com/Azareal/Gosora/common/phrases"
|
||||
)
|
||||
|
@ -322,6 +325,97 @@ func preRoute(w http.ResponseWriter, r *http.Request) (User, bool) {
|
|||
return *usercpy, true
|
||||
}
|
||||
|
||||
func UploadAvatar(w http.ResponseWriter, r *http.Request, user User, tuid int) (ext string, ferr RouteError) {
|
||||
// We don't want multiple files
|
||||
// TODO: Are we doing this correctly?
|
||||
filenameMap := make(map[string]bool)
|
||||
for _, fheaders := range r.MultipartForm.File {
|
||||
for _, hdr := range fheaders {
|
||||
if hdr.Filename == "" {
|
||||
continue
|
||||
}
|
||||
filenameMap[hdr.Filename] = true
|
||||
}
|
||||
}
|
||||
if len(filenameMap) > 1 {
|
||||
return "", LocalError("You may only upload one avatar", w, r, user)
|
||||
}
|
||||
|
||||
for _, fheaders := range r.MultipartForm.File {
|
||||
for _, hdr := range fheaders {
|
||||
if hdr.Filename == "" {
|
||||
continue
|
||||
}
|
||||
infile, err := hdr.Open()
|
||||
if err != nil {
|
||||
return "", LocalError("Upload failed", w, r, user)
|
||||
}
|
||||
defer infile.Close()
|
||||
|
||||
if ext == "" {
|
||||
extarr := strings.Split(hdr.Filename, ".")
|
||||
if len(extarr) < 2 {
|
||||
return "", LocalError("Bad file", w, r, user)
|
||||
}
|
||||
ext = extarr[len(extarr)-1]
|
||||
|
||||
// TODO: Can we do this without a regex?
|
||||
reg, err := regexp.Compile("[^A-Za-z0-9]+")
|
||||
if err != nil {
|
||||
return "", LocalError("Bad file extension", w, r, user)
|
||||
}
|
||||
ext = reg.ReplaceAllString(ext, "")
|
||||
ext = strings.ToLower(ext)
|
||||
|
||||
if !ImageFileExts.Contains(ext) {
|
||||
return "", LocalError("You can only use an image for your avatar", w, r, user)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Centralise this string, so we don't have to change it in two different places when it changes
|
||||
outfile, err := os.Create("./uploads/avatar_" + strconv.Itoa(tuid) + "." + ext)
|
||||
if err != nil {
|
||||
return "", LocalError("Upload failed [File Creation Failed]", w, r, user)
|
||||
}
|
||||
defer outfile.Close()
|
||||
|
||||
_, err = io.Copy(outfile, infile)
|
||||
if err != nil {
|
||||
return "", LocalError("Upload failed [Copy Failed]", w, r, user)
|
||||
}
|
||||
}
|
||||
}
|
||||
if ext == "" {
|
||||
return "", LocalError("No file", w, r, user)
|
||||
}
|
||||
return ext, nil
|
||||
}
|
||||
|
||||
func ChangeAvatar(path string, w http.ResponseWriter, r *http.Request, user User) RouteError {
|
||||
err := user.ChangeAvatar(path)
|
||||
if err != nil {
|
||||
return InternalError(err, w, r)
|
||||
}
|
||||
|
||||
// Clean up the old avatar data, so we don't end up with too many dead files in /uploads/
|
||||
if len(user.RawAvatar) > 2 {
|
||||
if user.RawAvatar[0] == '.' && user.RawAvatar[1] == '.' {
|
||||
err := os.Remove("./uploads/avatar_" + strconv.Itoa(user.ID) + "_tmp" + user.RawAvatar[1:])
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
LogWarning(err)
|
||||
return LocalError("Something went wrong", w, r, user)
|
||||
}
|
||||
err = os.Remove("./uploads/avatar_" + strconv.Itoa(user.ID) + "_w48" + user.RawAvatar[1:])
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
LogWarning(err)
|
||||
return LocalError("Something went wrong", w, r, user)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SuperAdminOnly makes sure that only super admin can access certain critical panel routes
|
||||
func SuperAdminOnly(w http.ResponseWriter, r *http.Request, user User) RouteError {
|
||||
if !user.IsSuperAdmin {
|
||||
|
|
560
gen_router.go
560
gen_router.go
File diff suppressed because it is too large
Load Diff
|
@ -796,6 +796,7 @@
|
|||
"panel_users_activate":"Activate",
|
||||
|
||||
"panel_user_head":"User Editor",
|
||||
"panel_user_avatar":"Avatar",
|
||||
"panel_user_name":"Name",
|
||||
"panel_user_name_placeholder":"Jane Doe",
|
||||
"panel_user_password":"Password",
|
||||
|
|
|
@ -199,6 +199,8 @@ func panelRoutes() *RouteGroup {
|
|||
View("panel.Users", "/panel/users/"),
|
||||
View("panel.UsersEdit", "/panel/users/edit/", "extraData"),
|
||||
Action("panel.UsersEditSubmit", "/panel/users/edit/submit/", "extraData"),
|
||||
UploadAction("panel.UsersAvatarSubmit", "/panel/users/avatar/submit/", "extraData").MaxSizeVar("int(c.Config.MaxRequestSize)"),
|
||||
Action("panel.UsersAvatarRemoveSubmit", "/panel/users/avatar/remove/submit/", "extraData"),
|
||||
|
||||
View("panel.AnalyticsViews", "/panel/analytics/views/").Before("ParseForm"),
|
||||
View("panel.AnalyticsRoutes", "/panel/analytics/routes/").Before("ParseForm"),
|
||||
|
|
|
@ -6,12 +6,9 @@ import (
|
|||
"database/sql"
|
||||
"encoding/hex"
|
||||
"html"
|
||||
"io"
|
||||
"log"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
|
@ -431,93 +428,18 @@ func AccountEditAvatarSubmit(w http.ResponseWriter, r *http.Request, user c.User
|
|||
return c.NoPermissions(w, r, user)
|
||||
}
|
||||
|
||||
// We don't want multiple files
|
||||
// TODO: Are we doing this correctly?
|
||||
filenameMap := make(map[string]bool)
|
||||
for _, fheaders := range r.MultipartForm.File {
|
||||
for _, hdr := range fheaders {
|
||||
if hdr.Filename == "" {
|
||||
continue
|
||||
}
|
||||
filenameMap[hdr.Filename] = true
|
||||
}
|
||||
}
|
||||
if len(filenameMap) > 1 {
|
||||
return c.LocalError("You may only upload one avatar", w, r, user)
|
||||
ext, ferr := c.UploadAvatar(w,r,user,user.ID)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
|
||||
var ext string
|
||||
for _, fheaders := range r.MultipartForm.File {
|
||||
for _, hdr := range fheaders {
|
||||
if hdr.Filename == "" {
|
||||
continue
|
||||
}
|
||||
infile, err := hdr.Open()
|
||||
if err != nil {
|
||||
return c.LocalError("Upload failed", w, r, user)
|
||||
}
|
||||
defer infile.Close()
|
||||
|
||||
if ext == "" {
|
||||
extarr := strings.Split(hdr.Filename, ".")
|
||||
if len(extarr) < 2 {
|
||||
return c.LocalError("Bad file", w, r, user)
|
||||
}
|
||||
ext = extarr[len(extarr)-1]
|
||||
|
||||
// TODO: Can we do this without a regex?
|
||||
reg, err := regexp.Compile("[^A-Za-z0-9]+")
|
||||
if err != nil {
|
||||
return c.LocalError("Bad file extension", w, r, user)
|
||||
}
|
||||
ext = reg.ReplaceAllString(ext, "")
|
||||
ext = strings.ToLower(ext)
|
||||
|
||||
if !c.ImageFileExts.Contains(ext) {
|
||||
return c.LocalError("You can only use an image for your avatar", w, r, user)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Centralise this string, so we don't have to change it in two different places when it changes
|
||||
outfile, err := os.Create("./uploads/avatar_" + strconv.Itoa(user.ID) + "." + ext)
|
||||
if err != nil {
|
||||
return c.LocalError("Upload failed [File Creation Failed]", w, r, user)
|
||||
}
|
||||
defer outfile.Close()
|
||||
|
||||
_, err = io.Copy(outfile, infile)
|
||||
if err != nil {
|
||||
return c.LocalError("Upload failed [Copy Failed]", w, r, user)
|
||||
}
|
||||
}
|
||||
}
|
||||
if ext == "" {
|
||||
return c.LocalError("No file", w, r, user)
|
||||
}
|
||||
|
||||
err := user.ChangeAvatar("." + ext)
|
||||
if err != nil {
|
||||
return c.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
// Clean up the old avatar data, so we don't end up with too many dead files in /uploads/
|
||||
if len(user.RawAvatar) > 2 {
|
||||
if user.RawAvatar[0] == '.' && user.RawAvatar[1] == '.' {
|
||||
err := os.Remove("./uploads/avatar_" + strconv.Itoa(user.ID) + "_tmp" + user.RawAvatar[1:])
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
c.LogWarning(err)
|
||||
return c.LocalError("Something went wrong", w, r, user)
|
||||
}
|
||||
err = os.Remove("./uploads/avatar_" + strconv.Itoa(user.ID) + "_w48" + user.RawAvatar[1:])
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
c.LogWarning(err)
|
||||
return c.LocalError("Something went wrong", w, r, user)
|
||||
}
|
||||
}
|
||||
ferr = c.ChangeAvatar("." + ext, w, r, user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
|
||||
// TODO: Only schedule a resize if the avatar isn't tiny
|
||||
err = user.ScheduleAvatarResize()
|
||||
err := user.ScheduleAvatarResize()
|
||||
if err != nil {
|
||||
return c.InternalError(err, w, r)
|
||||
}
|
||||
|
@ -531,25 +453,9 @@ func AccountEditRevokeAvatarSubmit(w http.ResponseWriter, r *http.Request, user
|
|||
return ferr
|
||||
}
|
||||
|
||||
err := user.ChangeAvatar("")
|
||||
if err != nil {
|
||||
return c.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
// Clean up the old avatar data, so we don't end up with too many dead files in /uploads/
|
||||
if len(user.RawAvatar) > 2 {
|
||||
if user.RawAvatar[0] == '.' && user.RawAvatar[1] == '.' {
|
||||
err := os.Remove("./uploads/avatar_" + strconv.Itoa(user.ID) + "_tmp" + user.RawAvatar[1:])
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
c.LogWarning(err)
|
||||
return c.LocalError("Something went wrong", w, r, user)
|
||||
}
|
||||
err = os.Remove("./uploads/avatar_" + strconv.Itoa(user.ID) + "_w48" + user.RawAvatar[1:])
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
c.LogWarning(err)
|
||||
return c.LocalError("Something went wrong", w, r, user)
|
||||
}
|
||||
}
|
||||
ferr = c.ChangeAvatar("", w, r, user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/user/edit/?avatar_updated=1", http.StatusSeeOther)
|
||||
|
|
|
@ -98,7 +98,6 @@ func UsersEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, suid s
|
|||
} else if err != nil {
|
||||
return c.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
if targetUser.IsAdmin && !user.IsAdmin {
|
||||
return c.LocalError("Only administrators can edit the account of other administrators.", w, r, user)
|
||||
}
|
||||
|
@ -162,3 +161,80 @@ func UsersEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, suid s
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func UsersAvatarSubmit(w http.ResponseWriter, r *http.Request, user c.User, suid string) c.RouteError {
|
||||
_, ferr := c.SimplePanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
if !user.Perms.EditUser {
|
||||
return c.NoPermissions(w, r, user)
|
||||
}
|
||||
|
||||
uid, err := strconv.Atoi(suid)
|
||||
if err != nil {
|
||||
return c.LocalError("The provided UserID is not a valid number.", w, r, user)
|
||||
}
|
||||
|
||||
targetUser, err := c.Users.Get(uid)
|
||||
if err == sql.ErrNoRows {
|
||||
return c.LocalError("The user you're trying to edit doesn't exist.", w, r, user)
|
||||
} else if err != nil {
|
||||
return c.InternalError(err, w, r)
|
||||
}
|
||||
if targetUser.IsAdmin && !user.IsAdmin {
|
||||
return c.LocalError("Only administrators can edit the account of other administrators.", w, r, user)
|
||||
}
|
||||
|
||||
ext, ferr := c.UploadAvatar(w,r,user,targetUser.ID)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
|
||||
ferr = c.ChangeAvatar("." + ext, w, r, *targetUser)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
|
||||
// TODO: Only schedule a resize if the avatar isn't tiny
|
||||
err = targetUser.ScheduleAvatarResize()
|
||||
if err != nil {
|
||||
return c.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/panel/users/edit/"+strconv.Itoa(targetUser.ID)+"?updated=1", http.StatusSeeOther)
|
||||
return nil
|
||||
}
|
||||
|
||||
func UsersAvatarRemoveSubmit(w http.ResponseWriter, r *http.Request, user c.User, suid string) c.RouteError {
|
||||
_, ferr := c.SimplePanelUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
if !user.Perms.EditUser {
|
||||
return c.NoPermissions(w, r, user)
|
||||
}
|
||||
|
||||
uid, err := strconv.Atoi(suid)
|
||||
if err != nil {
|
||||
return c.LocalError("The provided UserID is not a valid number.", w, r, user)
|
||||
}
|
||||
|
||||
targetUser, err := c.Users.Get(uid)
|
||||
if err == sql.ErrNoRows {
|
||||
return c.LocalError("The user you're trying to edit doesn't exist.", w, r, user)
|
||||
} else if err != nil {
|
||||
return c.InternalError(err, w, r)
|
||||
}
|
||||
if targetUser.IsAdmin && !user.IsAdmin {
|
||||
return c.LocalError("Only administrators can edit the account of other administrators.", w, r, user)
|
||||
}
|
||||
|
||||
ferr = c.ChangeAvatar("", w, r, *targetUser)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/panel/users/edit/"+strconv.Itoa(targetUser.ID)+"?updated=1", http.StatusSeeOther)
|
||||
return nil
|
||||
}
|
|
@ -1,31 +1,46 @@
|
|||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><h1>{{lang "panel_user_head"}}</h1></div>
|
||||
</div>
|
||||
<div id="panel_user" class="colstack_item the_form">
|
||||
<form action="/panel/users/edit/submit/{{.Something.ID}}?session={{.CurrentUser.Session}}" method="post">
|
||||
<div class="formrow">
|
||||
<div class="formitem formlabel"><a>{{lang "panel_user_name"}}</a></div>
|
||||
<div class="formitem"><input name="user-name" type="text" value="{{.Something.Name}}" placeholder="{{lang "panel_user_name_placeholder"}}" /></div>
|
||||
</div>
|
||||
{{if .CurrentUser.Perms.EditUserPassword}}<div class="formrow">
|
||||
<div class="formitem formlabel"><a>{{lang "panel_user_password"}}</a></div>
|
||||
<div class="formitem"><input name="user-password" type="password" placeholder="*****" autocomplete="off" /></div>
|
||||
</div>{{end}}
|
||||
{{if .CurrentUser.Perms.EditUserEmail}}<div class="formrow">
|
||||
<div class="formitem formlabel"><a>{{lang "panel_user_email"}}</a></div>
|
||||
<div class="formitem"><input name="user-email" type="email" value="{{.Something.Email}}" placeholder="example@localhost" /></div>
|
||||
</div>{{end}}
|
||||
{{if .CurrentUser.Perms.EditUserGroup}}
|
||||
<div class="formrow">
|
||||
<div class="formitem formlabel"><a>{{lang "panel_user_group"}}</a></div>
|
||||
<div class="formitem">
|
||||
<select name="user-group">
|
||||
{{range .ItemList}}<option {{if eq .ID $.Something.Group}}selected {{end}}value="{{.ID}}">{{.Name}}</option>{{end}}
|
||||
</select>
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><h1>{{lang "panel_user_head"}}</h1></div>
|
||||
</div>
|
||||
<div id="panel_user" class="colstack_item the_form">
|
||||
<form id="user_form" action="/panel/users/edit/submit/{{.Something.ID}}?session={{.CurrentUser.Session}}" method="post"></form>
|
||||
<form id="avatar_form" enctype="multipart/form-data" action="/panel/users/avatar/submit/{{.Something.ID}}?session={{.CurrentUser.Session}}" method="post"></form>
|
||||
<form id="remove_avatar_form" action="/panel/users/avatar/remove/submit/{{.Something.ID}}?session={{.CurrentUser.Session}}" method="post"></form>
|
||||
<div class="formrow">
|
||||
<div class="formitem formlabel"><a>{{lang "panel_user_avatar"}}</a></div>
|
||||
<div class="formitem avataritem">
|
||||
{{if .Something.RawAvatar}}<img src="{{.Something.Avatar}}" height=56 width=56 />{{end}}
|
||||
<div class="avatarbuttons">
|
||||
<input form="avatar_form" id="select_avatar" name="avatar_file" type="file" required class="auto_hide" />
|
||||
<label for="select_avatar" class="formbutton">Select</label>
|
||||
<button form="avatar_form" name="avatar_action" value=0>Upload</button>
|
||||
{{if .Something.RawAvatar}}<button form="remove_avatar_form" name="avatar_action" value=1>Remove</button>{{end}}
|
||||
</div>
|
||||
</div>{{end}}
|
||||
<div class="formrow">
|
||||
<div class="formitem"><button name="panel-button" class="formbutton">{{lang "panel_user_update_button"}}</button></div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="formrow">
|
||||
<div class="formitem formlabel"><a>{{lang "panel_user_name"}}</a></div>
|
||||
<div class="formitem"><input form="user_form" name="user-name" type="text" value="{{.Something.Name}}" placeholder="{{lang "panel_user_name_placeholder"}}" /></div>
|
||||
</div>
|
||||
{{if .CurrentUser.Perms.EditUserPassword}}<div class="formrow">
|
||||
<div class="formitem formlabel"><a>{{lang "panel_user_password"}}</a></div>
|
||||
<div class="formitem"><input form="user_form" name="user-password" type="password" placeholder="*****" autocomplete="off" /></div>
|
||||
</div>{{end}}
|
||||
{{if .CurrentUser.Perms.EditUserEmail}}<div class="formrow">
|
||||
<div class="formitem formlabel"><a>{{lang "panel_user_email"}}</a></div>
|
||||
<div class="formitem"><input form="user_form" name="user-email" type="email" value="{{.Something.Email}}" placeholder="example@localhost" /></div>
|
||||
</div>{{end}}
|
||||
{{if .CurrentUser.Perms.EditUserGroup}}
|
||||
<div class="formrow">
|
||||
<div class="formitem formlabel"><a>{{lang "panel_user_group"}}</a></div>
|
||||
<div class="formitem">
|
||||
<select form="user_form" name="user-group">
|
||||
{{range .ItemList}}<option {{if eq .ID $.Something.Group}}selected {{end}}value="{{.ID}}">{{.Name}}</option>{{end}}
|
||||
</select>
|
||||
</div>
|
||||
</div>{{end}}
|
||||
<div class="formrow">
|
||||
<div class="formitem">
|
||||
<button form="user_form" name="panel-button" class="formbutton">{{lang "panel_user_update_button"}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,18 +1,20 @@
|
|||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><h1>{{lang "panel_users_head"}}</h1></div>
|
||||
</div>
|
||||
<div id="panel_users" class="colstack_item rowlist bgavatars">
|
||||
{{range .ItemList}}
|
||||
<div class="rowitem editable_parent" style="background-image: url('{{.Avatar}}');">
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><h1>{{lang "panel_users_head"}}</h1></div>
|
||||
</div>
|
||||
<div id="panel_users" class="colstack_item rowlist bgavatars">
|
||||
{{range .ItemList}}
|
||||
<div class="rowitem" style="background-image: url('{{.Avatar}}');">
|
||||
<a class="rowAvatar"{{if $.CurrentUser.Perms.EditUser}} href="/panel/users/edit/{{.ID}}?session={{$.CurrentUser.Session}}"{{end}}>
|
||||
<img class="bgsub" src="{{.Avatar}}" alt="Avatar" aria-hidden="true" />
|
||||
<a class="rowTitle editable_block"{{if $.CurrentUser.Perms.EditUser}} href="/panel/users/edit/{{.ID}}?session={{$.CurrentUser.Session}}"{{end}}>{{.Name}}</a>
|
||||
<span class="panel_floater">
|
||||
<a href="{{.Link}}" class="tag-mini profile_url">{{lang "panel_users_profile"}}</a>
|
||||
{{if (.Tag) and (.IsSuperMod)}}<span class="panel_tag">{{.Tag}}</span></span>{{end}}
|
||||
{{if .IsBanned}}<a href="/users/unban/{{.ID}}?session={{$.CurrentUser.Session}}" class="panel_tag panel_right_button ban_button">{{lang "panel_users_unban"}}</a>{{else if not .IsSuperMod}}<a href="/user/{{.ID}}#ban_user" class="panel_tag panel_right_button ban_button">{{lang "panel_users_ban"}}</a>{{end}}
|
||||
{{if not .Active}}<a href="/users/activate/{{.ID}}?session={{$.CurrentUser.Session}}" class="panel_tag panel_right_button">{{lang "panel_users_activate"}}</a>{{end}}
|
||||
</span>
|
||||
</div>
|
||||
{{end}}
|
||||
</a>
|
||||
<a class="rowTitle"{{if $.CurrentUser.Perms.EditUser}} href="/panel/users/edit/{{.ID}}?session={{$.CurrentUser.Session}}"{{end}}>{{.Name}}</a>
|
||||
<span class="panel_floater">
|
||||
<a href="{{.Link}}" class="tag-mini profile_url">{{lang "panel_users_profile"}}</a>
|
||||
{{if (.Tag) and (.IsSuperMod)}}<span class="panel_tag">{{.Tag}}</span></span>{{end}}
|
||||
{{if .IsBanned}}<a href="/users/unban/{{.ID}}?session={{$.CurrentUser.Session}}" class="panel_tag panel_right_button ban_button">{{lang "panel_users_unban"}}</a>{{else if not .IsSuperMod}}<a href="/user/{{.ID}}#ban_user" class="panel_tag panel_right_button ban_button">{{lang "panel_users_ban"}}</a>{{end}}
|
||||
{{if not .Active}}<a href="/users/activate/{{.ID}}?session={{$.CurrentUser.Session}}" class="panel_tag panel_right_button">{{lang "panel_users_activate"}}</a>{{end}}
|
||||
</span>
|
||||
</div>
|
||||
{{template "paginator.html" . }}
|
||||
{{end}}
|
||||
</div>
|
||||
{{template "paginator.html" . }}
|
|
@ -754,8 +754,6 @@ textarea {
|
|||
}
|
||||
.rowlist.bgavatars .rowitem {
|
||||
flex-direction: column;
|
||||
}
|
||||
.rowlist.bgavatars .rowitem {
|
||||
padding-top: 16px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
@ -781,6 +779,9 @@ textarea {
|
|||
font-size: 20px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
.rowlist.bgavatars .rowAvatar {
|
||||
margin-bottom: -4px;
|
||||
}
|
||||
.rowlist .panel_compactrow {
|
||||
padding: 16px;
|
||||
}
|
||||
|
|
|
@ -1074,7 +1074,7 @@ input[type=checkbox]:checked + label .sel {
|
|||
.rowlist.not_grid .rowitem {
|
||||
flex-direction: row;
|
||||
}
|
||||
.rowlist.bgavatars .bgsub, .rowlist.bgavatars .rowTitle {
|
||||
.rowlist.bgavatars .bgsub, .rowlist.bgavatars .rowTitle, .rowlist.bgavatars .rowAvatar {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
@ -1087,6 +1087,9 @@ input[type=checkbox]:checked + label .sel {
|
|||
font-size: 18px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.rowlist.bgavatars .rowAvatar {
|
||||
margin-bottom: -4px;
|
||||
}
|
||||
.rowlist.bgavatars.not_grid .bgsub {
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
|
|
|
@ -95,9 +95,16 @@
|
|||
.alert {
|
||||
margin-top: 18px;
|
||||
}
|
||||
.rowitem {
|
||||
.rowitem, .formitem.avataritem {
|
||||
display: flex;
|
||||
}
|
||||
.formitem.avataritem {
|
||||
flex-direction: column;
|
||||
}
|
||||
.avataritem .avatarbuttons {
|
||||
margin-top: 7px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.colstack_grid {
|
||||
display: grid;
|
||||
|
@ -125,31 +132,12 @@
|
|||
.grid2 {
|
||||
margin-top: 12px;
|
||||
}
|
||||
/*.panel_dashboard .grid_item {
|
||||
text-align: center;
|
||||
}
|
||||
#dash-cpu, #dash-disk, #dash-ram, #dash-memused {
|
||||
background-repeat: no-repeat;
|
||||
padding-left: 61px;
|
||||
background-position: left 12px top 50%;
|
||||
background-size: 40px;
|
||||
}
|
||||
#dash-cpu {
|
||||
background-image: url(./fa-svg/server-bg.svg);
|
||||
background-size: 30px;
|
||||
}
|
||||
#dash-disk {
|
||||
background-image: url(./fa-svg/hdd-bg.svg);
|
||||
}
|
||||
#dash-ram, #dash-memused {
|
||||
background-image: url(./fa-svg/memory.svg);
|
||||
}*/
|
||||
|
||||
.panel_buttons, .panel_floater {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.colstack_right input, .colstack_right select, .colstack_right textarea {
|
||||
.colstack_right input, .colstack_right select, .colstack_right textarea, .formitem img {
|
||||
padding: 4px;
|
||||
padding-bottom: 3px;
|
||||
padding-left: 6px;
|
||||
|
|
Loading…
Reference in New Issue