web routing is POG now

This commit is contained in:
Priec
2026-08-12 23:40:14 +02:00
parent 10c8235420
commit f8e483efa8
28 changed files with 1015 additions and 697 deletions

View File

@@ -1,9 +1,12 @@
//! Reads everything the workspace shows.
//! Reads the context every one of the five pages shows.
//!
//! One loader serves them all, because they all need the same thing: which
//! table is being worked on, and what it currently is. Four calls, in this
//! order — the column-type catalog is the vocabulary the append panel offers,
//! the profile tree names the tables, the profile details describe the
//! selected table's columns and scripts, and the rename history explains how
//! those columns got their names.
//!
//! Four calls, in this order: the column-type catalog is the vocabulary the
//! append panel offers, the profile tree names the profiles and their tables,
//! the profile details describe the selected table's columns and scripts, and
//! the rename history explains how those columns got their names.
//! Nothing here trusts the posted selection — a profile or table that is gone
//! is dropped from the selection rather than reported as an error, because the
//! commonest way to get here with a stale one is having just deleted it.
@@ -13,7 +16,7 @@ use tonic::transport::Channel;
use crate::{
AppState,
auth::{GetAuthorizationRequest, ListGrantableObjectsRequest, ListRolePermissionsRequest, ListRolesRequest},
auth::GetAuthorizationRequest,
definitions::{
common::Empty,
table_definition::{
@@ -26,8 +29,8 @@ use crate::{
};
use super::state::{
DetailColumn, GLOBAL_SCOPE, LoadError, PageInputs, RenameEntry, ScriptView, TableDefinitionPageState,
TableDetailView, TablePermissionAction, TableRolePermissions, TableSummary,
DetailColumn, GLOBAL_SCOPE, LoadError, PageInputs, RenameEntry, ScriptView,
TableDefinitionPageState, TableDetailView, TableSummary,
};
/// Reads the column-type vocabulary on its own.
@@ -92,11 +95,13 @@ pub(crate) async fn load_page(
let profiles = tree
.profiles
.iter()
.map(|profile| profile.name.clone())
.map(|profile| profile.name.as_str())
.collect::<Vec<_>>();
// A profile that no longer exists takes the table selection with it.
if inputs.selection.profile != GLOBAL_SCOPE && !profiles.contains(&inputs.selection.profile) {
if inputs.selection.profile != GLOBAL_SCOPE
&& !profiles.contains(&inputs.selection.profile.as_str())
{
inputs.selection.profile.clear();
inputs.selection.table.clear();
}
@@ -127,7 +132,6 @@ pub(crate) async fn load_page(
format!("{} ({})", dependency.table_name, dependency.column_name)
})
.collect(),
row_display_columns: table.row_display_columns.clone(),
})
.collect::<Vec<_>>()
})
@@ -195,8 +199,6 @@ pub(crate) async fn load_page(
})
.collect(),
row_display_columns: table.row_display_columns,
table_kind: table.table_kind,
name: table.name,
})
}
false => None,
@@ -230,84 +232,8 @@ pub(crate) async fn load_page(
false => Vec::new(),
};
let mut permission_object = String::new();
let mut role_permissions = Vec::new();
if inputs.selection.has_table()
&& crate::authz::can_manage(&authorization, crate::authz::STRUCT_ROLE)
{
let expected_object = crate::authz::table_object(
&inputs.selection.profile,
&inputs.selection.table,
);
let roles = auth
.list_roles(
authenticated_request(headers, ListRolesRequest {})
.map_err(|_| LoadError::Unauthenticated)?,
)
.await
.map_err(|error| LoadError::Backend(error.message().to_string()))?
.into_inner()
.roles;
for role in roles.into_iter().filter(|role| role.kind == "data") {
let grantable = auth
.list_grantable_objects(
authenticated_request(
headers,
ListGrantableObjectsRequest {
target_role: role.name.clone(),
},
)
.map_err(|_| LoadError::Unauthenticated)?,
)
.await
.map_err(|error| LoadError::Backend(error.message().to_string()))?
.into_inner()
.objects
.into_iter()
.find(|object| object.object == expected_object);
let Some(grantable) = grantable else {
continue;
};
permission_object = expected_object.clone();
let permissions = auth
.list_role_permissions(
authenticated_request(
headers,
ListRolePermissionsRequest {
role: role.name.clone(),
},
)
.map_err(|_| LoadError::Unauthenticated)?,
)
.await
.map_err(|error| LoadError::Backend(error.message().to_string()))?
.into_inner();
role_permissions.push(TableRolePermissions {
role: role.name,
actions: grantable
.allowed_actions
.into_iter()
.map(|action| TablePermissionAction {
direct: crate::authz::is_direct_permission(
&permissions.permissions,
&expected_object,
&action,
),
effective: crate::authz::permissions_permit(
&permissions.effective_permissions,
&expected_object,
&action,
),
action,
})
.collect(),
});
}
}
Ok(TableDefinitionPageState {
nav: crate::ui::Nav::new(headers, "admin").with_authorization(&authorization),
profiles,
tables,
detail,
history,
@@ -320,7 +246,8 @@ pub(crate) async fn load_page(
error: inputs.error,
sql: inputs.sql,
generated: inputs.generated,
permission_object,
role_permissions,
// Overwritten by the handler, which is the only thing that knows
// which of the pages it is answering for.
active: "",
})
}