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))
|
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.
|
/// List all tables in the database.
|
||||||
pub fn list_tables(&self) -> Result<Vec<String>> {
|
pub fn list_tables(&self) -> Result<Vec<String>> {
|
||||||
let conn = self.lock_conn();
|
let conn = self.lock_conn();
|
||||||
|
|||||||
@ -31,6 +31,12 @@ pub struct TableTemplate {
|
|||||||
pub schema: Option<String>,
|
pub schema: Option<String>,
|
||||||
pub row_count: Option<i64>,
|
pub row_count: Option<i64>,
|
||||||
pub columns_info: Option<Vec<(String, String, bool)>>, // (name, type, pk)
|
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 `/`
|
/// Query parameters for `/`
|
||||||
@ -38,6 +44,7 @@ pub struct TableTemplate {
|
|||||||
pub struct TableQuery {
|
pub struct TableQuery {
|
||||||
pub table: Option<String>,
|
pub table: Option<String>,
|
||||||
pub view: Option<String>,
|
pub view: Option<String>,
|
||||||
|
pub page: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// GET /
|
/// GET /
|
||||||
@ -51,47 +58,70 @@ pub async fn index(
|
|||||||
State(app_state): State<AppState>,
|
State(app_state): State<AppState>,
|
||||||
Query(query): Query<TableQuery>,
|
Query(query): Query<TableQuery>,
|
||||||
) -> Html<String> {
|
) -> Html<String> {
|
||||||
// Fetch available tables (for dropdown)
|
// Tables + selection
|
||||||
let tables = app_state.db.list_tables().unwrap();
|
let tables = app_state.db.list_tables().unwrap();
|
||||||
|
|
||||||
// Determine selected table
|
|
||||||
let table_name = query
|
let table_name = query
|
||||||
.table
|
.table
|
||||||
.unwrap_or_else(|| tables.first().cloned().unwrap_or_default());
|
.unwrap_or_else(|| tables.first().cloned().unwrap_or_default());
|
||||||
|
|
||||||
// Determine active view (default = data)
|
|
||||||
let view = query.view.unwrap_or_else(|| "data".to_string());
|
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
|
// Data view
|
||||||
let (columns, rows) = if view == "data" {
|
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 {
|
} else {
|
||||||
(Vec::new(), Vec::new())
|
(Vec::new(), Vec::new())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let end = (offset + rows.len()).min(total_rows as usize);
|
||||||
|
|
||||||
// Structure view
|
// 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.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()),
|
Some(app_state.db.get_table_columns(&table_name).unwrap()),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
(None, None, None)
|
(None, None)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build template context
|
// Template
|
||||||
let template = TableTemplate {
|
let template = TableTemplate {
|
||||||
tables,
|
tables,
|
||||||
table_name,
|
table_name,
|
||||||
view,
|
view,
|
||||||
|
|
||||||
columns,
|
columns,
|
||||||
rows,
|
rows,
|
||||||
|
|
||||||
schema,
|
schema,
|
||||||
row_count,
|
row_count: Some(total_rows), // reuse
|
||||||
|
|
||||||
columns_info,
|
columns_info,
|
||||||
|
|
||||||
|
page,
|
||||||
|
total_pages,
|
||||||
|
total_rows,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Render HTML response
|
|
||||||
Html(template.render().unwrap())
|
Html(template.render().unwrap())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,6 +39,35 @@
|
|||||||
<!-- DATA VIEW -->
|
<!-- DATA VIEW -->
|
||||||
{% if view == "data" %}
|
{% 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">
|
<table border="1" cellspacing="0" cellpadding="4">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user