web displays system or user defined columns

This commit is contained in:
Priec
2026-08-12 16:48:56 +02:00
parent bd76efeaac
commit 771109f61c
8 changed files with 85 additions and 4 deletions

View File

@@ -12,6 +12,12 @@ mod schema;
mod services;
mod ui;
mod authz;
// The server's system column vocabulary, read out of `common` the same way the
// generated protos are: this crate compiles that source tree directly instead
// of depending on the crate. Only `is_system_column` is used here.
#[path = "../../common/src/system_column.rs"]
#[allow(dead_code)]
mod system_column;
mod analytics {
include!(concat!(
env!("CARGO_MANIFEST_DIR"),

View File

@@ -122,6 +122,7 @@ pub(crate) async fn load_admin_page(
nullable: column.is_nullable,
primary_key: column.is_primary_key,
quantity_ledger: column.quantity_ledger,
system: crate::system_column::is_system_column(&column.name),
})
.collect()
}

View File

@@ -20,6 +20,20 @@ pub(crate) struct AdminPageState {
pub can_export: bool,
}
impl AdminPageState {
/// The columns the user defined, in the order the backend reported them.
/// The columns pane renders these first.
pub(crate) fn user_columns(&self) -> Vec<&ColumnView> {
self.columns.iter().filter(|column| !column.system).collect()
}
/// The columns the server puts on every managed table. They are hidden
/// everywhere else, so they trail the user's own here.
pub(crate) fn system_columns(&self) -> Vec<&ColumnView> {
self.columns.iter().filter(|column| column.system).collect()
}
}
#[derive(Debug)]
pub(crate) struct ProfileView {
pub name: String,
@@ -40,12 +54,18 @@ pub(crate) struct ColumnView {
pub nullable: bool,
pub primary_key: bool,
pub quantity_ledger: bool,
/// The server named this column, not the user. Ordinary screens never show
/// it, so the admin panel is the only place it is visible.
pub system: bool,
}
impl ColumnView {
/// The badge list rendered under each column name.
pub(crate) fn flags(&self) -> Vec<&'static str> {
let mut flags = Vec::new();
if self.system {
flags.push("system");
}
if self.primary_key {
flags.push("primary key");
}

View File

@@ -80,4 +80,41 @@ mod tests {
assert!(html.contains(route), "missing admin action route {route}");
}
}
#[test]
fn system_columns_render_after_the_user_defined_ones() {
let column = |name: &str, system: bool| crate::pages::admin::admin::state::ColumnView {
name: name.to_string(),
data_type: "TEXT".to_string(),
nullable: true,
primary_key: false,
quantity_ledger: false,
system,
};
let page = AdminPageState {
nav: Nav::default(),
profiles: Vec::new(),
selected_profile: Some("books".to_string()),
tables: Vec::new(),
selected_table: Some("invoice".to_string()),
// The backend reports the physical order, system columns first.
columns: vec![
column("id", true),
column("title", false),
column("created_at", true),
],
can_manage_tables: true,
can_manage_scripts: true,
can_manage_validations: true,
can_export: true,
};
let html = render_workspace(&page);
let title = html.find(">title<").expect("the user column is rendered");
let label = html
.find("System columns")
.expect("the system group is labelled");
let id = html.find(">id<").expect("the system column is rendered");
assert!(title < label && label < id, "system columns must trail");
}
}

View File

@@ -55,6 +55,9 @@
.column { display: grid; grid-template-columns: minmax(100px, 1fr) auto; gap: 3px 12px; padding: 10px 11px; border-bottom: 1px solid #edf0f3; }
.column code { color: #315078; font-size: 12px; }
.column small { grid-column: 1 / -1; }
.column.system { color: #6b7686; background: #fafbfc; }
.column.system code { color: #67788e; }
.group-label { margin: 14px 0 4px; padding: 0 11px; color: #7c8796; font-size: 11px; letter-spacing: .04em; text-transform: uppercase; }
.empty { margin: 8px; color: #7c8796; }
/* ---------- Forms (components/form_card.html) ---------- */

View File

@@ -49,20 +49,34 @@
</section>
<section class="pane">
<div class="pane-title"><h2>Columns</h2><span>{{ page.columns.len() }}</span></div>
<div class="pane-title">
<h2>Columns</h2>
<span>{{ page.user_columns().len() }} user · {{ page.system_columns().len() }} system</span>
</div>
<div class="pane-list">
{% if page.selected_table.is_none() %}
<p class="empty">Select a table to inspect its columns.</p>
{% else if page.columns.is_empty() %}
<p class="empty">This table has no visible columns.</p>
{% else %}
{% for column in page.columns %}
{% for column in page.user_columns() %}
<div class="column">
<span>{{ column.name }}</span>
<code>{{ column.data_type }}</code>
<small>{{ column.flags()|join(" · ") }}</small>
</div>
{% endfor %}
{% let system_columns = page.system_columns() %}
{% if !system_columns.is_empty() %}
<p class="group-label">System columns · hidden from users</p>
{% for column in system_columns %}
<div class="column system">
<span>{{ column.name }}</span>
<code>{{ column.data_type }}</code>
<small>{{ column.flags()|join(" · ") }}</small>
</div>
{% endfor %}
{% endif %}
{% endif %}
</div>
</section>