diff --git a/src/admin/records.rs b/src/admin/records.rs index b931134..f771848 100644 --- a/src/admin/records.rs +++ b/src/admin/records.rs @@ -36,6 +36,10 @@ pub(super) struct RecordLabel { pub(super) struct RecordEntry { pub uri: String, pub did: String, + pub collection: String, + pub rkey: String, + pub cid: String, + pub indexed_at: Option, pub record: Value, pub labels: Vec, } @@ -47,6 +51,16 @@ pub(super) struct ListRecordsResponse { pub cursor: Option, } +type RecordRow = ( + String, + String, + String, + String, + String, + Option, + String, +); + /// GET /admin/records?collection=X&limit=N&cursor=C — browse records by collection. pub(super) async fn list_records( State(state): State, @@ -63,10 +77,10 @@ pub(super) async fn list_records( .unwrap_or(0); let sql = adapt_sql( - "SELECT uri, did, record FROM records WHERE collection = ? ORDER BY indexed_at DESC LIMIT ? OFFSET ?", + "SELECT uri, did, collection, rkey, cid, indexed_at, record FROM records WHERE collection = ? ORDER BY indexed_at DESC LIMIT ? OFFSET ?", backend, ); - let rows: Vec<(String, String, String)> = sqlx::query_as(&sql) + let rows: Vec = sqlx::query_as(&sql) .bind(¶ms.collection) .bind(limit + 1) .bind(offset) @@ -75,13 +89,12 @@ pub(super) async fn list_records( .map_err(|e| AppError::Internal(format!("failed to list records: {e}")))?; let has_more = rows.len() as i64 > limit; - let visible_rows: Vec<(String, String, String)> = - rows.into_iter().take(limit as usize).collect(); + let visible_rows: Vec = rows.into_iter().take(limit as usize).collect(); // Batch-query external labels for all visible URIs let uris: Vec<&str> = visible_rows .iter() - .map(|(uri, _, _)| uri.as_str()) + .map(|(uri, _, _, _, _, _, _)| uri.as_str()) .collect(); let label_rows: Vec<(String, String, String, String)> = if uris.is_empty() { @@ -114,34 +127,40 @@ pub(super) async fn list_records( let records: Vec = visible_rows .into_iter() - .map(|(uri, did, record_str)| { - let record: Value = serde_json::from_str(&record_str).unwrap_or_default(); - let mut labels = labels_by_uri.remove(&uri).unwrap_or_default(); - - // Extract self-labels from record JSONB - if let Some(values) = record - .get("labels") - .and_then(|l| l.get("values")) - .and_then(|v| v.as_array()) - { - for entry in values { - if let Some(val) = entry.get("val").and_then(|v| v.as_str()) { - labels.push(RecordLabel { - src: did.clone(), - val: val.to_string(), - cts: String::new(), - }); + .map( + |(uri, did, collection, rkey, cid, indexed_at, record_str)| { + let record: Value = serde_json::from_str(&record_str).unwrap_or_default(); + let mut labels = labels_by_uri.remove(&uri).unwrap_or_default(); + + // Extract self-labels from record JSONB + if let Some(values) = record + .get("labels") + .and_then(|l| l.get("values")) + .and_then(|v| v.as_array()) + { + for entry in values { + if let Some(val) = entry.get("val").and_then(|v| v.as_str()) { + labels.push(RecordLabel { + src: did.clone(), + val: val.to_string(), + cts: String::new(), + }); + } } } - } - - RecordEntry { - uri, - did, - record, - labels, - } - }) + + RecordEntry { + uri, + did, + collection, + rkey, + cid, + indexed_at, + record, + labels, + } + }, + ) .collect(); let cursor = if has_more { diff --git a/web/src/app/dashboard/records/page.tsx b/web/src/app/dashboard/records/page.tsx index 462eeb4..01cbc40 100644 --- a/web/src/app/dashboard/records/page.tsx +++ b/web/src/app/dashboard/records/page.tsx @@ -28,6 +28,12 @@ import { ResponsiveDialogHeader, ResponsiveDialogTitle, } from "@/components/ui/responsive-dialog"; +import { + Sheet, + SheetContent, + SheetHeader, + SheetTitle, +} from "@/components/ui/sheet"; import { Checkbox } from "@/components/ui/checkbox"; import { CodeBlock } from "@/components/code-block"; import { DataTable } from "@/components/data-table/data-table"; @@ -430,29 +436,87 @@ export default function RecordsPage() { )} - {viewRecord && ( - setViewRecord(null)}> - - - - {viewRecord.uri} - - - - {hasPermission("records:delete") && ( -
- + { + if (!open) setViewRecord(null); + }} + > + + {viewRecord && ( + <> + + Record Detail + +
+
+
+ URI +

{viewRecord.uri}

+
+
+ DID +

{viewRecord.did}

+
+
+ Collection +

{viewRecord.collection}

+
+
+ Record Key +

{viewRecord.rkey}

+
+
+ CID +

{viewRecord.cid}

+
+ {viewRecord.indexed_at && ( +
+ Indexed +

+ {new Date(viewRecord.indexed_at).toLocaleString()} +

+
+ )} + {viewRecord.labels.length > 0 && ( +
+ Labels +
+ {viewRecord.labels.map((l, i) => ( + + {l.val} + + ))} +
+
+ )} +
+ +
+ Record +
+ +
+
- )} - - - )} + {hasPermission("records:delete") && ( +
+ +
+ )} + + )} +
+
labels: RecordLabel[] }