mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 19:26:48 +00:00
The Users, Groups and Removed VMs pages were bare tables: rows of coloured icon buttons without words, five columns of small checks for a user's role and permissions, dates broken over three lines and a lone "+" for a new entry. The owner chose a directory layout from three rendered options; it follows the host pages' cards: - Each list is a card with its count and search in the header and one list-group item per entry, which stacks on a phone without scrolling. - A user shows their role as a badge (Superuser, Template editor) or "User", Blocked as a red badge, their groups, a granted clone permission and live API tokens, and the last login on one line; role and status are searchable. Edit is the visible action, the menu holds View profile, Block or Unblock and Delete; Block and Delete are disabled on one's own account, which the server refuses anyway. - A group shows its member and permission counts; its name opens it. The page's title said "Users". - A removed VM shows its UUID, the host it left, its owners and when the record was made and expires. - Logs keep their table in a card, the server search in its header, the count of matching rows, Done and Failed as badges, and a message column wide enough to read on a phone. The user form was one long column of horizontal fields. The owner rejected three boxed layouts and chose one led by what the user may do: the Active switch, Template editor and Superuser as two cards, groups as chips, the user's own permissions as switches each saying what it allows, then the name and password and the quota folded into details whose summaries tell their values (-1 as "any"); they open on a new user and on an error inside them. A refused group or permission is said beside its field. The group form follows it: the name, what its members may do, its members as chips. Cancel goes back to the list instead of the browser history.
141 lines
8.1 KiB
HTML
141 lines
8.1 KiB
HTML
{% extends "base.html" %}
|
|
{% load django_bootstrap5 %}
|
|
{% load bootstrap_icons %}
|
|
{% load i18n %}
|
|
|
|
{% block title %}{{ title }}{% endblock %}
|
|
|
|
{% block page_heading %}{{ title }}{% endblock page_heading %}
|
|
|
|
{% block content %}
|
|
{# what the user may do comes first; who they are and their quota fold away #}
|
|
{% if edited_user %}
|
|
<div class="border-bottom pb-3 mb-3">
|
|
<span class="fs-5 fw-semibold text-break">{{ edited_user.username }}</span>
|
|
{% if edited_user.email %}<span class="text-body-secondary ms-2 text-break">{{ edited_user.email }}</span>{% endif %}
|
|
<div class="small text-body-secondary">{% trans "Last login" %} {{ edited_user.last_login|date:"Y-m-d H:i"|default:_("never") }}</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<form id="create-update" method="post">
|
|
{% csrf_token %}
|
|
{% bootstrap_form_errors user_form type="non_fields" %}
|
|
|
|
<div class="form-check form-switch mb-4">
|
|
<input id="{{ user_form.is_active.id_for_label }}" class="form-check-input" type="checkbox" role="switch" name="is_active"
|
|
aria-describedby="active-help"{% if user_form.is_active.value %} checked{% endif %}>
|
|
<label class="form-check-label fw-semibold" for="{{ user_form.is_active.id_for_label }}">{% trans "Active: may sign in" %}</label>
|
|
<div id="active-help" class="small text-body-secondary">{% trans "Turn off to block the user while keeping the account." %}</div>
|
|
</div>
|
|
|
|
<h4 class="h5 mb-1">
|
|
{% if edited_user %}{% blocktrans with name=edited_user.username %}What can {{ name }} do?{% endblocktrans %}{% else %}{% trans "What can the user do?" %}{% endif %}
|
|
</h4>
|
|
<p class="small text-body-secondary mb-3">{% trans "The permissions of the user's groups apply too." %}</p>
|
|
|
|
<div class="row row-cols-1 row-cols-md-2 g-2 mb-3">
|
|
<div class="col">
|
|
<label class="border rounded p-3 h-100 d-flex gap-2">
|
|
<input class="form-check-input flex-shrink-0 mt-1" type="checkbox" name="is_staff"{% if user_form.is_staff.value %} checked{% endif %}>
|
|
<span><span class="d-block fw-semibold">{% trans "Template editor" %}</span>
|
|
<span class="small text-body-secondary">{% trans "May change and delete the template VMs given to them." %}</span></span>
|
|
</label>
|
|
</div>
|
|
<div class="col">
|
|
<label class="border rounded p-3 h-100 d-flex gap-2">
|
|
<input class="form-check-input flex-shrink-0 mt-1" type="checkbox" name="is_superuser"{% if user_form.is_superuser.value %} checked{% endif %}>
|
|
<span><span class="d-block fw-semibold">{% trans "Superuser" %}</span>
|
|
<span class="small text-body-secondary">{% trans "Has every permission and manages the panel, whatever is chosen below." %}</span></span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<fieldset class="mb-3">
|
|
<legend class="fs-6 fw-semibold">{% trans "Groups" %}</legend>
|
|
<div class="d-flex flex-wrap gap-2">
|
|
{% for box in user_form.groups %}
|
|
<label class="border rounded px-2 py-1 text-break">
|
|
<input id="{{ box.id_for_label }}" class="form-check-input me-1" type="checkbox" name="groups" value="{{ box.data.value }}"{% if box.data.selected %} checked{% endif %}>
|
|
{{ box.choice_label }}
|
|
</label>
|
|
{% empty %}
|
|
<span class="small text-body-secondary">{% trans "There are no groups yet." %}</span>
|
|
{% endfor %}
|
|
</div>
|
|
{% for error in user_form.groups.errors %}<div class="invalid-feedback d-block">{{ error }}</div>{% endfor %}
|
|
</fieldset>
|
|
|
|
<fieldset class="mb-3">
|
|
<legend class="fs-6 fw-semibold">{% trans "Own permissions" %}</legend>
|
|
<p class="small text-body-secondary mb-2">{% trans "Off grants nothing here; it takes away nothing a group grants." %}</p>
|
|
<div class="border-top">
|
|
{% for box, help in user_form.permission_rows %}
|
|
<div class="form-check form-switch border-bottom py-2 ps-5">
|
|
<input id="{{ box.id_for_label }}" class="form-check-input" type="checkbox" role="switch" name="user_permissions"
|
|
value="{{ box.data.value }}"{% if box.data.selected %} checked{% endif %}>
|
|
<label class="form-check-label" for="{{ box.id_for_label }}">
|
|
<span class="d-block">{{ box.data.value.instance.name }}</span>
|
|
<span class="small text-body-secondary">{{ help }}</span>
|
|
</label>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% for error in user_form.user_permissions.errors %}<div class="invalid-feedback d-block">{{ error }}</div>{% endfor %}
|
|
</fieldset>
|
|
|
|
<details class="border-bottom py-3"{% if not edited_user or user_form.identity_has_errors %} open{% endif %}>
|
|
<summary>
|
|
<span class="fw-semibold">{% trans "Name and password" %}</span>
|
|
{% if edited_user %}
|
|
<span class="d-block small text-body-secondary ms-3 text-break">
|
|
{{ edited_user.username }}{% if edited_user.email %} · {{ edited_user.email }}{% endif %} ·
|
|
{% if edited_user.has_usable_password %}{% trans "password set" %}{% else %}{% trans "no password" %}{% endif %}
|
|
</span>
|
|
{% endif %}
|
|
</summary>
|
|
<div class="pt-3">
|
|
{% bootstrap_field user_form.username %}
|
|
{% if user_form.password %}{% bootstrap_field user_form.password %}{% endif %}
|
|
<div class="row g-2">
|
|
<div class="col-sm-6">{% bootstrap_field user_form.first_name %}</div>
|
|
<div class="col-sm-6">{% bootstrap_field user_form.last_name %}</div>
|
|
</div>
|
|
{% bootstrap_field user_form.email %}
|
|
{% if edited_user %}
|
|
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2">
|
|
<span>{% trans "Password" %}: {% if edited_user.has_usable_password %}{% trans "Set" %}{% else %}{% trans "Not set" %}{% endif %}</span>
|
|
<a class="btn btn-sm btn-outline-secondary" href="{% url 'admin:user_update_password' edited_user.id %}">{% trans "Change password" %}</a>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</details>
|
|
|
|
{% with quota=attributes_form.instance %}
|
|
<details class="border-bottom py-3"{% if attributes_form.errors %} open{% endif %}>
|
|
<summary>
|
|
<span class="fw-semibold">{% trans "Quota" %}</span>
|
|
<span class="d-block small text-body-secondary ms-3">
|
|
{% if quota.max_instances == -1 %}{% trans "any number of VMs" %}{% else %}{% blocktrans count count=quota.max_instances %}{{ count }} VM{% plural %}{{ count }} VMs{% endblocktrans %}{% endif %} ·
|
|
{% if quota.max_cpus == -1 %}{% trans "any vCPUs" %}{% else %}{% blocktrans count count=quota.max_cpus %}{{ count }} vCPU{% plural %}{{ count }} vCPUs{% endblocktrans %}{% endif %} ·
|
|
{% if quota.max_memory == -1 %}{% trans "any memory" %}{% else %}{{ quota.max_memory }} MiB{% endif %} ·
|
|
{% if quota.max_disk_size == -1 %}{% trans "any disk size" %}{% else %}{{ quota.max_disk_size }} GiB{% endif %}
|
|
</span>
|
|
</summary>
|
|
<div class="pt-3">
|
|
<p class="small text-body-secondary">{% trans "For each limit: -1 means no limit, 0 none allowed." %}</p>
|
|
<div class="row row-cols-1 row-cols-sm-2 g-2">
|
|
<div class="col">{% bootstrap_field attributes_form.max_instances show_help=False %}</div>
|
|
<div class="col">{% bootstrap_field attributes_form.max_cpus show_help=False %}</div>
|
|
<div class="col">{% bootstrap_field attributes_form.max_memory show_help=False %}</div>
|
|
<div class="col">{% bootstrap_field attributes_form.max_disk_size show_help=False %}</div>
|
|
</div>
|
|
</div>
|
|
</details>
|
|
{% endwith %}
|
|
|
|
<div class="d-flex justify-content-end gap-2 mt-3">
|
|
<a class="btn btn-outline-secondary" href="{% url 'admin:user_list' %}">{% trans "Cancel" %}</a>
|
|
<button type="submit" class="btn btn-primary">{% trans "Save" %}</button>
|
|
</div>
|
|
</form>
|
|
{% endblock content %}
|