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"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"os"
|
||||||
|
"io"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
"github.com/Azareal/Gosora/common/phrases"
|
"github.com/Azareal/Gosora/common/phrases"
|
||||||
)
|
)
|
||||||
|
@ -322,6 +325,97 @@ func preRoute(w http.ResponseWriter, r *http.Request) (User, bool) {
|
||||||
return *usercpy, true
|
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
|
// SuperAdminOnly makes sure that only super admin can access certain critical panel routes
|
||||||
func SuperAdminOnly(w http.ResponseWriter, r *http.Request, user User) RouteError {
|
func SuperAdminOnly(w http.ResponseWriter, r *http.Request, user User) RouteError {
|
||||||
if !user.IsSuperAdmin {
|
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_users_activate":"Activate",
|
||||||
|
|
||||||
"panel_user_head":"User Editor",
|
"panel_user_head":"User Editor",
|
||||||
|
"panel_user_avatar":"Avatar",
|
||||||
"panel_user_name":"Name",
|
"panel_user_name":"Name",
|
||||||
"panel_user_name_placeholder":"Jane Doe",
|
"panel_user_name_placeholder":"Jane Doe",
|
||||||
"panel_user_password":"Password",
|
"panel_user_password":"Password",
|
||||||
|
|
|
@ -199,6 +199,8 @@ func panelRoutes() *RouteGroup {
|
||||||
View("panel.Users", "/panel/users/"),
|
View("panel.Users", "/panel/users/"),
|
||||||
View("panel.UsersEdit", "/panel/users/edit/", "extraData"),
|
View("panel.UsersEdit", "/panel/users/edit/", "extraData"),
|
||||||
Action("panel.UsersEditSubmit", "/panel/users/edit/submit/", "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.AnalyticsViews", "/panel/analytics/views/").Before("ParseForm"),
|
||||||
View("panel.AnalyticsRoutes", "/panel/analytics/routes/").Before("ParseForm"),
|
View("panel.AnalyticsRoutes", "/panel/analytics/routes/").Before("ParseForm"),
|
||||||
|
|
|
@ -6,12 +6,9 @@ import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"html"
|
"html"
|
||||||
"io"
|
|
||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -431,93 +428,18 @@ func AccountEditAvatarSubmit(w http.ResponseWriter, r *http.Request, user c.User
|
||||||
return c.NoPermissions(w, r, user)
|
return c.NoPermissions(w, r, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't want multiple files
|
ext, ferr := c.UploadAvatar(w,r,user,user.ID)
|
||||||
// TODO: Are we doing this correctly?
|
if ferr != nil {
|
||||||
filenameMap := make(map[string]bool)
|
return ferr
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var ext string
|
ferr = c.ChangeAvatar("." + ext, w, r, user)
|
||||||
for _, fheaders := range r.MultipartForm.File {
|
if ferr != nil {
|
||||||
for _, hdr := range fheaders {
|
return ferr
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Only schedule a resize if the avatar isn't tiny
|
// TODO: Only schedule a resize if the avatar isn't tiny
|
||||||
err = user.ScheduleAvatarResize()
|
err := user.ScheduleAvatarResize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.InternalError(err, w, r)
|
return c.InternalError(err, w, r)
|
||||||
}
|
}
|
||||||
|
@ -531,25 +453,9 @@ func AccountEditRevokeAvatarSubmit(w http.ResponseWriter, r *http.Request, user
|
||||||
return ferr
|
return ferr
|
||||||
}
|
}
|
||||||
|
|
||||||
err := user.ChangeAvatar("")
|
ferr = c.ChangeAvatar("", w, r, user)
|
||||||
if err != nil {
|
if ferr != nil {
|
||||||
return c.InternalError(err, w, r)
|
return ferr
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
http.Redirect(w, r, "/user/edit/?avatar_updated=1", http.StatusSeeOther)
|
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 {
|
} else if err != nil {
|
||||||
return c.InternalError(err, w, r)
|
return c.InternalError(err, w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
if targetUser.IsAdmin && !user.IsAdmin {
|
if targetUser.IsAdmin && !user.IsAdmin {
|
||||||
return c.LocalError("Only administrators can edit the account of other administrators.", w, r, user)
|
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
|
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="colstack_item colstack_head">
|
||||||
<div class="rowitem"><h1>{{lang "panel_user_head"}}</h1></div>
|
<div class="rowitem"><h1>{{lang "panel_user_head"}}</h1></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="panel_user" class="colstack_item the_form">
|
<div id="panel_user" class="colstack_item the_form">
|
||||||
<form action="/panel/users/edit/submit/{{.Something.ID}}?session={{.CurrentUser.Session}}" method="post">
|
<form id="user_form" action="/panel/users/edit/submit/{{.Something.ID}}?session={{.CurrentUser.Session}}" method="post"></form>
|
||||||
<div class="formrow">
|
<form id="avatar_form" enctype="multipart/form-data" action="/panel/users/avatar/submit/{{.Something.ID}}?session={{.CurrentUser.Session}}" method="post"></form>
|
||||||
<div class="formitem formlabel"><a>{{lang "panel_user_name"}}</a></div>
|
<form id="remove_avatar_form" action="/panel/users/avatar/remove/submit/{{.Something.ID}}?session={{.CurrentUser.Session}}" method="post"></form>
|
||||||
<div class="formitem"><input name="user-name" type="text" value="{{.Something.Name}}" placeholder="{{lang "panel_user_name_placeholder"}}" /></div>
|
<div class="formrow">
|
||||||
</div>
|
<div class="formitem formlabel"><a>{{lang "panel_user_avatar"}}</a></div>
|
||||||
{{if .CurrentUser.Perms.EditUserPassword}}<div class="formrow">
|
<div class="formitem avataritem">
|
||||||
<div class="formitem formlabel"><a>{{lang "panel_user_password"}}</a></div>
|
{{if .Something.RawAvatar}}<img src="{{.Something.Avatar}}" height=56 width=56 />{{end}}
|
||||||
<div class="formitem"><input name="user-password" type="password" placeholder="*****" autocomplete="off" /></div>
|
<div class="avatarbuttons">
|
||||||
</div>{{end}}
|
<input form="avatar_form" id="select_avatar" name="avatar_file" type="file" required class="auto_hide" />
|
||||||
{{if .CurrentUser.Perms.EditUserEmail}}<div class="formrow">
|
<label for="select_avatar" class="formbutton">Select</label>
|
||||||
<div class="formitem formlabel"><a>{{lang "panel_user_email"}}</a></div>
|
<button form="avatar_form" name="avatar_action" value=0>Upload</button>
|
||||||
<div class="formitem"><input name="user-email" type="email" value="{{.Something.Email}}" placeholder="example@localhost" /></div>
|
{{if .Something.RawAvatar}}<button form="remove_avatar_form" name="avatar_action" value=1>Remove</button>{{end}}
|
||||||
</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>
|
</div>
|
||||||
</div>{{end}}
|
|
||||||
<div class="formrow">
|
|
||||||
<div class="formitem"><button name="panel-button" class="formbutton">{{lang "panel_user_update_button"}}</button></div>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
|
||||||
</div>
|
</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="colstack_item colstack_head">
|
||||||
<div class="rowitem"><h1>{{lang "panel_users_head"}}</h1></div>
|
<div class="rowitem"><h1>{{lang "panel_users_head"}}</h1></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="panel_users" class="colstack_item rowlist bgavatars">
|
<div id="panel_users" class="colstack_item rowlist bgavatars">
|
||||||
{{range .ItemList}}
|
{{range .ItemList}}
|
||||||
<div class="rowitem editable_parent" style="background-image: url('{{.Avatar}}');">
|
<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" />
|
<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>
|
</a>
|
||||||
<span class="panel_floater">
|
<a class="rowTitle"{{if $.CurrentUser.Perms.EditUser}} href="/panel/users/edit/{{.ID}}?session={{$.CurrentUser.Session}}"{{end}}>{{.Name}}</a>
|
||||||
<a href="{{.Link}}" class="tag-mini profile_url">{{lang "panel_users_profile"}}</a>
|
<span class="panel_floater">
|
||||||
{{if (.Tag) and (.IsSuperMod)}}<span class="panel_tag">{{.Tag}}</span></span>{{end}}
|
<a href="{{.Link}}" class="tag-mini profile_url">{{lang "panel_users_profile"}}</a>
|
||||||
{{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 (.Tag) and (.IsSuperMod)}}<span class="panel_tag">{{.Tag}}</span></span>{{end}}
|
||||||
{{if not .Active}}<a href="/users/activate/{{.ID}}?session={{$.CurrentUser.Session}}" class="panel_tag panel_right_button">{{lang "panel_users_activate"}}</a>{{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}}
|
||||||
</span>
|
{{if not .Active}}<a href="/users/activate/{{.ID}}?session={{$.CurrentUser.Session}}" class="panel_tag panel_right_button">{{lang "panel_users_activate"}}</a>{{end}}
|
||||||
</div>
|
</span>
|
||||||
{{end}}
|
|
||||||
</div>
|
</div>
|
||||||
{{template "paginator.html" . }}
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{template "paginator.html" . }}
|
|
@ -754,8 +754,6 @@ textarea {
|
||||||
}
|
}
|
||||||
.rowlist.bgavatars .rowitem {
|
.rowlist.bgavatars .rowitem {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
|
||||||
.rowlist.bgavatars .rowitem {
|
|
||||||
padding-top: 16px;
|
padding-top: 16px;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
@ -781,6 +779,9 @@ textarea {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
margin-bottom: 3px;
|
margin-bottom: 3px;
|
||||||
}
|
}
|
||||||
|
.rowlist.bgavatars .rowAvatar {
|
||||||
|
margin-bottom: -4px;
|
||||||
|
}
|
||||||
.rowlist .panel_compactrow {
|
.rowlist .panel_compactrow {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1074,7 +1074,7 @@ input[type=checkbox]:checked + label .sel {
|
||||||
.rowlist.not_grid .rowitem {
|
.rowlist.not_grid .rowitem {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
.rowlist.bgavatars .bgsub, .rowlist.bgavatars .rowTitle {
|
.rowlist.bgavatars .bgsub, .rowlist.bgavatars .rowTitle, .rowlist.bgavatars .rowAvatar {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
@ -1087,6 +1087,9 @@ input[type=checkbox]:checked + label .sel {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
.rowlist.bgavatars .rowAvatar {
|
||||||
|
margin-bottom: -4px;
|
||||||
|
}
|
||||||
.rowlist.bgavatars.not_grid .bgsub {
|
.rowlist.bgavatars.not_grid .bgsub {
|
||||||
height: 28px;
|
height: 28px;
|
||||||
width: 28px;
|
width: 28px;
|
||||||
|
|
|
@ -95,9 +95,16 @@
|
||||||
.alert {
|
.alert {
|
||||||
margin-top: 18px;
|
margin-top: 18px;
|
||||||
}
|
}
|
||||||
.rowitem {
|
.rowitem, .formitem.avataritem {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
.formitem.avataritem {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.avataritem .avatarbuttons {
|
||||||
|
margin-top: 7px;
|
||||||
|
margin-bottom: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
.colstack_grid {
|
.colstack_grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
@ -125,31 +132,12 @@
|
||||||
.grid2 {
|
.grid2 {
|
||||||
margin-top: 12px;
|
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 {
|
.panel_buttons, .panel_floater {
|
||||||
margin-left: auto;
|
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: 4px;
|
||||||
padding-bottom: 3px;
|
padding-bottom: 3px;
|
||||||
padding-left: 6px;
|
padding-left: 6px;
|
||||||
|
|
Loading…
Reference in New Issue