improve navigation within workspaces

This commit is contained in:
Brent Schroeter 2025-11-13 02:59:00 +00:00
parent cf4c07f5b8
commit ab5f778cc4
10 changed files with 241 additions and 43 deletions

View file

@ -13,7 +13,7 @@ use uuid::Uuid;
use validator::Validate;
use crate::{
app::{App, AppDbConn},
app::App,
errors::AppError,
extractors::ValidatedForm,
navigator::{Navigator, NavigatorPage as _},

View file

@ -16,6 +16,8 @@ mod add_service_credential_handler;
mod add_table_handler;
mod nav_handler;
mod service_credentials_handler;
mod settings_handler;
mod update_name_handler;
mod update_service_cred_permissions_handler;
#[derive(Clone, Debug, Deserialize)]
@ -37,6 +39,11 @@ pub(super) fn new_router() -> Router<App> {
},
),
)
.route_with_tsr("/{workspace_id}/settings/", get(settings_handler::get))
.route(
"/{workspace_id}/settings/update-name",
post(update_name_handler::post),
)
.route(
"/{workspace_id}/add-service-credential",
post(add_service_credential_handler::post),

View file

@ -0,0 +1,68 @@
use askama::Template;
use axum::{
debug_handler,
extract::{Path, State},
response::{Html, IntoResponse},
};
use interim_models::workspace::Workspace;
use serde::Deserialize;
use uuid::Uuid;
use crate::{
app::{App, AppDbConn},
errors::AppError,
navigator::Navigator,
settings::Settings,
user::CurrentUser,
workspace_nav::WorkspaceNav,
workspace_pooler::{RoleAssignment, WorkspacePooler},
};
#[derive(Debug, Deserialize)]
pub(super) struct PathParams {
workspace_id: Uuid,
}
/// HTTP GET handler for workspace settings, including renaming, access control,
/// and deletion.
#[debug_handler(state = App)]
pub(super) async fn get(
State(settings): State<Settings>,
CurrentUser(user): CurrentUser,
AppDbConn(mut app_db): AppDbConn,
Path(PathParams { workspace_id }): Path<PathParams>,
navigator: Navigator,
State(mut pooler): State<WorkspacePooler>,
) -> Result<impl IntoResponse, AppError> {
// FIXME: Check workspace authorization.
// permission to access/alter both as needed.
let workspace = Workspace::with_id(workspace_id)
.fetch_one(&mut app_db)
.await?;
let mut workspace_client = pooler
.acquire_for(workspace.id, RoleAssignment::User(user.id))
.await?;
#[derive(Debug, Template)]
#[template(path = "workspaces_single/settings.html")]
struct ResponseTemplate {
settings: Settings,
workspace: Workspace,
workspace_nav: WorkspaceNav,
}
Ok(Html(
ResponseTemplate {
workspace_nav: WorkspaceNav::builder()
.navigator(navigator)
.workspace(workspace.clone())
.populate_rels(&mut app_db, &mut workspace_client)
.await?
.build()?,
workspace,
settings,
}
.render()?,
))
}

View file

@ -0,0 +1,50 @@
use axum::{debug_handler, extract::Path, response::Response};
use serde::Deserialize;
use sqlx::query;
use uuid::Uuid;
use validator::Validate;
use crate::{
app::{App, AppDbConn},
errors::AppError,
extractors::ValidatedForm,
navigator::{Navigator, NavigatorPage as _},
user::CurrentUser,
};
#[derive(Debug, Deserialize)]
pub(super) struct PathParams {
workspace_id: Uuid,
}
#[derive(Debug, Deserialize, Validate)]
pub(super) struct FormBody {
name: String,
}
/// HTTP POST handler for updating a workspace's name.
#[debug_handler(state = App)]
pub(super) async fn post(
AppDbConn(mut app_db): AppDbConn,
CurrentUser(_user): CurrentUser,
navigator: Navigator,
Path(PathParams { workspace_id }): Path<PathParams>,
ValidatedForm(FormBody { name }): ValidatedForm<FormBody>,
) -> Result<Response, AppError> {
// FIXME: Check workspace authorization.
query!(
"update workspaces set display_name = $1 where id = $2",
name,
workspace_id
)
.execute(app_db.get_conn())
.await?;
Ok(navigator
.workspace_page()
.workspace_id(workspace_id)
.suffix("settings/")
.build()?
.redirect_to())
}

View file

@ -5,10 +5,8 @@
<div class="page-grid">
<div class="page-grid__toolbar">
<div class="page-grid__toolbar-utilities">
<a href="settings">
<button class="button--secondary" type="button">
<a class="button--secondary" href="settings" role="button">
Portal Settings
</button>
</a>
<filter-menu
identifier-hints="{{ attr_names | json }}"

View file

@ -3,17 +3,22 @@
{% block main %}
<div class="page-grid">
<div class="page-grid__toolbar">
<a href="{{ navigator.portal_page()
<div class="page-grid__toolbar-utilities">
<a
class="button--secondary"
href="{{ navigator.portal_page()
.workspace_id(*portal.workspace_id)
.rel_oid(*portal.class_oid)
.portal_id(*portal.id)
.build()?
.get_path() }}">
<button class="button--secondary" style="margin-left: 0.5rem;" type="button">
.get_path() }}"
role="button"
>
Back
</button>
</a>
</div>
{% include "toolbar_user.html" %}
</div>
<div class="page-grid__sidebar">
<div style="padding: 1rem;">
{{ workspace_nav | safe }}
@ -22,7 +27,7 @@
<main class="page-grid__main padded--lg">
<form method="post" action="update-name">
<section>
<h1>Name</h1>
<h1>Portal Name</h1>
<input type="text" name="name" value="{{ portal.name }}">
<button class="button--primary" type="submit">Save</button>
</section>

View file

@ -3,16 +3,17 @@
{% block main %}
<div class="page-grid">
<div class="page-grid__toolbar">
{% include "toolbar_user.html" %}
</div>
<div class="page-grid__sidebar">
<div style="padding: 1rem;">
{{ workspace_nav | safe }}
</div>
</div>
<main class="page-grid__main">
<main class="page-grid__main padded--lg">
<form method="post" action="update-name">
<section>
<h1>Name</h1>
<h1>Table Name</h1>
<input type="text" name="name" value="{{ rel.relname }}">
<button class="button--primary" type="submit">Save</button>
</section>

View file

@ -8,14 +8,22 @@
{% endif %}
</h1>
<basic-dropdown button-class="button--secondary button--small" button-aria-label="Workspace Menu">
<span slot="button-contents"><i class="ti ti-dots" aria-hidden="true"></i></span>
<span slot="button-contents"><i class="ti ti-dots-vertical" aria-hidden="true"></i></span>
<menu slot="popover" class="basic-dropdown__menu">
<li>
<a
href="{{ navigator.get_root_path() }}/w/{{ workspace.id.simple() }}/service-credentials"
role="button"
>
PostgreSQL Credentials
PostgreSQL credentials
</a>
</li>
<li>
<a
href="{{ navigator.get_root_path() }}/w/{{ workspace.id.simple() }}/settings"
role="button"
>
Workspace settings
</a>
</li>
<li>
@ -23,7 +31,7 @@
href="{{ navigator.get_root_path() }}/"
role="button"
>
All Workspaces
All workspaces
</a>
</li>
</menu>
@ -39,7 +47,9 @@
method="post"
>
<!-- FIXME: CSRF -->
<button class="workspace-nav__aux-button" type="submit">+</button>
<button aria-label="Add table" class="button--secondary button--small" type="submit">
<i class="ti ti-database-plus"></i>
</button>
</form>
</div>
<menu class="workspace-nav__menu">
@ -57,16 +67,22 @@
>
<div class="workspace-nav__heading" slot="summary">
<h3>{{ rel.name }}</h3>
<basic-dropdown button-class="button--secondary button--small" button-aria-label="Table menu">
<span slot="button-contents"><i class="ti ti-dots-vertical" aria-hidden="true"></i></span>
<menu slot="popover" class="basic-dropdown__menu">
<li>
<a
href="{{ navigator.get_root_path() -}}
/w/{{ workspace.id.simple() -}}
/r/{{ rel.oid.0 -}}
/settings/"
class="workspace-nav__aux-button"
role="button"
>
Settings
Table settings
</a>
</li>
</menu>
</basic-dropdown>
</div>
<menu class="workspace-nav__menu" slot="content">
<li class="workspace-nav__menu-item">
@ -81,7 +97,9 @@
method="post"
>
<!-- FIXME: CSRF -->
<button class="workspace-nav__aux-button" type="submit">+</button>
<button aria-label="Add portal" class="workspace-nav__aux-button" type="submit">
<i class="ti ti-table-plus"></i>
</button>
</form>
</div>
<menu slot="content" class="workspace-nav__menu">
@ -103,17 +121,35 @@
>
{{ portal.name }}
</a>
<basic-dropdown button-class="button--secondary button--small" button-aria-label="Portal menu">
<span slot="button-contents"><i class="ti ti-dots-vertical" aria-hidden="true"></i></span>
<menu slot="popover" class="basic-dropdown__menu">
<li>
<a
href="{{ navigator.get_root_path() -}}
/w/{{ workspace.id.simple() -}}
/r/{{ rel.oid.0 -}}
/p/{{ portal.id.simple() -}}
/settings/"
role="button"
>
Portal settings
</a>
</li>
<li>
<a
href="{{ navigator.get_root_path() -}}
/w/{{ workspace.id.simple() -}}
/r/{{ rel.oid.0 -}}
/p/{{ portal.id.simple() -}}
/form/"
class="workspace-nav__aux-button"
role="button"
>
Form
</a>
</li>
</menu>
</basic-dropdown>
</div>
</li>
{% endfor %}

View file

@ -0,0 +1,29 @@
{% extends "base.html" %}
{% block main %}
<div class="page-grid">
<div class="page-grid__toolbar">
{% include "toolbar_user.html" %}
</div>
<div class="page-grid__sidebar">
<div style="padding: 1rem;">
{{ workspace_nav | safe }}
</div>
</div>
<main class="page-grid__main padded--lg">
<form method="post" action="update-name">
<section>
<h1>Workspace Name</h1>
<input type="text" name="name" value="{{ workspace.display_name }}">
<button class="button--primary" type="submit">Save</button>
</section>
</form>
<form method="post" action="">
<section>
<h1>Sharing</h1>
<button class="button--primary" type="submit">Save</button>
</section>
</form>
</main>
</div>
{% endblock %}

View file

@ -58,18 +58,22 @@ button, input[type="submit"] {
.button {
&--primary {
@include globals.reset-anchor;
@include globals.button-primary;
}
&--secondary {
@include globals.reset-anchor;
@include globals.button-secondary;
}
&--clear {
@include globals.reset-anchor;
@include globals.button-clear;
}
&--small {
@include globals.reset-anchor;
@include globals.button-small;
}
}