admin/roles: view, archive and restore

This commit is contained in:
Philippe Loctaux 2023-05-07 18:39:02 +02:00
parent 8fa2fb7ddc
commit d778380d8b
10 changed files with 363 additions and 2 deletions

View file

@ -34,6 +34,7 @@ pub enum Page {
AdminUsersNew(AdminUsersNew),
AdminRolesList(AdminRolesList),
AdminRolesNew(AdminRolesNew),
AdminRolesView(AdminRolesView),
}
impl Page {
@ -64,6 +65,7 @@ impl Page {
Page::AdminUsersNew(_) => "pages/admin/users/new",
Page::AdminRolesList(_) => "pages/admin/roles/list",
Page::AdminRolesNew(_) => "pages/admin/roles/new",
Page::AdminRolesView(_) => "pages/admin/roles/view",
}
}
@ -94,6 +96,7 @@ impl Page {
Page::AdminUsersNew(_) => "New user",
Page::AdminRolesList(_) => "Roles",
Page::AdminRolesNew(_) => "New role",
Page::AdminRolesView(_) => "Role info",
}
}
@ -126,6 +129,7 @@ impl Page {
Page::AdminUsersNew(_) => Some(AdminMenu::Users.into()),
Page::AdminRolesList(_) => Some(AdminMenu::Roles.into()),
Page::AdminRolesNew(_) => Some(AdminMenu::Roles.into()),
Page::AdminRolesView(_) => Some(AdminMenu::Roles.into()),
}
}
@ -156,6 +160,7 @@ impl Page {
Page::AdminUsersNew(new) => Box::new(new),
Page::AdminRolesList(list) => Box::new(list),
Page::AdminRolesNew(new) => Box::new(new),
Page::AdminRolesView(view) => Box::new(view),
}
}
}

View file

@ -40,6 +40,8 @@ pub fn routes() -> Vec<Route> {
admin_roles_list,
admin_roles_new,
admin_roles_new_form,
admin_roles_view,
admin_roles_archive,
]
}
@ -143,4 +145,13 @@ pub mod content {
pub struct AdminRolesNew {
pub user: JwtClaims,
}
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
#[derive(Clone)]
pub struct AdminRolesView {
pub user: JwtClaims,
pub role: Role,
pub jwt_duration: i64,
}
}

View file

@ -1,4 +1,5 @@
use crate::routes::prelude::*;
use crate::tokens::JWT_DURATION_MINUTES;
use rocket::{get, post};
use roles::Role;
use std::str::FromStr;
@ -71,9 +72,107 @@ pub async fn admin_roles_new_form(
transaction.commit().await?;
let id = RocketRoleID(name);
Ok(Flash::new(
Redirect::to(uri!(admin_roles_list)),
Redirect::to(uri!(admin_roles_view(id))),
FlashKind::Success,
"Role has been created.",
))
}
#[get("/admin/roles/<id>")]
pub async fn admin_roles_view(
admin: JwtAdmin,
mut db: Connection<Database>,
id: RocketRoleID,
flash: Option<FlashMessage<'_>>,
) -> Result<Template> {
let role_id = id.0;
let mut transaction = db.begin().await?;
let role = Role::get_by_name(&mut transaction, &role_id)
.await?
.ok_or_else(|| Error::not_found(role_id.to_string()))?;
transaction.commit().await?;
let page = Page::AdminRolesView(super::content::AdminRolesView {
user: admin.0,
role,
jwt_duration: JWT_DURATION_MINUTES,
});
Ok(flash
.map(|flash| Page::with_flash(page.clone(), flash))
.unwrap_or_else(|| page.into()))
}
#[derive(Debug, FromForm)]
pub struct ArchiveRoleForm {
pub archive: Option<bool>,
pub restore: Option<bool>,
}
#[post("/admin/roles/<id>/archive", data = "<form>")]
pub async fn admin_roles_archive(
_admin: JwtAdmin,
mut db: Connection<Database>,
id: RocketRoleID,
form: Form<ArchiveRoleForm>,
) -> Result<Flash<Redirect>> {
let (redirect, flash_kind, flash_message) = match (form.archive, form.restore) {
(Some(true), _) => {
// Archive role
let mut transaction = db.begin().await?;
// Get role
let role = Role::get_by_name(&mut transaction, &id.0)
.await?
.ok_or_else(|| Error::not_found("Could not find role"))?;
// Set new status
role.set_archive_status(&mut transaction, true).await?;
transaction.commit().await?;
(
Redirect::to(uri!(admin_roles_list)),
FlashKind::Success,
"Role has been archived.",
)
}
(_, Some(true)) => {
// Restore role
let mut transaction = db.begin().await?;
// Get role
let role = Role::get_by_name(&mut transaction, &id.0)
.await?
.ok_or_else(|| Error::not_found("Could not find role"))?;
// Set new status
role.set_archive_status(&mut transaction, false).await?;
transaction.commit().await?;
(
Redirect::to(uri!(admin_roles_view(id))),
FlashKind::Success,
"Role has been restored.",
)
}
_ => {
// Nothing to do
(
Redirect::to(uri!(admin_roles_view(id))),
FlashKind::Warning,
"Nothing to do.",
)
}
};
Ok(Flash::new(redirect, flash_kind, flash_message))
}

View file

@ -40,7 +40,7 @@
class="form-control"
required>
<small class="form-hint">
This name identifies the role inside the system. It is not public.<br>
This name is for internal usage. It is not public and cannot be changed.<br>
Allowed characters: <code>[a-z][0-9]-</code>
</small>
</div>

View file

@ -0,0 +1,145 @@
{% extends "shell" %}
{% block content %}
<div class="page-header d-print-none">
<div class="container-xl">
<div class="row align-items-center">
<div class="col">
<div class="page-pretitle">
Admin dashboard
</div>
<h2 class="page-title">
Roles
</h2>
</div>
</div>
</div>
</div>
<!-- Page body -->
<div class="page-body">
<div class="container-xl">
{% if flash %}
<div class="alert alert-{{flash.0}}" role="alert">
<h4 class="alert-title">{{ flash.1 | safe }}</h4>
</div>
{% endif %}
{% if role.is_archived %}
<div class="card">
<div class="card-header">
<h3 class="card-title">Archived role</h3>
</div>
<div class="card-body">
<p>This role is archived.</p>
<form action="{{ role.name }}/archive" method="post">
<button type="submit" name="restore" value="true"
class="btn btn-primary">
Restore role
</button>
</form>
</div>
</div>
{% else %}
<div class="card">
<div class="card-header">
<h3 class="card-title">Role information</h3>
</div>
<form action="{{ role.name }}/info" method="post">
<div class="card-body">
<!-- Name -->
<div class="mb-3">
<label class="form-label" for="name">Name</label>
<div>
<input id="name" value="{{ role.name }}" type="text"
class="form-control cursor-not-allowed" readonly>
</div>
</div>
<!-- Label -->
<div class="mb-3">
<label class="form-label required" for="label">Label</label>
<div>
<input name="label" value="{{ role.label }}" id="label" type="text"
placeholder="Enter label"
class="form-control"
required>
<small class="form-hint">
The label is shown to the users.
</small>
</div>
</div>
</div>
<div class="card-footer text-end">
<div class="d-flex">
<a class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#modal-archive">
Archive
</a>
<button type="submit" class="btn btn-primary ms-auto">Save</button>
</div>
</div>
</form>
</div>
{% endif %}
</div>
</div>
<!-- Archive modal -->
<div class="modal modal-blur" tabindex="-1" id="modal-archive">
<div class="modal-dialog modal-sm modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-status bg-danger"></div>
<div class="modal-body text-center py-4">
<div class="text-danger mb-2">
{% include "icons/alert-triangle-large" %}
</div>
<h3>Do you want to archive this role?</h3>
<div class="mt-2">This role will not be able to be used by users.</div>
<div class="mt-2">
This action can take up to {{ jwt_duration }} minutes to be effective for all users.
</div>
</div>
<div class="modal-footer">
<div class="w-100">
<div class="row">
<div class="col">
<a href="#" class="btn w-100" data-bs-dismiss="modal">Cancel</a>
</div>
<div class="col">
<form action="{{ role.name }}/archive" method="post">
<button type="submit" name="archive" value="true"
class="btn btn-danger w-100">
Archive role
</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}
{% block libs_js %}
{% endblock lib_js %}
{% block additional_js %}
{% endblock additional_js %}