Pagination

This commit is contained in:
Candifloss 2026-04-24 16:34:11 +05:30
parent d0e95733ec
commit e505b04dc0
3 changed files with 114 additions and 10 deletions

View File

@ -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();

View File

@ -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())
} }

View File

@ -39,6 +39,35 @@
<!-- DATA VIEW --> <!-- DATA VIEW -->
{% if view == "data" %} {% if view == "data" %}
<p>
Showing {{ start }}-{{ end }} of {{ total_rows }}
&nbsp;&nbsp;
Page {{ page }} of {{ total_pages }}
&nbsp;&nbsp;
{% if page > 1 %}
<a href="/?table={{ table_name }}&view=data&page={{ page - 1 }}">&lt;</a>
{% endif %}
{% if page < total_pages %}
<a href="/?table={{ table_name }}&view=data&page={{ page + 1 }}">&gt;</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>