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:
@@ -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, ¶ms);
|
||||
let result = build_item_list(&conn, ¶ms, &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()
|
||||
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 {
|
||||
format!(" [{}]", tag_names.join(", "))
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
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
|
||||
));
|
||||
// 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));
|
||||
}
|
||||
|
||||
html.push_str("</ul>");
|
||||
// Actions column
|
||||
html.push_str(&format!(
|
||||
"<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("</table>");
|
||||
|
||||
// Add pagination info
|
||||
html.push_str(&format!("<p>Showing {} to {} of {} items</p>",
|
||||
|
||||
Reference in New Issue
Block a user