Pagination
This commit is contained in:
parent
d0e95733ec
commit
e505b04dc0
45
src/db.rs
45
src/db.rs
@ -78,6 +78,51 @@ impl SqliteDb {
|
||||
Ok((column_names, rows))
|
||||
}
|
||||
|
||||
pub fn fetch_table_page(
|
||||
&self,
|
||||
table_name: &str,
|
||||
limit: usize,
|
||||
offset: usize,
|
||||
) -> Result<(Vec<String>, Vec<Vec<String>>)> {
|
||||
let conn = self.lock_conn();
|
||||
|
||||
let query = format!("SELECT * FROM {table_name} LIMIT {limit} OFFSET {offset}");
|
||||
|
||||
let mut stmt = conn.prepare(&query)?;
|
||||
|
||||
let column_names = stmt
|
||||
.column_names()
|
||||
.iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let col_count = stmt.column_count();
|
||||
|
||||
let rows_iter = stmt.query_map([], move |row| {
|
||||
let mut values = Vec::with_capacity(col_count);
|
||||
|
||||
for i in 0..col_count {
|
||||
let value: Value = row.get(i)?;
|
||||
|
||||
let cell = match value {
|
||||
Value::Null => "<null>".to_string(),
|
||||
Value::Integer(i) => i.to_string(),
|
||||
Value::Real(f) => f.to_string(),
|
||||
Value::Text(t) => t,
|
||||
Value::Blob(b) => format!("<blob {} bytes>", b.len()),
|
||||
};
|
||||
|
||||
values.push(cell);
|
||||
}
|
||||
|
||||
Ok(values)
|
||||
})?;
|
||||
|
||||
let rows = rows_iter.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
Ok((column_names, rows))
|
||||
}
|
||||
|
||||
/// List all tables in the database.
|
||||
pub fn list_tables(&self) -> Result<Vec<String>> {
|
||||
let conn = self.lock_conn();
|
||||
|
||||
@ -31,6 +31,12 @@ pub struct TableTemplate {
|
||||
pub schema: Option<String>,
|
||||
pub row_count: Option<i64>,
|
||||
pub columns_info: Option<Vec<(String, String, bool)>>, // (name, type, pk)
|
||||
|
||||
pub page: usize,
|
||||
pub total_pages: usize,
|
||||
pub total_rows: i64,
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
}
|
||||
|
||||
/// Query parameters for `/`
|
||||
@ -38,6 +44,7 @@ pub struct TableTemplate {
|
||||
pub struct TableQuery {
|
||||
pub table: Option<String>,
|
||||
pub view: Option<String>,
|
||||
pub page: Option<usize>,
|
||||
}
|
||||
|
||||
/// GET /
|
||||
@ -51,47 +58,70 @@ pub async fn index(
|
||||
State(app_state): State<AppState>,
|
||||
Query(query): Query<TableQuery>,
|
||||
) -> Html<String> {
|
||||
// Fetch available tables (for dropdown)
|
||||
// Tables + selection
|
||||
let tables = app_state.db.list_tables().unwrap();
|
||||
|
||||
// Determine selected table
|
||||
let table_name = query
|
||||
.table
|
||||
.unwrap_or_else(|| tables.first().cloned().unwrap_or_default());
|
||||
|
||||
// Determine active view (default = data)
|
||||
let view = query.view.unwrap_or_else(|| "data".to_string());
|
||||
|
||||
// Pagination
|
||||
let page_size: usize = 20;
|
||||
|
||||
let page = query.page.unwrap_or(1).max(1);
|
||||
let offset = (page - 1) * page_size;
|
||||
|
||||
// Always needed for pagination
|
||||
let total_rows = app_state.db.count_rows(&table_name).unwrap();
|
||||
|
||||
let total_pages = ((total_rows as usize) + page_size - 1) / page_size;
|
||||
|
||||
let start = if total_rows == 0 { 0 } else { offset + 1 };
|
||||
|
||||
// Data view
|
||||
let (columns, rows) = if view == "data" {
|
||||
app_state.db.fetch_table(&table_name).unwrap()
|
||||
app_state
|
||||
.db
|
||||
.fetch_table_page(&table_name, page_size, offset)
|
||||
.unwrap()
|
||||
} else {
|
||||
(Vec::new(), Vec::new())
|
||||
};
|
||||
|
||||
let end = (offset + rows.len()).min(total_rows as usize);
|
||||
|
||||
// Structure view
|
||||
let (schema, row_count, columns_info) = if view == "structure" {
|
||||
let (schema, columns_info) = if view == "structure" {
|
||||
(
|
||||
Some(app_state.db.get_table_schema(&table_name).unwrap()),
|
||||
Some(app_state.db.count_rows(&table_name).unwrap()),
|
||||
Some(app_state.db.get_table_columns(&table_name).unwrap()),
|
||||
)
|
||||
} else {
|
||||
(None, None, None)
|
||||
(None, None)
|
||||
};
|
||||
|
||||
// Build template context
|
||||
// Template
|
||||
let template = TableTemplate {
|
||||
tables,
|
||||
table_name,
|
||||
view,
|
||||
|
||||
columns,
|
||||
rows,
|
||||
|
||||
schema,
|
||||
row_count,
|
||||
row_count: Some(total_rows), // reuse
|
||||
|
||||
columns_info,
|
||||
|
||||
page,
|
||||
total_pages,
|
||||
total_rows,
|
||||
start,
|
||||
end,
|
||||
};
|
||||
|
||||
// Render HTML response
|
||||
Html(template.render().unwrap())
|
||||
}
|
||||
|
||||
@ -39,6 +39,35 @@
|
||||
<!-- DATA VIEW -->
|
||||
{% if view == "data" %}
|
||||
|
||||
|
||||
<p>
|
||||
Showing {{ start }}-{{ end }} of {{ total_rows }}
|
||||
|
||||
|
||||
|
||||
Page {{ page }} of {{ total_pages }}
|
||||
|
||||
|
||||
|
||||
{% if page > 1 %}
|
||||
<a href="/?table={{ table_name }}&view=data&page={{ page - 1 }}"><</a>
|
||||
{% endif %}
|
||||
|
||||
{% if page < total_pages %}
|
||||
<a href="/?table={{ table_name }}&view=data&page={{ page + 1 }}">></a>
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
<form method="get" style="display:inline;">
|
||||
<input type="hidden" name="table" value="{{ table_name }}">
|
||||
<input type="hidden" name="view" value="data">
|
||||
|
||||
Page:
|
||||
<input type="number" name="page" min="1" max="{{ total_pages }}" value="{{ page }}">
|
||||
<button type="submit">Go</button>
|
||||
</form>
|
||||
|
||||
|
||||
<table border="1" cellspacing="0" cellpadding="4">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user