web synchronized with the new changes
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
{% block title %}Add table{% endblock %}
|
||||
{% block eyebrow %}Table definition{% endblock %}
|
||||
{% block heading %}Add table{% endblock %}
|
||||
{% block lead %}<p>Build the table one column at a time, the same way the terminal client does.</p>{% endblock %}
|
||||
{% block lead %}<p>Build the table one column at a time. After creation, its table-definition workspace opens so you can assign the initial role permissions.</p>{% endblock %}
|
||||
|
||||
{% block form %}
|
||||
{#
|
||||
|
||||
@@ -12,13 +12,13 @@
|
||||
<p>Browse profiles, tables, and their physical columns.</p>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<a href="/admin/table-definition">Table definition</a>
|
||||
<a href="/admin/tables/new">Add table</a>
|
||||
<a href="/admin/logic/new">Add logic</a>
|
||||
<a href="/admin/validation/new">Add validation</a>
|
||||
<a href="/admin/validation/sets/new">Add rule</a>
|
||||
<a href="/admin/import">Import</a>
|
||||
<a href="/admin/export">Export</a>
|
||||
{% if page.can_manage_tables %}<a href="/admin/table-definition">Table definition</a>{% endif %}
|
||||
{% if page.can_manage_tables %}<a href="/admin/tables/new">Add table</a>{% endif %}
|
||||
{% if page.can_manage_scripts %}<a href="/admin/logic/new">Add logic</a>{% endif %}
|
||||
{% if page.can_manage_validations %}<a href="/admin/validation/new">Add validation</a>{% endif %}
|
||||
{% if page.can_manage_validations %}<a href="/admin/validation/sets/new">Add rule</a>{% endif %}
|
||||
{% if page.can_manage_permissions %}<a href="/admin/permissions">Permissions</a>{% endif %}
|
||||
{% if page.can_export %}<a href="/admin/export">Export</a>{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
<div id="admin-workspace">{% include "pages/admin/admin/workspace.html" %}</div>
|
||||
|
||||
99
web/templates/pages/admin/permissions/permissions.html
Normal file
99
web/templates/pages/admin/permissions/permissions.html
Normal file
@@ -0,0 +1,99 @@
|
||||
{% extends "ui/base.html" %}
|
||||
|
||||
{% block title %}Roles and permissions{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<main>
|
||||
<section class="heading">
|
||||
<div>
|
||||
<p class="eyebrow">Authorization</p>
|
||||
<h1>Roles and permissions</h1>
|
||||
<p>Manage data roles, their direct grants, inheritance, and user assignments.</p>
|
||||
</div>
|
||||
<div class="actions"><a href="/admin">← Admin panel</a></div>
|
||||
</section>
|
||||
|
||||
{% if page.can_manage_roles %}
|
||||
<section class="panel">
|
||||
<h2>Data roles</h2>
|
||||
<form method="get" action="/admin/permissions" class="form-grid">
|
||||
<label>Role
|
||||
<select name="role" onchange="this.form.submit()">
|
||||
{% for role in page.editable_roles() %}
|
||||
<option value="{{ role.name }}" {% if page.selected(role.name.as_str()) %}selected{% endif %}>{{ role.name }}{% if !role.parent.is_empty() %} → {{ role.parent }}{% endif %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
</form>
|
||||
<form hx-post="/admin/permissions/roles" hx-target="#permission-status" class="form-grid">
|
||||
<label>New role<input name="name" required maxlength="50" placeholder="sales"></label>
|
||||
<label>Inherits from
|
||||
<select name="parent"><option value="">No parent</option>{% for role in page.editable_roles() %}<option value="{{ role.name }}">{{ role.name }}</option>{% endfor %}</select>
|
||||
</label>
|
||||
<button type="submit">Create role</button>
|
||||
</form>
|
||||
{% if page.selected_role_is_removable() %}
|
||||
<form hx-post="/admin/permissions/roles/remove" hx-target="#permission-status">
|
||||
<input type="hidden" name="role" value="{{ page.selected_role }}">
|
||||
<button type="submit" class="danger">Remove {{ page.selected_role }}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<h2>Grants for {{ page.selected_role }}</h2>
|
||||
<p class="hint">Direct grants can be revoked here. “Inherited” permissions come from the role’s parent.</p>
|
||||
<div class="table-scroll">
|
||||
<table class="builder-table">
|
||||
<thead><tr><th>Object</th><th>Scope</th><th>Actions</th></tr></thead>
|
||||
<tbody>
|
||||
{% for object in page.grantable_objects %}
|
||||
<tr>
|
||||
<td><code>{{ object.object }}</code></td>
|
||||
<td>{{ object.kind }}{% if !object.profile.is_empty() %} · {{ object.profile }}{% endif %}{% if !object.table.is_empty() %} / {{ object.table }}{% endif %}</td>
|
||||
<td>
|
||||
<div class="actions">
|
||||
{% for action in object.allowed_actions %}
|
||||
{% if page.direct(object.object.as_str(), action.as_str()) %}
|
||||
<form hx-post="/admin/permissions/revoke" hx-target="#permission-status">
|
||||
<input type="hidden" name="role" value="{{ page.selected_role }}"><input type="hidden" name="object" value="{{ object.object }}"><input type="hidden" name="action" value="{{ action }}">
|
||||
<button type="submit" class="danger">{{ action }} ✓</button>
|
||||
</form>
|
||||
{% else if page.effective(object.object.as_str(), action.as_str()) %}
|
||||
<span class="tag">{{ action }} · inherited</span>
|
||||
{% else %}
|
||||
<form hx-post="/admin/permissions/grant" hx-target="#permission-status">
|
||||
<input type="hidden" name="role" value="{{ page.selected_role }}"><input type="hidden" name="object" value="{{ object.object }}"><input type="hidden" name="action" value="{{ action }}">
|
||||
<button type="submit" class="secondary">Grant {{ action }}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
{% if page.can_manage_users %}
|
||||
<section class="panel">
|
||||
<h2>Users</h2>
|
||||
<table class="builder-table">
|
||||
<thead><tr><th>Username</th><th>Email</th><th>Current role</th><th>Assign role</th></tr></thead>
|
||||
<tbody>{% for user in page.users %}<tr>
|
||||
<td>{{ user.username }}</td><td>{{ user.email }}</td><td>{{ user.role }}</td>
|
||||
<td><form hx-post="/admin/permissions/users/role" hx-target="#permission-status" class="actions">
|
||||
<input type="hidden" name="username" value="{{ user.username }}">
|
||||
<select name="role">{% for role in page.assignable_roles() %}<option value="{{ role.name }}" {% if user.role == role.name %}selected{% endif %}>{{ role.name }}</option>{% endfor %}</select>
|
||||
<button type="submit">Assign</button>
|
||||
</form></td>
|
||||
</tr>{% endfor %}</tbody>
|
||||
</table>
|
||||
</section>
|
||||
{% endif %}
|
||||
<div id="permission-status" aria-live="polite"></div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
@@ -151,6 +151,35 @@
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
{% if !page.permission_object.is_empty() %}
|
||||
<section class="panel">
|
||||
<h2>Data permissions for <code>{{ page.selection.table }}</code></h2>
|
||||
<p class="hint">These grants cover this table family. A successful change retires the current session, so you will be asked to sign in again.</p>
|
||||
<table class="builder-table">
|
||||
<thead><tr><th>Role</th><th>Actions</th></tr></thead>
|
||||
<tbody>{% for role in page.role_permissions %}<tr>
|
||||
<td>{{ role.role }}</td>
|
||||
<td><div class="actions">{% for permission in role.actions %}
|
||||
{% if permission.direct %}
|
||||
<form hx-post="/admin/permissions/revoke" hx-target="#permission-status">
|
||||
<input type="hidden" name="role" value="{{ role.role }}"><input type="hidden" name="object" value="{{ page.permission_object }}"><input type="hidden" name="action" value="{{ permission.action }}">
|
||||
<button type="submit" class="danger">{{ permission.action }} ✓</button>
|
||||
</form>
|
||||
{% else if permission.effective %}
|
||||
<span class="tag">{{ permission.action }} · inherited</span>
|
||||
{% else %}
|
||||
<form hx-post="/admin/permissions/grant" hx-target="#permission-status">
|
||||
<input type="hidden" name="role" value="{{ role.role }}"><input type="hidden" name="object" value="{{ page.permission_object }}"><input type="hidden" name="action" value="{{ permission.action }}">
|
||||
<button type="submit" class="secondary">Grant {{ permission.action }}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endfor %}</div></td>
|
||||
</tr>{% endfor %}</tbody>
|
||||
</table>
|
||||
<div id="permission-status" aria-live="polite"></div>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
{% if page.table_is_writable() %}
|
||||
{#
|
||||
Append columns. The panel stages columns without writing anything; the
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
{%- for profile in page.catalog.profiles %}{% for table in profile.tables %}<option value="{{ table }}"></option>{% endfor %}{% endfor -%}
|
||||
</datalist>
|
||||
<div class="form-actions">
|
||||
<a href="/admin">Cancel</a>
|
||||
<a href="/">Cancel</a>
|
||||
<button type="submit">Download CSV</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -12,12 +12,7 @@
|
||||
hx-disabled-elt="button[type=submit]">
|
||||
<div class="form-grid">
|
||||
<label>Profile
|
||||
<select name="profile_name" required>
|
||||
<option value="">Choose a profile</option>
|
||||
{% for profile in page.catalog.profiles %}
|
||||
<option value="{{ profile.name }}" {% if page.form.profile_name == profile.name %}selected{% endif %}>{{ profile.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input name="profile_name" list="import-profiles" value="{{ page.form.profile_name }}" required placeholder="billing">
|
||||
</label>
|
||||
<label>Target tables<input name="table_names" list="import-tables" value="{{ page.form.table_names }}" required placeholder="invoices, customers"></label>
|
||||
<label class="wide">CSV file
|
||||
@@ -26,6 +21,9 @@
|
||||
</label>
|
||||
<label class="wide">CSV data<textarea id="csv-data" name="csv_data" rows="14" required>{{ page.form.csv_data }}</textarea></label>
|
||||
</div>
|
||||
<datalist id="import-profiles">
|
||||
{% for profile in page.catalog.profiles %}<option value="{{ profile.name }}"></option>{% endfor %}
|
||||
</datalist>
|
||||
<datalist id="import-tables">
|
||||
{%- for profile in page.catalog.profiles %}{% for table in profile.tables %}<option value="{{ table }}"></option>{% endfor %}{% endfor -%}
|
||||
</datalist>
|
||||
@@ -33,7 +31,7 @@
|
||||
{%- if let Some(message) = page.error %}{% call alert::error("Could not import CSV", message) %}{% endcall %}{% endif -%}
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<a href="/admin">Cancel</a>
|
||||
<a href="/">Cancel</a>
|
||||
<button type="submit">Import rows</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
20
web/templates/pages/login/initial_password.html
Normal file
20
web/templates/pages/login/initial_password.html
Normal file
@@ -0,0 +1,20 @@
|
||||
{% extends "ui/base.html" %}
|
||||
|
||||
{% block title %}Claim administrator{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<main class="login-main">
|
||||
<form class="login-card" hx-post="/initial-password" hx-target="#initial-password-status" hx-swap="innerHTML" hx-disabled-elt="button" novalidate>
|
||||
<h1>Claim bootstrap administrator</h1>
|
||||
<p class="hint">This works once for a fresh passwordless <code>admin</code> or <code>superadmin</code> account.</p>
|
||||
<label>Account
|
||||
<select name="username"><option value="admin">admin</option><option value="superadmin">superadmin</option></select>
|
||||
</label>
|
||||
<label>Password<input name="password" type="password" autocomplete="new-password" required></label>
|
||||
<label>Confirm password<input name="password_confirmation" type="password" autocomplete="new-password" required></label>
|
||||
<button type="submit">Set initial password</button>
|
||||
<p class="login-alt"><a href="/login">Back to sign in</a></p>
|
||||
<div id="initial-password-status" aria-live="polite"></div>
|
||||
</form>
|
||||
</main>
|
||||
{% endblock %}
|
||||
@@ -8,10 +8,13 @@
|
||||
<form class="login-card" hx-post="/login" hx-target="#login-status" hx-swap="innerHTML"
|
||||
hx-disabled-elt="button" novalidate>
|
||||
<h1>Sign in</h1>
|
||||
{% if permissions_changed %}<p class="notice">Authorization changed successfully. Sign in again because the server retired the previous session.</p>{% endif %}
|
||||
{% if initial_password_set %}<p class="notice">The initial password was set. You can sign in now.</p>{% endif %}
|
||||
<label>Username or email<input name="identifier" autocomplete="username"></label>
|
||||
<label>Password <span>(optional)</span><input name="password" type="password" autocomplete="current-password"></label>
|
||||
<button type="submit">Login</button>
|
||||
<p class="login-alt">No account yet? <a href="/register">Register</a></p>
|
||||
<p class="login-alt">Fresh installation? <a href="/initial-password">Claim a bootstrap administrator</a></p>
|
||||
<div id="login-status" aria-live="polite"></div>
|
||||
</form>
|
||||
</main>
|
||||
|
||||
Reference in New Issue
Block a user