feat: implement configurable columns for item list page

Co-authored-by: aider (openai/andrew/openrouter/deepseek/deepseek-chat-v3.1) <aider@aider.chat>
This commit is contained in:
Andrew Phillips
2025-08-28 15:15:38 -03:00
parent 888e6457dd
commit 761542743c

View File

@@ -7,6 +7,8 @@ use axum::{
};
use rusqlite::Connection;
use serde::Deserialize;
use crate::config::ColumnConfig;
use std::collections::HashMap;
#[derive(Deserialize)]
pub struct ListQueryParams {
@@ -39,8 +41,9 @@ async fn list_items(
Query(params): Query<ListQueryParams>,
) -> Result<Html<String>, Html<String>> {
let conn = state.db.lock().await;
let settings = &state.settings;
let result = build_item_list(&conn, &params);
let result = build_item_list(&conn, &params, &settings.list_format);
match result {
Ok(html) => Ok(Html(html)),
@@ -48,7 +51,7 @@ async fn list_items(
}
}
fn build_item_list(conn: &Connection, params: &ListQueryParams) -> Result<String> {
fn build_item_list(conn: &Connection, params: &ListQueryParams, columns: &[ColumnConfig]) -> Result<String> {
let tags: Vec<String> = params.tags
.as_ref()
.map(|t| t.split(',').map(|s| s.trim().to_string()).collect())
@@ -77,40 +80,94 @@ fn build_item_list(conn: &Connection, params: &ListQueryParams) -> Result<String
vec![]
};
// Get tags for all items in the page
// Get tags and meta for all items in the page
let item_ids: Vec<i64> = page_items.iter().filter_map(|item| item.id).collect();
let tags_map = db::get_tags_for_items(conn, &item_ids)?;
let meta_map = db::get_meta_for_items(conn, &item_ids)?;
let mut html = String::new();
html.push_str("<html><head><title>Items</title></head><body>");
html.push_str("<html><head><title>Items</title>");
html.push_str("<style>");
html.push_str("table { border-collapse: collapse; width: 100%; }");
html.push_str("th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }");
html.push_str("th { background-color: #f2f2f2; }");
html.push_str("tr:nth-child(even) { background-color: #f9f9f9; }");
html.push_str("</style>");
html.push_str("</head><body>");
html.push_str("<h1>Items</h1>");
html.push_str("<p><a href=\"/swagger\">API Documentation</a></p>");
html.push_str("<ul>");
// Start table
html.push_str("<table>");
// Table headers
html.push_str("<tr>");
for column in columns {
html.push_str(&format!("<th>{}</th>", column.label));
}
html.push_str("<th>Actions</th>");
html.push_str("</tr>");
// Table rows
for item in page_items {
let item_id = item.id.unwrap_or(0);
let tags_html = if let Some(tags) = tags_map.get(&item_id) {
let tag_names: Vec<String> = tags.iter().map(|t| t.name.clone()).collect();
if tag_names.is_empty() {
String::new()
} else {
format!(" [{}]", tag_names.join(", "))
}
} else {
String::new()
};
let tags = tags_map.get(&item_id).cloned().unwrap_or_default();
let meta: HashMap<String, String> = meta_map.get(&item_id)
.map(|metas| metas.iter().map(|m| (m.name.clone(), m.value.clone())).collect())
.unwrap_or_default();
html.push_str("<tr>");
for column in columns {
let value = match column.name.as_str() {
"id" => item.id.map(|id| id.to_string()).unwrap_or_default(),
"time" => item.ts.format("%Y-%m-%d %H:%M:%S").to_string(),
"size" => item.size.map(|s| s.to_string()).unwrap_or_default(),
"tags" => tags.iter().map(|t| t.name.clone()).collect::<Vec<_>>().join(", "),
_ => {
if column.name.starts_with("meta:") {
let meta_key = &column.name[5..];
meta.get(meta_key).cloned().unwrap_or_default()
} else {
String::new()
}
}
};
// Apply max_len if specified
let display_value = if let Some(max_len_str) = &column.max_len {
if let Ok(max_len) = max_len_str.parse::<usize>() {
if value.chars().count() > max_len {
let truncated: String = value.chars().take(max_len).collect();
format!("{}...", truncated)
} else {
value
}
} else {
value
}
} else {
value
};
// Apply alignment
let align_style = match column.align {
crate::config::ColumnAlignment::Left => "text-align: left;",
crate::config::ColumnAlignment::Right => "text-align: right;",
};
html.push_str(&format!("<td style=\"{}\">{}</td>", align_style, display_value));
}
// Actions column
html.push_str(&format!(
"<li><a href=\"/item/{}\">Item #{}</a> - {}{} - <a href=\"/api/item/{}/content\">Download</a></li>",
item_id,
item_id,
item.ts.format("%Y-%m-%d %H:%M:%S"),
tags_html,
item_id
"<td><a href=\"/item/{}\">View</a> | <a href=\"/api/item/{}/content\">Download</a></td>",
item_id, item_id
));
html.push_str("</tr>");
}
html.push_str("</ul>");
html.push_str("</table>");
// Add pagination info
html.push_str(&format!("<p>Showing {} to {} of {} items</p>",