feat(config): add box owner policy settings
Adds configuration options and environment variables to manage box owner policies, including settings for refresh counts and expiry.
This commit is contained in:
198
templates/account_dashboard.html
Normal file
198
templates/account_dashboard.html
Normal file
@@ -0,0 +1,198 @@
|
||||
{{ template "account_shell_start" . }}
|
||||
<main class="account-window" aria-labelledby="account-dashboard-title">
|
||||
{{ template "account_window_titlebar" . }}
|
||||
|
||||
<nav class="menu-bar" aria-label="Dashboard toolbar">
|
||||
<div class="menu-item">
|
||||
<button class="menu-button" type="button" aria-expanded="false">File</button>
|
||||
<div class="menu-popup" role="menu">
|
||||
<a class="menu-action" href="/account"><span>R</span><span>Refresh dashboard</span><span class="shortcut">F5</span></a>
|
||||
<div class="menu-separator"></div>
|
||||
<form action="/account/logout" method="post">
|
||||
{{ template "account_csrf_field" . }}
|
||||
<button class="menu-action" type="submit"><span>Q</span><span>Log out</span><span></span></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-item">
|
||||
<button class="menu-button" type="button" aria-expanded="false">View</button>
|
||||
<div class="menu-popup" role="menu">
|
||||
<a class="menu-action" href="#alerts"><span>!</span><span>Go to alerts</span><span></span></a>
|
||||
<a class="menu-action" href="#recent-boxes"><span>B</span><span>Go to recent boxes</span><span></span></a>
|
||||
<a class="menu-action" href="#recent-activity"><span>T</span><span>Go to recent activity</span><span></span></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-item">
|
||||
<button class="menu-button" type="button" aria-expanded="false">Tools</button>
|
||||
<div class="menu-popup" role="menu">
|
||||
<a class="menu-action" href="/account/boxes"><span>B</span><span>Boxes</span><span></span></a>
|
||||
<a class="menu-action" href="/account/alerts"><span>!</span><span>Alerts</span><span></span></a>
|
||||
{{ if .CanManageUsers }}
|
||||
<a class="menu-action" href="/account/users"><span>U</span><span>Users</span><span></span></a>
|
||||
{{ end }}
|
||||
{{ if .CanViewSettings }}
|
||||
<a class="menu-action" href="/account/settings"><span>S</span><span>Settings</span><span></span></a>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="account-body-content">
|
||||
<section class="dashboard-hero raised-panel" aria-labelledby="account-dashboard-title">
|
||||
<div class="hero-copy">
|
||||
<h2 id="account-dashboard-title">Dashboard</h2>
|
||||
<p>Account overview for boxes, alerts, storage, users, and recent activity.</p>
|
||||
</div>
|
||||
<div class="hero-status" aria-label="System summary">
|
||||
{{ range .Statuses }}
|
||||
<div class="hero-status-row"><span>{{ .Label }}</span><strong class="status-{{ .Severity }}">{{ .Value }}</strong></div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="stats-grid" aria-label="Dashboard statistics">
|
||||
<article class="stat-card sunken-panel is-info">
|
||||
<p class="stat-label">Active boxes</p>
|
||||
<p class="stat-value">{{ .Stats.ActiveBoxes }}</p>
|
||||
<p class="stat-note"><span class="stat-note-pill">live filesystem scan</span></p>
|
||||
</article>
|
||||
<article class="stat-card sunken-panel is-info">
|
||||
<p class="stat-label">Storage used</p>
|
||||
<p class="stat-value">{{ .Stats.StorageUsedLabel }}</p>
|
||||
<p class="stat-note"><span class="stat-note-pill">local backend</span></p>
|
||||
</article>
|
||||
<article class="stat-card sunken-panel is-warning">
|
||||
<p class="stat-label">Alerts</p>
|
||||
<p class="stat-value">{{ .Stats.AlertCount }}</p>
|
||||
<p class="stat-note"><span class="stat-note-pill">alert model pending</span></p>
|
||||
</article>
|
||||
{{ if .ShowUsersStat }}
|
||||
<article class="stat-card sunken-panel is-ok">
|
||||
<p class="stat-label">Users</p>
|
||||
<p class="stat-value">{{ .Stats.TotalUsers }}</p>
|
||||
<p class="stat-note"><span class="stat-note-pill">{{ .Stats.ActiveUsers }} active</span><span class="stat-note-pill">{{ .Stats.DisabledUsers }} disabled</span></p>
|
||||
</article>
|
||||
{{ end }}
|
||||
</section>
|
||||
|
||||
<section class="main-grid" aria-label="Dashboard panels">
|
||||
<article id="alerts" class="win98-window section-window">
|
||||
<div class="win98-titlebar">
|
||||
<div class="win98-titlebar-label">
|
||||
<span class="win98-titlebar-icon">!</span>
|
||||
<h2>Alerts Preview</h2>
|
||||
</div>
|
||||
<div class="titlebar-actions">
|
||||
<a class="titlebar-link-button" href="/account/alerts">Show all</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section-body sunken-panel">
|
||||
<div class="scroll-panel alerts-scroll">
|
||||
<div class="alert-list">
|
||||
{{ range .Alerts }}
|
||||
<div class="alert-row">
|
||||
<span class="badge is-{{ .Severity }}">{{ .Severity }}</span>
|
||||
<div>
|
||||
<p class="alert-title">{{ .Title }}</p>
|
||||
<p class="alert-desc">{{ .Detail }}</p>
|
||||
</div>
|
||||
<div class="alert-actions">
|
||||
<a class="tiny-button" href="/account/alerts">Open</a>
|
||||
</div>
|
||||
</div>
|
||||
{{ else }}
|
||||
<div class="alert-row">
|
||||
<span class="badge is-ok">ok</span>
|
||||
<div><p class="alert-title">No alerts</p><p class="alert-desc">Nothing needs attention.</p></div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article id="recent-boxes" class="win98-window section-window">
|
||||
<div class="win98-titlebar">
|
||||
<div class="win98-titlebar-label">
|
||||
<span class="win98-titlebar-icon">B</span>
|
||||
<h2>Recent Boxes</h2>
|
||||
</div>
|
||||
<div class="titlebar-actions">
|
||||
<a class="titlebar-link-button" href="/account/boxes">Show all</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section-body sunken-panel">
|
||||
<div class="scroll-panel boxes-scroll">
|
||||
<table class="account-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Box</th>
|
||||
<th>Files</th>
|
||||
<th>Size</th>
|
||||
<th>Created</th>
|
||||
<th>Expires</th>
|
||||
<th>Flags</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range .RecentBoxes }}
|
||||
<tr>
|
||||
<td>{{ .ID }}</td>
|
||||
<td>{{ .FileCount }}</td>
|
||||
<td>{{ .TotalSizeLabel }}</td>
|
||||
<td>{{ .CreatedAt }}</td>
|
||||
<td>{{ .ExpiresAt }}</td>
|
||||
<td>{{ .Flags }}</td>
|
||||
<td>
|
||||
<div class="box-actions">
|
||||
<a class="tiny-button" href="/box/{{ .ID }}">Open</a>
|
||||
{{ if .CanManage }}
|
||||
<a class="tiny-button" href="/account/boxes/{{ .ID }}">Manage</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{{ else }}
|
||||
<tr><td colspan="7">No boxes found.</td></tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article id="recent-activity" class="win98-window section-window span-2">
|
||||
<div class="win98-titlebar">
|
||||
<div class="win98-titlebar-label">
|
||||
<span class="win98-titlebar-icon">T</span>
|
||||
<h2>Recent Activity</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section-body sunken-panel">
|
||||
<div class="scroll-panel activity-scroll">
|
||||
<div class="activity-list">
|
||||
{{ range .RecentActivity }}
|
||||
<div class="activity-row">
|
||||
<span class="activity-time">{{ .Time }}</span>
|
||||
<div>
|
||||
<p class="activity-title">{{ .Title }}</p>
|
||||
<p class="activity-meta">{{ .Meta }}</p>
|
||||
</div>
|
||||
<span class="tag info">account</span>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<footer class="win98-statusbar" aria-label="Dashboard status">
|
||||
<span>signed in: {{ .AccountNav.Username }}</span>
|
||||
<span>{{ if .AccountNav.IsAdmin }}admin{{ else }}account{{ end }}</span>
|
||||
<span>ready</span>
|
||||
</footer>
|
||||
</main>
|
||||
{{ template "account_shell_end" . }}
|
||||
45
templates/account_login.html
Normal file
45
templates/account_login.html
Normal file
@@ -0,0 +1,45 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ .PageTitle }}</title>
|
||||
{{ template "account_head_assets" . }}
|
||||
</head>
|
||||
<body class="account-body">
|
||||
<div class="app-shell">
|
||||
<div class="app-frame">
|
||||
<main class="account-window" aria-labelledby="account-login-title">
|
||||
<div class="win98-titlebar">
|
||||
<div class="win98-titlebar-label">
|
||||
<span class="win98-titlebar-icon">W</span>
|
||||
<h1 id="account-login-title">WarpBox Account Login</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="account-body-content">
|
||||
{{ if .Error }}
|
||||
<p class="account-error">{{ .Error }}</p>
|
||||
{{ end }}
|
||||
{{ if .AccountLoginEnabled }}
|
||||
<form class="account-form sunken-panel" action="/account/login" method="post">
|
||||
<label class="account-form-row">
|
||||
<span>Username</span>
|
||||
<input name="username" autocomplete="username" required>
|
||||
</label>
|
||||
<label class="account-form-row">
|
||||
<span>Password</span>
|
||||
<input name="password" type="password" autocomplete="current-password" required>
|
||||
</label>
|
||||
<button class="win98-button" type="submit">Login</button>
|
||||
</form>
|
||||
{{ else }}
|
||||
<p class="sunken-panel section-body">Account login is disabled. Set bootstrap admin credentials and restart to enable account access.</p>
|
||||
{{ end }}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
{{ template "account_toast_modal_containers" . }}
|
||||
<script src="/static/js/account-ui.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
110
templates/account_partials.html
Normal file
110
templates/account_partials.html
Normal file
@@ -0,0 +1,110 @@
|
||||
{{ define "account_head_assets" }}
|
||||
<link rel="icon" type="image/png" href="/static/WarpBoxLogo.png">
|
||||
<link rel="stylesheet" href="/static/css/account.css">
|
||||
{{ end }}
|
||||
|
||||
{{ define "account_shell_start" }}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ if .PageTitle }}{{ .PageTitle }}{{ else }}WarpBox Account{{ end }}</title>
|
||||
{{ template "account_head_assets" . }}
|
||||
</head>
|
||||
<body class="account-body">
|
||||
<div class="app-shell">
|
||||
<div class="app-frame">
|
||||
{{ template "account_taskbar" . }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "account_shell_end" }}
|
||||
</div>
|
||||
</div>
|
||||
{{ template "account_toast_modal_containers" . }}
|
||||
<script src="/static/js/account-ui.js"></script>
|
||||
{{ range .PageScripts }}
|
||||
<script src="{{ . }}"></script>
|
||||
{{ end }}
|
||||
</body>
|
||||
</html>
|
||||
{{ end }}
|
||||
|
||||
{{ define "account_taskbar" }}
|
||||
{{ $nav := .AccountNav }}
|
||||
<header class="top-taskbar" aria-label="Account navigation">
|
||||
<a class="start-button" href="/account">
|
||||
<span class="start-logo">W</span>
|
||||
<span>WarpBox</span>
|
||||
</a>
|
||||
|
||||
<nav class="taskbar-nav" aria-label="Primary">
|
||||
<a class="taskbar-button{{ if eq $nav.ActiveSection "dashboard" }} is-active{{ end }}" href="/account">Dashboard</a>
|
||||
{{ if $nav.CanViewBoxes }}
|
||||
<a class="taskbar-button{{ if eq $nav.ActiveSection "boxes" }} is-active{{ end }}" href="/account/boxes">Boxes</a>
|
||||
{{ end }}
|
||||
{{ if $nav.CanViewAlerts }}
|
||||
<a class="taskbar-button{{ if eq $nav.ActiveSection "alerts" }} is-active{{ end }}" href="/account/alerts">Alerts</a>
|
||||
{{ end }}
|
||||
{{ if $nav.CanViewUsers }}
|
||||
<a class="taskbar-button{{ if eq $nav.ActiveSection "users" }} is-active{{ end }}" href="/account/users">Users</a>
|
||||
{{ end }}
|
||||
{{ if $nav.CanViewAPIKeys }}
|
||||
<a class="taskbar-button{{ if eq $nav.ActiveSection "api-keys" }} is-active{{ end }}" href="/account/api-keys">API Keys</a>
|
||||
{{ end }}
|
||||
{{ if $nav.CanViewSettings }}
|
||||
<a class="taskbar-button{{ if eq $nav.ActiveSection "settings" }} is-active{{ end }}" href="/account/settings">Settings</a>
|
||||
{{ end }}
|
||||
</nav>
|
||||
|
||||
<div class="taskbar-session" aria-label="Current session summary">
|
||||
{{ if gt $nav.AlertCount 0 }}
|
||||
<a class="alert-chip is-{{ $nav.AlertSeverity }}" href="/account/alerts">! {{ $nav.AlertCount }} alerts</a>
|
||||
{{ else }}
|
||||
<span class="alert-chip is-ok">0 alerts</span>
|
||||
{{ end }}
|
||||
<span class="session-chip">signed in: {{ $nav.Username }}</span>
|
||||
{{ if $nav.IsAdmin }}
|
||||
<span class="session-chip">admin</span>
|
||||
{{ else }}
|
||||
<span class="session-chip">account</span>
|
||||
{{ end }}
|
||||
<span class="dirty-chip" data-dirty-chip></span>
|
||||
</div>
|
||||
</header>
|
||||
{{ end }}
|
||||
|
||||
{{ define "account_window_titlebar" }}
|
||||
<div class="win98-titlebar">
|
||||
<div class="win98-titlebar-label">
|
||||
<span class="win98-titlebar-icon">{{ if .WindowIcon }}{{ .WindowIcon }}{{ else }}W{{ end }}</span>
|
||||
<h1>{{ if .WindowTitle }}{{ .WindowTitle }}{{ else }}WarpBox Account Control Panel{{ end }}</h1>
|
||||
</div>
|
||||
<div class="win98-window-controls" aria-hidden="true">
|
||||
<button class="win98-control" type="button">_</button>
|
||||
<button class="win98-control" type="button">[]</button>
|
||||
<button class="win98-control" type="button">x</button>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ define "account_csrf_field" }}
|
||||
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
|
||||
{{ end }}
|
||||
|
||||
{{ define "account_toast_modal_containers" }}
|
||||
<div class="toast" id="account-toast" role="status" aria-live="polite"></div>
|
||||
<div class="modal-backdrop" id="account-modal-backdrop" aria-hidden="true"></div>
|
||||
<section class="account-modal win98-window" id="account-modal" role="dialog" aria-modal="true" aria-labelledby="account-modal-title">
|
||||
<div class="win98-titlebar">
|
||||
<div class="win98-titlebar-label">
|
||||
<span class="win98-titlebar-icon">W</span>
|
||||
<h2 id="account-modal-title">WarpBox</h2>
|
||||
</div>
|
||||
<div class="win98-window-controls">
|
||||
<button class="win98-control" type="button" data-modal-close aria-label="Close">x</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body sunken-panel" id="account-modal-body"></div>
|
||||
</section>
|
||||
{{ end }}
|
||||
134
templates/account_settings.html
Normal file
134
templates/account_settings.html
Normal file
@@ -0,0 +1,134 @@
|
||||
{{ template "account_shell_start" . }}
|
||||
<main class="account-window" aria-labelledby="account-settings-title">
|
||||
{{ template "account_window_titlebar" . }}
|
||||
|
||||
<nav class="menu-bar" aria-label="Settings toolbar">
|
||||
<div class="menu-item">
|
||||
<button class="menu-button" type="button" aria-expanded="false">File</button>
|
||||
<div class="menu-popup" role="menu">
|
||||
<a class="menu-action" href="/account/settings"><span>R</span><span>Refresh settings</span><span></span></a>
|
||||
<a class="menu-action" href="/account/settings/export.json"><span>E</span><span>Export JSON</span><span></span></a>
|
||||
<div class="menu-separator"></div>
|
||||
<form action="/account/logout" method="post">
|
||||
{{ template "account_csrf_field" . }}
|
||||
<button class="menu-action" type="submit"><span>Q</span><span>Log out</span><span></span></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-item">
|
||||
<button class="menu-button" type="button" aria-expanded="false">View</button>
|
||||
<div class="menu-popup" role="menu">
|
||||
{{ range .Groups }}
|
||||
<a class="menu-action" href="#settings-{{ .Key }}"><span>S</span><span>{{ .Label }}</span><span></span></a>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<form class="settings-layout account-body-content" action="/account/settings" method="post">
|
||||
{{ template "account_csrf_field" . }}
|
||||
|
||||
<section class="settings-summary raised-panel" aria-label="Settings status">
|
||||
{{ if .Error }}<span class="badge is-danger">{{ .Error }}</span>{{ end }}
|
||||
{{ if .Notice }}<span class="badge is-ok">{{ .Notice }}</span>{{ end }}
|
||||
{{ if .OverridesAllowed }}
|
||||
<span class="badge is-ok">overrides enabled</span>
|
||||
{{ else }}
|
||||
<span class="badge is-warning">read-only: overrides disabled</span>
|
||||
{{ end }}
|
||||
<a class="tiny-button" href="/account/settings/export.json">Export JSON</a>
|
||||
<button class="tiny-button" type="button" data-settings-import-toggle>Import JSON</button>
|
||||
</section>
|
||||
|
||||
<section class="settings-import raised-panel" data-settings-import-panel hidden>
|
||||
<label class="account-form-row">
|
||||
<span>Settings backup JSON</span>
|
||||
<textarea class="account-control" rows="5" data-settings-import-json></textarea>
|
||||
</label>
|
||||
<button class="win98-button" type="button" data-settings-import-submit {{ if not .CanEdit }}disabled{{ end }}>Import</button>
|
||||
</section>
|
||||
|
||||
<div class="settings-scroll scroll-panel" aria-label="Grouped settings">
|
||||
{{ range .Groups }}
|
||||
<section class="settings-group" id="settings-{{ .Key }}">
|
||||
<header class="settings-group-header">
|
||||
<h2>{{ .Label }}</h2>
|
||||
<p>{{ .Description }}</p>
|
||||
</header>
|
||||
<table class="account-table settings-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Setting</th>
|
||||
<th>Description</th>
|
||||
<th>Value</th>
|
||||
<th>Source</th>
|
||||
<th>Reset</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range .Rows }}
|
||||
<tr>
|
||||
<td>
|
||||
<strong>{{ .Label }}</strong>
|
||||
<span class="setting-key">{{ .Key }}</span>
|
||||
</td>
|
||||
<td><p class="setting-description">{{ .Description }}</p></td>
|
||||
<td>
|
||||
{{ if .Editable }}
|
||||
{{ if eq .Type "bool" }}
|
||||
<label class="account-checks"><span><input type="checkbox" name="{{ .Key }}" value="true" {{ if eq .Value "true" }}checked{{ end }}> enabled</span></label>
|
||||
{{ else }}
|
||||
<input class="account-control" name="{{ .Key }}" value="{{ .Value }}" inputmode="numeric">
|
||||
<span class="setting-key">{{ .DisplayValue }}</span>
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
<span>{{ .DisplayValue }}</span>
|
||||
{{ if .LockedReason }}<span class="setting-key">{{ .LockedReason }}</span>{{ end }}
|
||||
{{ end }}
|
||||
</td>
|
||||
<td>
|
||||
<span class="setting-source">
|
||||
<span class="badge is-info">{{ .Source }}</span>
|
||||
<span class="setting-env">{{ .EnvName }}</span>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{{ if .Editable }}
|
||||
<button class="tiny-button" type="submit" form="reset-{{ .Key }}">Reset</button>
|
||||
{{ else }}
|
||||
<span class="badge">locked</span>
|
||||
{{ end }}
|
||||
</td>
|
||||
</tr>
|
||||
{{ else }}
|
||||
<tr><td colspan="5">No settings in this group.</td></tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
<section class="settings-actions raised-panel" aria-label="Settings actions">
|
||||
<button class="win98-button" type="submit" {{ if not .CanEdit }}disabled{{ end }}>Save Settings</button>
|
||||
</section>
|
||||
</form>
|
||||
|
||||
{{ range .Groups }}
|
||||
{{ range .Rows }}
|
||||
{{ if .Editable }}
|
||||
<form id="reset-{{ .Key }}" action="/account/settings/reset" method="post" hidden>
|
||||
{{ template "account_csrf_field" $ }}
|
||||
<input type="hidden" name="key" value="{{ .Key }}">
|
||||
</form>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
<footer class="win98-statusbar" aria-label="Settings status">
|
||||
<span>settings</span>
|
||||
<span>{{ if .CanEdit }}editable{{ else }}read-only{{ end }}</span>
|
||||
<span>ready</span>
|
||||
</footer>
|
||||
</main>
|
||||
{{ template "account_shell_end" . }}
|
||||
Reference in New Issue
Block a user