mirror of
https://github.com/Theodor-Springmann-Stiftung/musenalm.git
synced 2025-10-28 16:55:32 +00:00
ajax -- user management and user edit
This commit is contained in:
10
.prettierrc
Normal file
10
.prettierrc
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"bracketSameLine": true,
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"whitespaceSensitivity": "ignore",
|
||||||
|
"bracketLine": true,
|
||||||
|
"useTabs": true,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"printWidth": 700,
|
||||||
|
"plugins": ["prettier-plugin-go-template"]
|
||||||
|
}
|
||||||
@@ -2,13 +2,13 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Theodor-Springmann-Stiftung/musenalm/app"
|
"github.com/Theodor-Springmann-Stiftung/musenalm/app"
|
||||||
"github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels"
|
"github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels"
|
||||||
"github.com/Theodor-Springmann-Stiftung/musenalm/middleware"
|
"github.com/Theodor-Springmann-Stiftung/musenalm/middleware"
|
||||||
"github.com/Theodor-Springmann-Stiftung/musenalm/pagemodels"
|
"github.com/Theodor-Springmann-Stiftung/musenalm/pagemodels"
|
||||||
"github.com/Theodor-Springmann-Stiftung/musenalm/templating"
|
"github.com/Theodor-Springmann-Stiftung/musenalm/templating"
|
||||||
|
"github.com/pocketbase/dbx"
|
||||||
"github.com/pocketbase/pocketbase/core"
|
"github.com/pocketbase/pocketbase/core"
|
||||||
"github.com/pocketbase/pocketbase/tools/router"
|
"github.com/pocketbase/pocketbase/tools/router"
|
||||||
)
|
)
|
||||||
@@ -55,8 +55,8 @@ func (p *UserManagementPage) Setup(router *router.Router[*core.RequestEvent], ap
|
|||||||
func GetSessionsCounts(app core.App) ([]*SessionCount, error) {
|
func GetSessionsCounts(app core.App) ([]*SessionCount, error) {
|
||||||
query := app.RecordQuery(dbmodels.SESSIONS_TABLE).
|
query := app.RecordQuery(dbmodels.SESSIONS_TABLE).
|
||||||
Select("COUNT(*) AS count", dbmodels.SESSIONS_USER_FIELD).
|
Select("COUNT(*) AS count", dbmodels.SESSIONS_USER_FIELD).
|
||||||
GroupBy(dbmodels.SESSIONS_USER_FIELD).
|
AndWhere(dbx.HashExp{dbmodels.SESSIONS_STATUS_FIELD: dbmodels.TOKEN_STATUS_VALUES[0]}).
|
||||||
OrderBy("count DESC")
|
GroupBy(dbmodels.SESSIONS_USER_FIELD)
|
||||||
|
|
||||||
var counts []*SessionCount
|
var counts []*SessionCount
|
||||||
err := query.All(&counts)
|
err := query.All(&counts)
|
||||||
@@ -70,14 +70,14 @@ func GetSessionsCounts(app core.App) ([]*SessionCount, error) {
|
|||||||
func (p *UserManagementPage) GET(engine *templating.Engine, app core.App) HandleFunc {
|
func (p *UserManagementPage) GET(engine *templating.Engine, app core.App) HandleFunc {
|
||||||
return func(e *core.RequestEvent) error {
|
return func(e *core.RequestEvent) error {
|
||||||
data := make(map[string]any)
|
data := make(map[string]any)
|
||||||
p.getData(app, data)
|
p.getData(app, templating.NewRequest(e), data)
|
||||||
SetRedirect(data, e)
|
SetRedirect(data, e)
|
||||||
|
|
||||||
return engine.Response200(e, p.Template, data, p.Layout)
|
return engine.Response200(e, p.Template, data, p.Layout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *UserManagementPage) getData(app core.App, data map[string]any) error {
|
func (p *UserManagementPage) getData(app core.App, req *templating.Request, data map[string]any) error {
|
||||||
records := []*core.Record{}
|
records := []*core.Record{}
|
||||||
err := app.RecordQuery(dbmodels.USERS_TABLE).OrderBy(dbmodels.USERS_NAME_FIELD).All(&records)
|
err := app.RecordQuery(dbmodels.USERS_TABLE).OrderBy(dbmodels.USERS_NAME_FIELD).All(&records)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -102,22 +102,17 @@ func (p *UserManagementPage) getData(app core.App, data map[string]any) error {
|
|||||||
data["users"] = users
|
data["users"] = users
|
||||||
data["len"] = len(users)
|
data["len"] = len(users)
|
||||||
data["session_counts"] = scmap
|
data["session_counts"] = scmap
|
||||||
|
data["csrf_token"] = req.Session().Token
|
||||||
csrfNonce, csrfToken, err := CSRF_CACHE.GenerateTokenBundleWithExpiration(2 * time.Hour)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Konnte kein CSRF-Token generieren.")
|
|
||||||
}
|
|
||||||
data["csrf_nonce"] = csrfNonce
|
|
||||||
data["csrf_token"] = csrfToken
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *UserManagementPage) ErrorResponse(engine *templating.Engine, e *core.RequestEvent, err error) error {
|
func (p *UserManagementPage) ErrorResponse(engine *templating.Engine, e *core.RequestEvent, err error) error {
|
||||||
data := make(map[string]any)
|
data := make(map[string]any)
|
||||||
|
req := templating.NewRequest(e)
|
||||||
data["error"] = err.Error()
|
data["error"] = err.Error()
|
||||||
|
|
||||||
err = p.getData(e.App, data)
|
err = p.getData(e.App, req, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
engine.Response500(e, fmt.Errorf("Nutzerdaten konnten nicht geladen werden: %w", err), data)
|
engine.Response500(e, fmt.Errorf("Nutzerdaten konnten nicht geladen werden: %w", err), data)
|
||||||
}
|
}
|
||||||
@@ -134,16 +129,16 @@ func (p *UserManagementPage) ErrorResponse(engine *templating.Engine, e *core.Re
|
|||||||
func (p *UserManagementPage) POSTDeactivate(engine *templating.Engine, app core.App) HandleFunc {
|
func (p *UserManagementPage) POSTDeactivate(engine *templating.Engine, app core.App) HandleFunc {
|
||||||
return func(e *core.RequestEvent) error {
|
return func(e *core.RequestEvent) error {
|
||||||
formdata := struct {
|
formdata := struct {
|
||||||
User string `form:"uid"`
|
User string `form:"uid"`
|
||||||
CSRF string `form:"csrf_token"`
|
CSRF string `form:"csrf_token"`
|
||||||
Nonce string `form:"csrf_nonce"`
|
|
||||||
}{}
|
}{}
|
||||||
|
|
||||||
if err := e.BindBody(&formdata); err != nil {
|
if err := e.BindBody(&formdata); err != nil {
|
||||||
return p.ErrorResponse(engine, e, fmt.Errorf("Formulardaten ungültig: %w", err))
|
return p.ErrorResponse(engine, e, fmt.Errorf("Formulardaten ungültig: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := CSRF_CACHE.ValidateTokenBundle(formdata.Nonce, formdata.CSRF); err != nil {
|
req := templating.NewRequest(e)
|
||||||
|
if err := req.CheckCSRF(formdata.CSRF); err != nil {
|
||||||
return p.ErrorResponse(engine, e, err)
|
return p.ErrorResponse(engine, e, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,9 +160,8 @@ func (p *UserManagementPage) POSTDeactivate(engine *templating.Engine, app core.
|
|||||||
data := make(map[string]any)
|
data := make(map[string]any)
|
||||||
data["success"] = "Nutzer " + u.Name() + "(" + u.Email() + ") wurde deaktiviert."
|
data["success"] = "Nutzer " + u.Name() + "(" + u.Email() + ") wurde deaktiviert."
|
||||||
|
|
||||||
p.getData(app, data)
|
p.getData(app, req, data)
|
||||||
|
|
||||||
req := templating.NewRequest(e)
|
|
||||||
if req.User() != nil && req.User().Id == u.Id {
|
if req.User() != nil && req.User().Id == u.Id {
|
||||||
return e.Redirect(303, "/login/")
|
return e.Redirect(303, "/login/")
|
||||||
}
|
}
|
||||||
@@ -180,16 +174,16 @@ func (p *UserManagementPage) POSTDeactivate(engine *templating.Engine, app core.
|
|||||||
func (p *UserManagementPage) POSTActivate(engine *templating.Engine, app core.App) HandleFunc {
|
func (p *UserManagementPage) POSTActivate(engine *templating.Engine, app core.App) HandleFunc {
|
||||||
return func(e *core.RequestEvent) error {
|
return func(e *core.RequestEvent) error {
|
||||||
formdata := struct {
|
formdata := struct {
|
||||||
User string `form:"uid"`
|
User string `form:"uid"`
|
||||||
CSRF string `form:"csrf_token"`
|
CSRF string `form:"csrf_token"`
|
||||||
Nonce string `form:"csrf_nonce"`
|
|
||||||
}{}
|
}{}
|
||||||
|
|
||||||
if err := e.BindBody(&formdata); err != nil {
|
if err := e.BindBody(&formdata); err != nil {
|
||||||
return p.ErrorResponse(engine, e, fmt.Errorf("Formulardaten ungültig: %w", err))
|
return p.ErrorResponse(engine, e, fmt.Errorf("Formulardaten ungültig: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := CSRF_CACHE.ValidateTokenBundle(formdata.Nonce, formdata.CSRF); err != nil {
|
req := templating.NewRequest(e)
|
||||||
|
if err := req.CheckCSRF(formdata.CSRF); err != nil {
|
||||||
return p.ErrorResponse(engine, e, err)
|
return p.ErrorResponse(engine, e, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,9 +205,8 @@ func (p *UserManagementPage) POSTActivate(engine *templating.Engine, app core.Ap
|
|||||||
data := make(map[string]any)
|
data := make(map[string]any)
|
||||||
data["success"] = "Nutzer " + u.Name() + "(" + u.Email() + ") wurde aktiviert."
|
data["success"] = "Nutzer " + u.Name() + "(" + u.Email() + ") wurde aktiviert."
|
||||||
|
|
||||||
p.getData(app, data)
|
p.getData(app, req, data)
|
||||||
|
|
||||||
req := templating.NewRequest(e)
|
|
||||||
if req.User() != nil && req.User().Id == u.Id {
|
if req.User() != nil && req.User().Id == u.Id {
|
||||||
return e.Redirect(303, "/login/")
|
return e.Redirect(303, "/login/")
|
||||||
}
|
}
|
||||||
@@ -226,16 +219,16 @@ func (p *UserManagementPage) POSTActivate(engine *templating.Engine, app core.Ap
|
|||||||
func (p *UserManagementPage) POSTLogout(engine *templating.Engine, app core.App) HandleFunc {
|
func (p *UserManagementPage) POSTLogout(engine *templating.Engine, app core.App) HandleFunc {
|
||||||
return func(e *core.RequestEvent) error {
|
return func(e *core.RequestEvent) error {
|
||||||
formdata := struct {
|
formdata := struct {
|
||||||
User string `form:"uid"`
|
User string `form:"uid"`
|
||||||
CSRF string `form:"csrf_token"`
|
CSRF string `form:"csrf_token"`
|
||||||
Nonce string `form:"csrf_nonce"`
|
|
||||||
}{}
|
}{}
|
||||||
|
|
||||||
if err := e.BindBody(&formdata); err != nil {
|
if err := e.BindBody(&formdata); err != nil {
|
||||||
return p.ErrorResponse(engine, e, fmt.Errorf("Formulardaten ungültig: %w", err))
|
return p.ErrorResponse(engine, e, fmt.Errorf("Formulardaten ungültig: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := CSRF_CACHE.ValidateTokenBundle(formdata.Nonce, formdata.CSRF); err != nil {
|
req := templating.NewRequest(e)
|
||||||
|
if err := req.CheckCSRF(formdata.CSRF); err != nil {
|
||||||
return p.ErrorResponse(engine, e, err)
|
return p.ErrorResponse(engine, e, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,9 +243,8 @@ func (p *UserManagementPage) POSTLogout(engine *templating.Engine, app core.App)
|
|||||||
data := make(map[string]any)
|
data := make(map[string]any)
|
||||||
data["success"] = "Nutzer " + u.Name() + "(" + u.Email() + ") wurde überall ausgeloggt."
|
data["success"] = "Nutzer " + u.Name() + "(" + u.Email() + ") wurde überall ausgeloggt."
|
||||||
|
|
||||||
p.getData(app, data)
|
p.getData(app, req, data)
|
||||||
|
|
||||||
req := templating.NewRequest(e)
|
|
||||||
if req.User() != nil && req.User().Id == u.Id {
|
if req.User() != nil && req.User().Id == u.Id {
|
||||||
return e.Redirect(301, "/login/")
|
return e.Redirect(301, "/login/")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
bracketSameLine: true,
|
"bracketSameLine": true,
|
||||||
bracketSpacing: true,
|
"bracketSpacing": true,
|
||||||
whitespaceSensitivity: "ignore",
|
"whitespaceSensitivity": "ignore",
|
||||||
proseWrap: "always",
|
"bracketLine": true,
|
||||||
bracketLine: true,
|
"useTabs": true,
|
||||||
useTabs: true,
|
"tabWidth": 2,
|
||||||
tabWidth: 2,
|
"printWidth": 700,
|
||||||
"plugins": ["prettier-plugin-go-template"]
|
"plugins": ["prettier-plugin-go-template"]
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -15,8 +15,8 @@
|
|||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
|
||||||
<script src="/assets/js/alpine.ajax.min.js"></script>
|
<script src="/assets/js/alpine.ajax.min.js" defer></script>
|
||||||
<script src="/assets/js/alpine.min.js"></script>
|
<script src="/assets/js/alpine.min.js" defer></script>
|
||||||
<script src="/assets/js/htmx.min.js"></script>
|
<script src="/assets/js/htmx.min.js"></script>
|
||||||
<script src="/assets/js/htmx-response-targets.js"></script>
|
<script src="/assets/js/htmx-response-targets.js"></script>
|
||||||
<script src="/assets/js/mark.min.js" defer></script>
|
<script src="/assets/js/mark.min.js" defer></script>
|
||||||
|
|||||||
@@ -4,26 +4,17 @@
|
|||||||
<div class="flex container-normal bg-slate-100 mx-auto !pt-36 px-8">
|
<div class="flex container-normal bg-slate-100 mx-auto !pt-36 px-8">
|
||||||
<div class="flex-col w-full">
|
<div class="flex-col w-full">
|
||||||
{{ if $model.redirect_url }}
|
{{ if $model.redirect_url }}
|
||||||
<a href="{{ $model.redirect_url }}" class="text-gray-700 hover:text-slate-950">
|
<a href="{{ $model.redirect_url }}" class="text-gray-700 hover:text-slate-950"> <i class="ri-arrow-left-s-line"></i> Zurück </a>
|
||||||
<i class="ri-arrow-left-s-line"></i> Zurück
|
|
||||||
</a>
|
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<a href="/" class="text-gray-700 hover:text-slate-950">
|
<a href="/" class="text-gray-700 hover:text-slate-950"> <i class="ri-arrow-left-s-line"></i> Startseite </a>
|
||||||
<i class="ri-arrow-left-s-line"></i> Startseite
|
|
||||||
</a>
|
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<h1 class="text-2xl self-baseline w-full my-6 font-bold text-slate-900">Profil bearbeiten</h1>
|
<h1 class="text-2xl self-baseline w-full my-6 font-bold text-slate-900">Profil bearbeiten</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex container-normal mx-auto px-8 mt-4">
|
<div class="flex container-normal mx-auto px-8 mt-4">
|
||||||
<div class="flex-col max-w-2xl w-full">
|
<div class="flex-col max-w-2xl w-full">
|
||||||
<form
|
<form class="w-full grid grid-cols-3 gap-4" id="changeuserform" x-target="changeuserform footer" hx-boost="false" method="POST" x-data="{ openpw: false }">
|
||||||
class="w-full grid grid-cols-3 gap-4"
|
|
||||||
id="changeuserform"
|
|
||||||
x-target="changeuserform footer"
|
|
||||||
hx-boost="false"
|
|
||||||
method="POST"
|
|
||||||
x-data="{ openpw: false }">
|
|
||||||
<div class="col-span-3">
|
<div class="col-span-3">
|
||||||
{{ template "_usermessage" $model }}
|
{{ template "_usermessage" $model }}
|
||||||
</div>
|
</div>
|
||||||
@@ -31,44 +22,21 @@
|
|||||||
class="rounded-xs col-span-3 border-2 border-transparent px-3
|
class="rounded-xs col-span-3 border-2 border-transparent px-3
|
||||||
py-1 pb-1.5 border-l-2 focus-within:border-l-slate-600
|
py-1 pb-1.5 border-l-2 focus-within:border-l-slate-600
|
||||||
bg-slate-200 focus-within:bg-slate-100 transition-all duration-100">
|
bg-slate-200 focus-within:bg-slate-100 transition-all duration-100">
|
||||||
<label for="username" class="text-sm text-gray-700 font-bold">
|
<label for="username" class="text-sm text-gray-700 font-bold"> Name <i class="ri-text"></i> </label>
|
||||||
Name <i class="ri-text"></i>
|
<input type="text" name="name" id="name" class="mt-1 block w-full focus:border-none focus:outline-none" placeholder="" required autocomplete="off" value="{{ $model.user.Name }}" autofocus />
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="name"
|
|
||||||
id="name"
|
|
||||||
class="mt-1 block w-full focus:border-none focus:outline-none"
|
|
||||||
placeholder=""
|
|
||||||
required
|
|
||||||
autocomplete="off"
|
|
||||||
value="{{ $model.user.Name }}"
|
|
||||||
autofocus />
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="rounded-xs col-span-3 border-2 border-transparent px-3
|
class="rounded-xs col-span-3 border-2 border-transparent px-3
|
||||||
py-1 pb-1.5 border-l-2 focus-within:border-l-slate-600
|
py-1 pb-1.5 border-l-2 focus-within:border-l-slate-600
|
||||||
bg-slate-200 focus-within:bg-slate-100 transition-all duration-100">
|
bg-slate-200 focus-within:bg-slate-100 transition-all duration-100">
|
||||||
<label for="username" class="text-sm text-gray-700 font-bold">
|
<label for="username" class="text-sm text-gray-700 font-bold"> E-Mail <i class="ri-at-line"></i> </label>
|
||||||
E-Mail <i class="ri-at-line"></i>
|
<input type="email" name="username" id="username" autocomplete="off" class="mt-1 block w-full rounded-md focus:border-none focus:outline-none" placeholder="" required value="{{ $model.user.Email }}" />
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="email"
|
|
||||||
name="username"
|
|
||||||
id="username"
|
|
||||||
autocomplete="off"
|
|
||||||
class="mt-1 block w-full rounded-md focus:border-none focus:outline-none"
|
|
||||||
placeholder=""
|
|
||||||
required
|
|
||||||
value="{{ $model.user.Email }}" />
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="rounded-xs col-span-3 border-2 border-transparent px-3
|
class="rounded-xs col-span-3 border-2 border-transparent px-3
|
||||||
py-1 pb-1.5 border-l-2 focus-within:border-l-slate-600
|
py-1 pb-1.5 border-l-2 focus-within:border-l-slate-600
|
||||||
bg-slate-200 focus-within:bg-slate-100 transition-all duration-100">
|
bg-slate-200 focus-within:bg-slate-100 transition-all duration-100">
|
||||||
<label for="role" class="text-sm text-gray-700 font-bold">
|
<label for="role" class="text-sm text-gray-700 font-bold"> Rolle <i class="ri-user-3-line"></i> </label>
|
||||||
Rolle <i class="ri-user-3-line"></i>
|
|
||||||
</label>
|
|
||||||
<select
|
<select
|
||||||
{{ if not (eq $model.request.user.Role "Admin") -}}
|
{{ if not (eq $model.request.user.Role "Admin") -}}
|
||||||
disabled
|
disabled
|
||||||
@@ -78,15 +46,9 @@
|
|||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
class="mt-1 block w-full rounded-md focus:border-none focus:outline-none
|
class="mt-1 block w-full rounded-md focus:border-none focus:outline-none
|
||||||
disabled:opacity-50">
|
disabled:opacity-50">
|
||||||
<option value="User" {{ if eq $model.user.Role "User" }}selected{{ end }}>
|
<option value="User" {{ if eq $model.user.Role "User" }}selected{{ end }}>Benutzer</option>
|
||||||
Benutzer
|
<option value="Editor" {{ if eq $model.user.Role "Editor" }}selected{{ end }}>Redakteur</option>
|
||||||
</option>
|
<option value="Admin" {{ if eq $model.user.Role "Admin" }}selected{{ end }}>Administrator</option>
|
||||||
<option value="Editor" {{ if eq $model.user.Role "Editor" }}selected{{ end }}>
|
|
||||||
Redakteur
|
|
||||||
</option>
|
|
||||||
<option value="Admin" {{ if eq $model.user.Role "Admin" }}selected{{ end }}>
|
|
||||||
Administrator
|
|
||||||
</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
{{- if and
|
{{- if and
|
||||||
@@ -98,29 +60,20 @@
|
|||||||
<div class="align-top">
|
<div class="align-top">
|
||||||
<i class="ri-information-line text-gray-700 mt-2 mr-2 align-top"></i>
|
<i class="ri-information-line text-gray-700 mt-2 mr-2 align-top"></i>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm text-gray-700 max-w-[80ch]">
|
<p class="text-sm text-gray-700 max-w-[80ch]">Achtung! Wenn Sie Ihre eigenen Berechtigungen ändern, verlieren Sie die Möglichkeit, diese Änderungen rückgängig zu machen. Weiter werden Sie von allen laufenden Sitzungen abgemeldet und müssen sich erneut anmelden.</p>
|
||||||
Achtung! Wenn Sie Ihre eigenen Berechtigungen ändern, verlieren Sie die Möglichkeit,
|
|
||||||
diese Änderungen rückgängig zu machen. Weiter werden Sie von allen laufenden Sitzungen
|
|
||||||
abgemeldet und müssen sich erneut anmelden.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
{{- else if (eq $model.request.user.Role "Admin") -}}
|
{{- else if (eq $model.request.user.Role "Admin") -}}
|
||||||
<div class="flex flex-row col-span-3">
|
<div class="flex flex-row col-span-3">
|
||||||
<div class="align-top">
|
<div class="align-top">
|
||||||
<i class="ri-information-line text-gray-700 mt-2 mr-2 align-top"></i>
|
<i class="ri-information-line text-gray-700 mt-2 mr-2 align-top"></i>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm text-gray-700 max-w-[80ch]">
|
<p class="text-sm text-gray-700 max-w-[80ch]">Achtung! Wenn Sie die Rolle eines Benutzers ändern, wird dieser unter Umständen von laufenden Sitzungen abgemeldet und muss sich erneut anmelden.</p>
|
||||||
Achtung! Wenn Sie die Rolle eines Benutzers ändern, wird dieser unter Umständen von
|
|
||||||
laufenden Sitzungen abgemeldet und muss sich erneut anmelden.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
<div class="col-span-3">
|
<div class="col-span-3">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<input type="checkbox" name="openpw" id="openpw" x-model="openpw" class="mr-2" />
|
<input type="checkbox" name="openpw" id="openpw" x-model="openpw" class="mr-2" />
|
||||||
<label for="openpw" class="text-sm text-gray-700 font-bold">
|
<label for="openpw" class="text-sm text-gray-700 font-bold"> Passwort ändern <i class="ri-key-2-line"></i> </label>
|
||||||
Passwort ändern <i class="ri-key-2-line"></i>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{- if not (eq $model.request.user.Role "Admin") -}}
|
{{- if not (eq $model.request.user.Role "Admin") -}}
|
||||||
@@ -130,13 +83,7 @@
|
|||||||
py-1 pb-1.5 border-l-2 focus-within:border-l-slate-600
|
py-1 pb-1.5 border-l-2 focus-within:border-l-slate-600
|
||||||
bg-slate-200 focus-within:bg-slate-100 transition-all duration-100">
|
bg-slate-200 focus-within:bg-slate-100 transition-all duration-100">
|
||||||
<label for="password_old" class="text-sm text-gray-700 font-bold"> Altes Passwort </label>
|
<label for="password_old" class="text-sm text-gray-700 font-bold"> Altes Passwort </label>
|
||||||
<input
|
<input x-bind:type="openpw ? 'password' : 'hidden'" name="password_old" id="password_old" class="mt-1 block w-full rounded-md focus:border-none focus:outline-none" placeholder="" required />
|
||||||
x-bind:type="openpw ? 'password' : 'hidden'"
|
|
||||||
name="password_old"
|
|
||||||
id="password_old"
|
|
||||||
class="mt-1 block w-full rounded-md focus:border-none focus:outline-none"
|
|
||||||
placeholder=""
|
|
||||||
required />
|
|
||||||
</div>
|
</div>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
<div
|
<div
|
||||||
@@ -145,42 +92,19 @@
|
|||||||
py-1 pb-1.5 border-l-2 focus-within:border-l-slate-600
|
py-1 pb-1.5 border-l-2 focus-within:border-l-slate-600
|
||||||
bg-slate-200 focus-within:bg-slate-100 transition-all duration-100">
|
bg-slate-200 focus-within:bg-slate-100 transition-all duration-100">
|
||||||
<label for="password" class="text-sm text-gray-700 font-bold"> Neues Passwort </label>
|
<label for="password" class="text-sm text-gray-700 font-bold"> Neues Passwort </label>
|
||||||
<input
|
<input x-bind:type="openpw ? 'password' : 'hidden'" minlength="10" name="password" id="password" class="mt-1 block w-full rounded-md focus:border-none focus:outline-none" placeholder="" required />
|
||||||
x-bind:type="openpw ? 'password' : 'hidden'"
|
|
||||||
minlength="10"
|
|
||||||
name="password"
|
|
||||||
id="password"
|
|
||||||
class="mt-1 block w-full rounded-md focus:border-none focus:outline-none"
|
|
||||||
placeholder=""
|
|
||||||
required />
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
x-bind:style="!openpw ? 'display:none' : ''"
|
x-bind:style="!openpw ? 'display:none' : ''"
|
||||||
class="rounded-xs col-span-3 border-2 border-transparent px-3
|
class="rounded-xs col-span-3 border-2 border-transparent px-3
|
||||||
py-1 pb-1.5 border-l-2 focus-within:border-l-slate-600
|
py-1 pb-1.5 border-l-2 focus-within:border-l-slate-600
|
||||||
bg-slate-200 focus-within:bg-slate-100 transition-all duration-100">
|
bg-slate-200 focus-within:bg-slate-100 transition-all duration-100">
|
||||||
<label for="password_repeat" class="text-sm text-gray-700 font-bold">
|
<label for="password_repeat" class="text-sm text-gray-700 font-bold"> Passwort wiederholen </label>
|
||||||
Passwort wiederholen
|
<input x-bind:type="openpw ? 'password' : 'hidden'" minlength="10" name="password_repeat" id="password_repeat" class="mt-1 block w-full rounded-md focus:border-none focus:outline-none" placeholder="" required />
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
x-bind:type="openpw ? 'password' : 'hidden'"
|
|
||||||
minlength="10"
|
|
||||||
name="password_repeat"
|
|
||||||
id="password_repeat"
|
|
||||||
class="mt-1 block w-full rounded-md focus:border-none focus:outline-none"
|
|
||||||
placeholder=""
|
|
||||||
required />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-span-3 flex justify-end" x-bind:style="!openpw ? 'display:none' : ''">
|
<div class="col-span-3 flex justify-end" x-bind:style="!openpw ? 'display:none' : ''">
|
||||||
<input
|
<input type="checkbox" name="logout" id="logout" class="mr-2" x-bind:style="!openpw ? 'display:none' : ''" />
|
||||||
type="checkbox"
|
<label for="logout" class="text-sm text-gray-700 font-bold"> überall ausloggen <i class="ri-logout-box-line"></i> </label>
|
||||||
name="logout"
|
|
||||||
id="logout"
|
|
||||||
class="mr-2"
|
|
||||||
x-bind:style="!openpw ? 'display:none' : ''" />
|
|
||||||
<label for="logout" class="text-sm text-gray-700 font-bold">
|
|
||||||
überall ausloggen <i class="ri-logout-box-line"></i>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-span-1 col-start-2">
|
<div class="col-span-1 col-start-2">
|
||||||
<a
|
<a
|
||||||
@@ -192,18 +116,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-span-1 col-start-3">
|
<div class="col-span-1 col-start-3">
|
||||||
<input
|
<input type="hidden" name="csrf_token" id="csrf_token" required value="{{ $model.csrf_token }}" />
|
||||||
type="hidden"
|
|
||||||
name="csrf_nonce"
|
|
||||||
id="csrf_nonce"
|
|
||||||
required
|
|
||||||
value="{{ $model.csrf_nonce }}" />
|
|
||||||
<input
|
|
||||||
type="hidden"
|
|
||||||
name="csrf_token"
|
|
||||||
id="csrf_token"
|
|
||||||
required
|
|
||||||
value="{{ $model.csrf_token }}" />
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="w-full inline-flex justify-center py-2 px-4 border border-transparent rounded-md
|
class="w-full inline-flex justify-center py-2 px-4 border border-transparent rounded-md
|
||||||
@@ -216,47 +129,17 @@
|
|||||||
|
|
||||||
<div class="col-span-1 mt-12 justify-self-end self-end items-end flex flex-row justify-end">
|
<div class="col-span-1 mt-12 justify-self-end self-end items-end flex flex-row justify-end">
|
||||||
{{ if not $model.user.Deactivated }}
|
{{ if not $model.user.Deactivated }}
|
||||||
<form action="/user/{{ $model.user.Id }}/deactivate/" method="POST">
|
<form id="actbtn" x-init @ajax:before="confirm('Der Benutzer {{ $model.user.Name }} wird deaktiviert und kann sich nicht mehr einloggen. Sicher?') || $event.preventDefault()" action="/user/{{ $model.user.Id }}/deactivate/" method="POST" hx-boost="false" x-target="user-message footer actbtn" x-target.away="_top">
|
||||||
<input
|
<input type="hidden" name="csrf_token" id="csrf_token" required value="{{ $model.csrf_token }}" />
|
||||||
type="hidden"
|
<button type="submit" type="button" class="cursor-pointer text-red-700 hover:text-red-900 pr-4 mt-6">
|
||||||
name="csrf_nonce"
|
|
||||||
id="csrf_nonce"
|
|
||||||
required
|
|
||||||
value="{{ $model.csrf_nonce }}" />
|
|
||||||
<input
|
|
||||||
type="hidden"
|
|
||||||
name="csrf_token"
|
|
||||||
id="csrf_token"
|
|
||||||
required
|
|
||||||
value="{{ $model.csrf_token }}" />
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
type="button"
|
|
||||||
class="cursor-pointer text-red-700 hover:text-red-900 pr-4 mt-6">
|
|
||||||
<i class="ri-prohibited-2-line"></i>
|
<i class="ri-prohibited-2-line"></i>
|
||||||
Deaktivieren
|
Deaktivieren
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<form action="/user/{{ $model.user.Id }}/activate/" method="POST">
|
<form id="actbtn" x-init @ajax:before="confirm('Der Benutzer {{ $model.user.Name }} wird wieder aktiviert und kann sich einloggen. Sicher?') || $event.preventDefault()" action="/user/{{ $model.user.Id }}/activate/" method="POST" hx-boost="false" x-target="user-message footer actbtn" x-target.away="_top">
|
||||||
<input
|
<input type="hidden" name="csrf_token" id="csrf_token" required value="{{ $model.csrf_token }}" />
|
||||||
type="hidden"
|
<button type="submit" type="button" class="cursor-pointer text-green-700 hover:text-green-900 pr-4 mt-6"><i class="ri-restart-line"></i> Aktivieren</button>
|
||||||
name="csrf_nonce"
|
|
||||||
id="csrf_nonce"
|
|
||||||
required
|
|
||||||
value="{{ $model.csrf_nonce }}" />
|
|
||||||
<input
|
|
||||||
type="hidden"
|
|
||||||
name="csrf_token"
|
|
||||||
id="csrf_token"
|
|
||||||
required
|
|
||||||
value="{{ $model.csrf_token }}" />
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
type="button"
|
|
||||||
class="cursor-pointer text-green-700 hover:text-green-900 pr-4 mt-6">
|
|
||||||
<i class="ri-restart-line"></i> Aktivieren
|
|
||||||
</button>
|
|
||||||
</form>
|
</form>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,13 +4,9 @@
|
|||||||
<div class="flex container-normal bg-slate-100 mx-auto !pt-36 px-8">
|
<div class="flex container-normal bg-slate-100 mx-auto !pt-36 px-8">
|
||||||
<div class="flex-col w-full">
|
<div class="flex-col w-full">
|
||||||
{{ if $model.redirect_url }}
|
{{ if $model.redirect_url }}
|
||||||
<a href="{{ $model.redirect_url }}" class="text-gray-700 hover:text-slate-950">
|
<a href="{{ $model.redirect_url }}" class="text-gray-700 hover:text-slate-950"> <i class="ri-arrow-left-s-line"></i> Zurück </a>
|
||||||
<i class="ri-arrow-left-s-line"></i> Zurück
|
|
||||||
</a>
|
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<a href="/" class="text-gray-700 hover:text-slate-950">
|
<a href="/" class="text-gray-700 hover:text-slate-950"> <i class="ri-arrow-left-s-line"></i> Startseite </a>
|
||||||
<i class="ri-arrow-left-s-line"></i> Startseite
|
|
||||||
</a>
|
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<h1
|
<h1
|
||||||
class="text-2xl self-baseline w-full my-6 font-bold
|
class="text-2xl self-baseline w-full my-6 font-bold
|
||||||
@@ -19,24 +15,10 @@
|
|||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex container-normal mx-auto px-8 mt-4">
|
<div class="flex container-normal mx-auto px-8 mt-4">
|
||||||
<div class="flex-col w-full">
|
<div class="flex-col w-full">
|
||||||
<div id="user-message">
|
{{ template "_usermessage" $model }}
|
||||||
{{ if $model.success }}
|
|
||||||
<div
|
|
||||||
class="text-green-800 text-sm mt-2 rounded bg-green-200 p-2 font-bold border-green-700
|
|
||||||
border-2 mb-3">
|
|
||||||
{{ $model.success }}
|
|
||||||
</div>
|
|
||||||
{{ end }}
|
|
||||||
{{ if $model.error }}
|
|
||||||
<div
|
|
||||||
class="text-red-800 text-sm mt-2 rounded bg-red-200 p-2 font-bold border-red-700
|
|
||||||
border-2 mb-3">
|
|
||||||
{{ $model.error }}
|
|
||||||
</div>
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
|
||||||
<table class="user-mgmt w-full text-lg">
|
<table class="user-mgmt w-full text-lg">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -49,7 +31,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{ range $u := $model.users }}
|
{{ range $u := $model.users }}
|
||||||
<tr class="{{ if $u.Deactivated }}deactivated{{ end }}">
|
<tr class="{{ if $u.Deactivated }}deactivated{{ end }}" id="user-{{ $u.Id }}">
|
||||||
<td>{{ $u.Name }}</td>
|
<td>{{ $u.Name }}</td>
|
||||||
<td>{{ $u.Email }}</td>
|
<td>{{ $u.Email }}</td>
|
||||||
{{- if eq $u.Role "Admin" -}}
|
{{- if eq $u.Role "Admin" -}}
|
||||||
@@ -61,61 +43,64 @@
|
|||||||
{{- end -}}
|
{{- end -}}
|
||||||
<td>{{ index $model.session_counts $u.Id }}</td>
|
<td>{{ index $model.session_counts $u.Id }}</td>
|
||||||
<td>
|
<td>
|
||||||
<form class="flex flex-row gap-x-4 justify-end">
|
<div class="flex flex-row justify-end items-center gap-2">
|
||||||
<input type="hidden" name="uid" id="uid" required value="{{ $u.Id }}" />
|
<!-- INFO: User edit button -->
|
||||||
<input
|
<div class="edit-button">
|
||||||
type="hidden"
|
<tool-tip position="top">
|
||||||
name="csrf_nonce"
|
<div class="data-tip font-bold hidden">Bearbeiten</div>
|
||||||
id="csrf_nonce"
|
<a href="/user/{{ $u.Id }}/edit?redirectTo=/user/management" class="text-gray-200 no-underline">
|
||||||
required
|
<i class="ri-pencil-line"></i>
|
||||||
value="{{ $model.csrf_nonce }}" />
|
</a>
|
||||||
<input
|
</tool-tip>
|
||||||
type="hidden"
|
</div>
|
||||||
name="csrf_token"
|
|
||||||
id="csrf_token"
|
<!-- INFO: User logout button -->
|
||||||
required
|
<form
|
||||||
value="{{ $model.csrf_token }}" />
|
{{- if eq $u.Id $model.request.user.Id }}
|
||||||
<!-- INFO: we dont use request.fullpath here, since this can be /logout /activate
|
x-init @ajax:before="confirm('Sie werden ausgeloggt und müssen sich neu einloggen. Sicher?') || $event.preventDefault()"
|
||||||
or /deactivate, which would not work with the redirectTo query param -->
|
{{ end }}
|
||||||
<tool-tip position="top">
|
class=""
|
||||||
<div class="data-tip font-bold hidden">Bearbeiten</div>
|
method="POST"
|
||||||
<button
|
action="/user/management/logout/"
|
||||||
formmethod="GET"
|
hx-boost="false"
|
||||||
formaction="/user/{{ $u.Id }}/edit?redirectTo=/user/management">
|
x-target="user-{{ $u.Id }} user-message"
|
||||||
<i class="ri-pencil-line"></i>
|
x-target.away="_top">
|
||||||
</button>
|
<input type="hidden" name="uid" id="uid" required value="{{ $u.Id }}" />
|
||||||
</tool-tip>
|
<input type="hidden" name="csrf_token" id="csrf_token" required value="{{ $model.csrf_token }}" />
|
||||||
<tool-tip position="top">
|
<tool-tip position="top">
|
||||||
<div class="data-tip font-bold hidden">Logout</div>
|
<div class="data-tip font-bold hidden">Logout</div>
|
||||||
<button
|
<button class="text-red-900 bg-red-200 hover:bg-red-300">
|
||||||
formmethod="POST"
|
<i class="ri-logout-box-r-line"></i>
|
||||||
formaction="/user/management/logout/"
|
</button>
|
||||||
class="text-orange-800 bg-orange-200 hover:bg-orange-300">
|
</tool-tip>
|
||||||
<i class="ri-logout-box-r-line"></i>
|
</form>
|
||||||
</button>
|
|
||||||
</tool-tip>
|
<!-- INFO: User activate/deactivate button -->
|
||||||
{{- if $u.Deactivated }}
|
{{- if $u.Deactivated }}
|
||||||
<tool-tip position="top">
|
<form x-init @ajax:before="confirm('Der Benutzer {{ $u.Name }} wird wieder aktiviert und kann sich einloggen. Sicher?') || $event.preventDefault()" class="" method="POST" action="/user/management/activate/" hx-boost="false" x-target="user-{{ $u.Id }} user-message">
|
||||||
<div class="data-tip font-bold hidden">Reaktivieren</div>
|
<input type="hidden" name="uid" id="uid" required value="{{ $u.Id }}" />
|
||||||
<button
|
<input type="hidden" name="csrf_token" id="csrf_token" required value="{{ $model.csrf_token }}" />
|
||||||
formmethod="POST"
|
<tool-tip position="top">
|
||||||
formaction="/user/management/activate/"
|
<div class="data-tip font-bold hidden">Reaktivieren</div>
|
||||||
class="text-green-800 bg-green-200 hover:bg-green-300">
|
<button class="bg-green-600 hover:bg-green-700">
|
||||||
<i class="ri-restart-line"></i>
|
<i class="ri-restart-line"></i>
|
||||||
</button>
|
</button>
|
||||||
</tool-tip>
|
</tool-tip>
|
||||||
|
</form>
|
||||||
{{- else -}}
|
{{- else -}}
|
||||||
<tool-tip position="top">
|
<form x-init @ajax:before="confirm('Der Benutzer {{ $u.Name }} wird ausgeloggt und kann sich nicht mehr einloggen. Sicher?') || $event.preventDefault()" class="" method="POST" action="/user/management/deactivate/" hx-boost="false" x-target="user-{{ $u.Id }} user-message" x-target.away="_top">
|
||||||
<div class="data-tip font-bold hidden">Deaktivieren</div>
|
<input type="hidden" name="uid" id="uid" required value="{{ $u.Id }}" />
|
||||||
<button
|
<input type="hidden" name="csrf_token" id="csrf_token" required value="{{ $model.csrf_token }}" />
|
||||||
formmethod="POST"
|
<tool-tip position="top">
|
||||||
formaction="/user/management/deactivate/"
|
<div class="data-tip font-bold hidden">Deaktivieren</div>
|
||||||
class="text-red-800 bg-red-200 hover:bg-red-300">
|
<button class="bg-red-600 hover:bg-red-700">
|
||||||
<i class="ri-prohibited-2-line"></i>
|
<i class="ri-prohibited-2-line"></i>
|
||||||
</button>
|
</button>
|
||||||
</tool-tip>
|
</tool-tip>
|
||||||
|
</form>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
</form>
|
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|||||||
@@ -560,7 +560,8 @@
|
|||||||
@apply text-gray-400 line-through;
|
@apply text-gray-400 line-through;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-mgmt form button {
|
.user-mgmt form button,
|
||||||
|
.user-mgmt .edit-button {
|
||||||
@apply bg-slate-700 text-gray-200 text-base rounded-xs font-sans transition-all duration-75 px-3 py-1.5 hover:bg-slate-800 hover:text-white cursor-pointer;
|
@apply bg-slate-700 text-gray-200 text-base rounded-xs font-sans transition-all duration-75 px-3 py-1.5 hover:bg-slate-800 hover:text-white cursor-pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -765,7 +766,7 @@
|
|||||||
@apply bg-gray-100 text-gray-700 border border-gray-300 py-1 px-1.5 text-sm rounded hover:bg-gray-200 hover:border-gray-400 disabled:bg-gray-50 disabled:text-gray-400 disabled:border-gray-200 disabled:opacity-70 disabled:cursor-not-allowed;
|
@apply bg-gray-100 text-gray-700 border border-gray-300 py-1 px-1.5 text-sm rounded hover:bg-gray-200 hover:border-gray-400 disabled:bg-gray-50 disabled:text-gray-400 disabled:border-gray-200 disabled:opacity-70 disabled:cursor-not-allowed;
|
||||||
}
|
}
|
||||||
.mss-create-new-button.hidden {
|
.mss-create-new-button.hidden {
|
||||||
@apply hidden !important; /* Ensure it hides */
|
@apply !hidden; /* Ensure it hides */
|
||||||
}
|
}
|
||||||
|
|
||||||
.mss-options-list {
|
.mss-options-list {
|
||||||
|
|||||||
Reference in New Issue
Block a user