feat(webui): open a currated version of the user accounts section to non admin users
All checks were successful
main / main (push) Successful in 1m46s
main / deploy (push) Has been skipped
main / publish (push) Has been skipped

This commit is contained in:
Julien Dessaux 2025-04-15 00:02:48 +02:00
parent 2bf1731343
commit 0aaf93d4ba
Signed by: adyxax
GPG key ID: F92E51B86E07177E
8 changed files with 46 additions and 23 deletions

View file

@ -2,9 +2,12 @@
<h1>User Accounts</h1> <h1>User Accounts</h1>
<div class="flex-row" style="justify-content:space-between;"> <div class="flex-row" style="justify-content:space-between;">
<div style="min-width:240px;"> <div style="min-width:240px;">
<p>There are <span class="button">{{ len .Accounts }}</span> user accounts.</p> <p>
<p>Use this page to inspect user accounts or create a new one.</p> There are <strong>{{ len .Accounts }}</strong> user accounts.
Use this page to inspect user accounts.
</p>
</div> </div>
{{ if .Page.IsAdmin }}
<form action="/accounts" enctype="multipart/form-data" method="post"> <form action="/accounts" enctype="multipart/form-data" method="post">
<fieldset> <fieldset>
<legend>New User Account</legend> <legend>New User Account</legend>
@ -39,6 +42,7 @@
<button class="primary" type="submit" value="submit">Create User Account</button> <button class="primary" type="submit" value="submit">Create User Account</button>
</fieldset> </fieldset>
</form> </form>
{{ end }}
</div> </div>
<article> <article>
<table style="width:100%;"> <table style="width:100%;">

View file

@ -3,7 +3,7 @@
{{ if ne .Account.PasswordReset nil }} {{ if ne .Account.PasswordReset nil }}
<h2>Password Reset</h2> <h2>Password Reset</h2>
<article> <article>
Direct the user to <a href="/account/{{ .Account.Id }}/reset/{{ .Account.PasswordReset }}">/account/{{ .Account.Id }}/reset/{{ .Account.PasswordReset }}</a> so that they can create their password. Direct the user to <a href="/account/{{ .Account.Id }}/reset/{{ .Account.PasswordReset }}">/account/{{ .Account.Id }}/reset/{{ .Account.PasswordReset }}</a> so that they can create their password.
</article> </article>
{{ end }} {{ end }}
<h2>Status</h2> <h2>Status</h2>
@ -24,6 +24,7 @@ Direct the user to <a href="/account/{{ .Account.Id }}/reset/{{ .Account.Passwor
{{ if .Account.IsAdmin }} {{ if .Account.IsAdmin }}
<p>This accounts has <strong>admin</strong> privileges on TfStated.</p> <p>This accounts has <strong>admin</strong> privileges on TfStated.</p>
{{ end }} {{ end }}
{{ if .Page.IsAdmin }}
<h2>Operations</h2> <h2>Operations</h2>
<form action="/accounts/{{ .Account.Id }}" enctype="multipart/form-data" method="post"> <form action="/accounts/{{ .Account.Id }}" enctype="multipart/form-data" method="post">
<div class="flex-row"> <div class="flex-row">
@ -37,7 +38,8 @@ Direct the user to <a href="/account/{{ .Account.Id }}/reset/{{ .Account.Passwor
type="text" type="text"
value="{{ .Username }}"> value="{{ .Username }}">
<label for="is-admin">Is Admin</label> <label for="is-admin">Is Admin</label>
<input {{ if .Account.IsAdmin }} checked{{ end }} <input {{ if .Account.IsAdmin }}checked{{ end }}
{{ if eq .Page.AccountId.String .Account.Id.String }}disabled{{ end }}
id="is-admin" id="is-admin"
name="is-admin" name="is-admin"
type="checkbox" type="checkbox"
@ -61,12 +63,21 @@ Direct the user to <a href="/account/{{ .Account.Id }}/reset/{{ .Account.Passwor
</fieldset> </fieldset>
<fieldset> <fieldset>
<legend>Danger Zone</legend> <legend>Danger Zone</legend>
<button type="submit" value="delete">Delete User Account</button> <button {{ if eq .Page.AccountId.String .Account.Id.String }}disabled{{ end }}
type="submit"
value="delete">
Delete User Account
</button>
<!--<button type="submit" value="lock">Lock User Account</button>--> <!--<button type="submit" value="lock">Lock User Account</button>-->
<button type="submit" value="reset-password">Reset Password</button> <button {{ if eq .Page.AccountId.String .Account.Id.String }}disabled{{ end }}
type="submit"
value="reset-password">
Reset Password
</button>
</fieldset> </fieldset>
</div> </div>
</form> </form>
{{ end }}
{{ if gt (len .Versions) 0 }} {{ if gt (len .Versions) 0 }}
<h2>Activity</h2> <h2>Activity</h2>
<article> <article>

View file

@ -28,12 +28,10 @@
<i class="material-symbols-outlined">settings</i> <i class="material-symbols-outlined">settings</i>
<span>Settings</span> <span>Settings</span>
</a> </a>
{{ if .Page.IsAdmin }}
<a href="/accounts"{{ if eq .Page.Section "accounts" }} class="primary"{{ end}}> <a href="/accounts"{{ if eq .Page.Section "accounts" }} class="primary"{{ end}}>
<i class="material-symbols-outlined">person</i> <i class="material-symbols-outlined">person</i>
<span>User Accounts</span> <span>User Accounts</span>
</a> </a>
{{ end }}
<hr> <hr>
<a href="/logout"> <a href="/logout">
<i class="material-symbols-outlined">logout</i> <i class="material-symbols-outlined">logout</i>

View file

@ -2,7 +2,7 @@
<h1>States</h1> <h1>States</h1>
<div class="flex-row" style="justify-content:space-between;"> <div class="flex-row" style="justify-content:space-between;">
<div style="min-width:240px;"> <div style="min-width:240px;">
<p>TfStated is currently managing <span class="button">{{ len .States }}</span> states.</p> <p>TfStated is currently managing <strong>{{ len .States }}</strong> states.</p>
<p>Use this page to inspect the existing states.</p> <p>Use this page to inspect the existing states.</p>
<p>You also have the option to upload a JSON state file in order to create a new state in TfStated. This is equivalent to using the <code>state push</code> command of OpenTofu/Terraform on a brand new state.</p> <p>You also have the option to upload a JSON state file in order to create a new state in TfStated. This is equivalent to using the <code>state push</code> command of OpenTofu/Terraform on a brand new state.</p>
</div> </div>

View file

@ -2,23 +2,22 @@
<h1>State</h1> <h1>State</h1>
<p> <p>
The state at path The state at path
<span class="button">{{ .State.Path }}</span> <strong>{{ .State.Path }}</strong>
has has
<span class="button">{{ len .Versions }}</span> <strong>{{ len .Versions }}</strong>
versions and is currently versions and is currently
<span class="button"> <strong>
{{ if eq .State.Lock nil }} {{ if eq .State.Lock nil }}
unlocked unlocked.
{{ else }} {{ else }}
<span class="tooltip"> <span class="tooltip">
locked locked.
<span class="tooltip-text"> <span class="tooltip-text">
{{ .State.Lock }} {{ .State.Lock }}
</span> </span>
</span> </span>
{{ end }} {{ end }}
</span> </strong>
.
</p> </p>
<p>Use this page to inspect the existing versions.</p> <p>Use this page to inspect the existing versions.</p>
<article> <article>

View file

@ -5,9 +5,11 @@ import (
"net/http" "net/http"
"git.adyxax.org/adyxax/tfstated/pkg/model" "git.adyxax.org/adyxax/tfstated/pkg/model"
"go.n16f.net/uuid"
) )
type Page struct { type Page struct {
AccountId uuid.UUID
IsAdmin bool IsAdmin bool
LightMode bool LightMode bool
Section string Section string
@ -16,6 +18,7 @@ type Page struct {
func makePage(r *http.Request, page *Page) *Page { func makePage(r *http.Request, page *Page) *Page {
account := r.Context().Value(model.AccountContextKey{}).(*model.Account) account := r.Context().Value(model.AccountContextKey{}).(*model.Account)
page.AccountId = account.Id
page.IsAdmin = account.IsAdmin page.IsAdmin = account.IsAdmin
settings := r.Context().Value(model.SettingsContextKey{}).(*model.Settings) settings := r.Context().Value(model.SettingsContextKey{}).(*model.Settings)
page.LightMode = settings.LightMode page.LightMode = settings.LightMode

View file

@ -13,8 +13,8 @@ func addRoutes(
requireSession := sessionsMiddleware(db) requireSession := sessionsMiddleware(db)
requireLogin := loginMiddleware(db, requireSession) requireLogin := loginMiddleware(db, requireSession)
requireAdmin := adminMiddleware(db, requireLogin) requireAdmin := adminMiddleware(db, requireLogin)
mux.Handle("GET /accounts", requireAdmin(handleAccountsGET(db))) mux.Handle("GET /accounts", requireLogin(handleAccountsGET(db)))
mux.Handle("GET /accounts/{id}", requireAdmin(handleAccountsIdGET(db))) mux.Handle("GET /accounts/{id}", requireLogin(handleAccountsIdGET(db)))
mux.Handle("POST /accounts", requireAdmin(handleAccountsPOST(db))) mux.Handle("POST /accounts", requireAdmin(handleAccountsPOST(db)))
mux.Handle("GET /healthz", handleHealthz()) mux.Handle("GET /healthz", handleHealthz())
mux.Handle("GET /login", requireSession(handleLoginGET())) mux.Handle("GET /login", requireSession(handleLoginGET()))

View file

@ -85,6 +85,11 @@ input:not([type=image i], [type=range i], [type=checkbox i], [type=radio i]) {
overflow: clip !important; overflow: clip !important;
overflow-clip-margin: 0 !important; overflow-clip-margin: 0 !important;
} }
button:disabled, button:disabled:hover {
background-color: var(--bg-2);
border: 1px solid var(--fg-1);
color:var(--dim) !important;
}
html { html {
font-family: var(--sans-font); font-family: var(--sans-font);
@ -248,12 +253,11 @@ fieldset {
padding: 8px; padding: 8px;
} }
button, .button { button, .button {
background-color: var(--bg-2);
border: 1px solid var(--orange);
color: var(--orange);
align-items: center; align-items: center;
background-color: var(--bg-2);
border: 1px solid var(--fg-1); border: 1px solid var(--fg-1);
border-radius: 8px; border-radius: 8px;
color: var(--orange);
display: inline-flex; display: inline-flex;
font-size: 16px; font-size: 16px;
gap: 8px; gap: 8px;
@ -329,6 +333,10 @@ footer p {
footer a { footer a {
color: var(--green); color: var(--green);
} }
strong {
color: var(--orange);
font-weight: bolder;
}
@media only screen and (640px <= width < 968px) { /*856*/ @media only screen and (640px <= width < 968px) { /*856*/
header { header {