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 rusqlite::Connection;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use crate::config::ColumnConfig;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct ListQueryParams {
|
pub struct ListQueryParams {
|
||||||
@@ -39,8 +41,9 @@ async fn list_items(
|
|||||||
Query(params): Query<ListQueryParams>,
|
Query(params): Query<ListQueryParams>,
|
||||||
) -> Result<Html<String>, Html<String>> {
|
) -> Result<Html<String>, Html<String>> {
|
||||||
let conn = state.db.lock().await;
|
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 {
|
match result {
|
||||||
Ok(html) => Ok(Html(html)),
|
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
|
let tags: Vec<String> = params.tags
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|t| t.split(',').map(|s| s.trim().to_string()).collect())
|
.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![]
|
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 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 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();
|
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("<h1>Items</h1>");
|
||||||
html.push_str("<p><a href=\"/swagger\">API Documentation</a></p>");
|
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 {
|
for item in page_items {
|
||||||
let item_id = item.id.unwrap_or(0);
|
let item_id = item.id.unwrap_or(0);
|
||||||
let tags_html = if let Some(tags) = tags_map.get(&item_id) {
|
let tags = tags_map.get(&item_id).cloned().unwrap_or_default();
|
||||||
let tag_names: Vec<String> = tags.iter().map(|t| t.name.clone()).collect();
|
let meta: HashMap<String, String> = meta_map.get(&item_id)
|
||||||
if tag_names.is_empty() {
|
.map(|metas| metas.iter().map(|m| (m.name.clone(), m.value.clone())).collect())
|
||||||
String::new()
|
.unwrap_or_default();
|
||||||
} else {
|
|
||||||
format!(" [{}]", tag_names.join(", "))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
String::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
|
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!(
|
html.push_str(&format!(
|
||||||
"<li><a href=\"/item/{}\">Item #{}</a> - {}{} - <a href=\"/api/item/{}/content\">Download</a></li>",
|
"<td><a href=\"/item/{}\">View</a> | <a href=\"/api/item/{}/content\">Download</a></td>",
|
||||||
item_id,
|
item_id, item_id
|
||||||
item_id,
|
|
||||||
item.ts.format("%Y-%m-%d %H:%M:%S"),
|
|
||||||
tags_html,
|
|
||||||
item_id
|
|
||||||
));
|
));
|
||||||
|
|
||||||
|
html.push_str("</tr>");
|
||||||
}
|
}
|
||||||
|
|
||||||
html.push_str("</ul>");
|
html.push_str("</table>");
|
||||||
|
|
||||||
// Add pagination info
|
// Add pagination info
|
||||||
html.push_str(&format!("<p>Showing {} to {} of {} items</p>",
|
html.push_str(&format!("<p>Showing {} to {} of {} items</p>",
|
||||||
|
|||||||
Reference in New Issue
Block a user