row_estimate came from pg_class.reltuples, which is -1 for tables that
have never been ANALYZEd (the common case on a fresh/small DB), so the
admin table list showed -1 for every table — meaningless.
Replace row_estimate with row_count + row_count_estimated:
- total_size < 100MB: SELECT count(*) (real value, cost negligible on
small tables; identifier quoted via PG escaping to stay injection-safe)
- total_size >= 100MB: fall back to reltuples estimate, flagged so the UI
can prefix with ~
Frontend now shows real counts (1/2/14...) for small tables and ~N for
large ones; header copy updated to reflect the mixed source.
extract(epoch FROM now() - query_start) returns numeric (decimal) in
PostgreSQL, but the result was read as f64 via r.get(4). tokio-postgres
has no FromSql<f64> impl for the numeric type, so get_db_status panicked
at runtime with 'error deserializing column 4' (HTTP 500).
Cast to ::double precision (float8) to match query_duration_secs: Option<f64>.
Audited the remaining column mappings in status.rs to rule out further
type mismatches — all sound: reltuples::bigint->i64, native bigint size
columns->i64, last_vacuum/last_analyze timestamptz->DateTime<Utc>
(chrono feature enabled), schema_migrations.version TEXT->String,
count(*)::int->i32 (fixed previously).
count(*) returns bigint (int8) in PostgreSQL, but the result was read as
i32 via conn_row.get(0). tokio-postgres's FromSql<i32> rejects an int8
column, so get_db_status panicked at runtime with
'error deserializing column 0' (HTTP 500).
Cast count(*)::int to int4, matching the adjacent setting::int column
and the total_connections/max_connections i32 struct fields. No other
queries in the file are affected: reltuples is already ::bigint -> i64,
size columns are native bigint -> i64, and system_status.rs reads its
count(*) as i64 correctly.
The /admin/system feature was never verified against the WASM frontend
target (dx serve builds src/main.rs with --features web and no server).
The entire src/api/database/ tree unconditionally pulled in server-only
crates (axum, sqlparser, dashmap, tokio, tokio_postgres, futures, regex)
and server-gated helpers (get_current_admin_user, parse_session_token,
get_user_by_token), so the frontend build failed with 43 errors.
Root cause + fixes (mirror the established settings.rs pattern):
- Gate server-only imports behind #[cfg(feature = server)] in
status/system_status/sql_console/schema/tasks/backup. The #[server] macro
strips call sites but NOT use statements, so unresolved imports remained.
- Ungate in main.rs: SystemSnapshot is a shared serde
type referenced by ServerStatus.host on both targets; only the sampler
task/SNAPSHOT static are internally server-gated.
- Gate (pure Axum handler, zero WASM consumers) and the
download_backup Axum handler in backup.rs.
- Move restore_backup's validation preamble (regex/backup_path/fs) inside
the #[cfg(feature = server)] block; gate std::path/chrono::Utc imports.
- Add [[bin]] required-features=[server] for generate_highlight_css so the
syntect-dependent build tool is skipped in web-only builds.
Genuine WASM bugs surfaced once the server bodies stopped masking them:
- system.rs: Closure import path (dioxus::prelude::wasm_bindgen ->
wasm_bindgen, matching write.rs); setTimeout expects i32 not u32;
editor_handle() -> editor_handle.read() (Signal is not Fn in 0.7).
- codemirror_bridge.rs: set_schema extern took &SqlSchema but SqlSchema is
a serde type with no IntoWasmAbi. Changed both set_schema signatures to
&JsValue; call site now serializes via serde-wasm-bindgen::to_value.
- system.rs: signals .set() inside spawn/use_future/onclick closures need
bindings (incl. the *_f rebinding copies); added cfg_attr
allow(unused_mut) on the 4 tab components, matching write.rs convention.
Verified: cargo check (default/server) clean; cargo check --features web
--target wasm32-unknown-unknown clean (0 errors); dx check clean; 411
cargo tests pass.
warn_if_app_base_url_unset() is unconditional—localhost dev also
emits a WARN (per csrf.rs doc). The main.rs side note claimed
localhost stays silent, contradicting the implementation.