diff --git a/public/admin/assets/js/articles.js b/public/admin/assets/js/articles.js index f5a8631..70e326e 100644 --- a/public/admin/assets/js/articles.js +++ b/public/admin/assets/js/articles.js @@ -74,11 +74,14 @@ async function loadArticles() { `).join(""); const total = data.total; - document.getElementById("pageInfo").textContent = - `${articleOffset + 1}–${Math.min(articleOffset + PAGE, total)} of ${total.toLocaleString()}`; + const start = data.rows.length ? articleOffset + 1 : 0; + const end = articleOffset + data.rows.length; + document.getElementById("pageInfo").textContent = total == null + ? `${start}–${end} · filtered results` + : `${start}–${end} of approximately ${total.toLocaleString()}`; document.getElementById("prevBtn").disabled = articleOffset === 0; - document.getElementById("nextBtn").disabled = articleOffset + PAGE >= total; + document.getElementById("nextBtn").disabled = !data.hasMore; } diff --git a/public/admin/assets/js/events.js b/public/admin/assets/js/events.js index 54a68a4..bcfef11 100644 --- a/public/admin/assets/js/events.js +++ b/public/admin/assets/js/events.js @@ -51,10 +51,13 @@ async function loadEvents() { `).join(""); const total = data.total; - document.getElementById("ePageInfo").textContent = - `${eventOffset + 1}–${Math.min(eventOffset + PAGE, total)} of ${total.toLocaleString()}`; + const start = data.rows.length ? eventOffset + 1 : 0; + const end = eventOffset + data.rows.length; + document.getElementById("ePageInfo").textContent = total == null + ? `${start}–${end} · filtered results` + : `${start}–${end} of approximately ${total.toLocaleString()}`; document.getElementById("ePrevBtn").disabled = eventOffset === 0; - document.getElementById("eNextBtn").disabled = eventOffset + PAGE >= total; + document.getElementById("eNextBtn").disabled = !data.hasMore; } diff --git a/public/admin/pages/autonomy.html b/public/admin/pages/autonomy.html index 0b4b6cd..abfb5b8 100644 --- a/public/admin/pages/autonomy.html +++ b/public/admin/pages/autonomy.html @@ -5,10 +5,10 @@ Duriin — Autonomy - - - - + + + + @@ -131,7 +131,7 @@
- - + + diff --git a/public/admin/pages/ingest/articles.html b/public/admin/pages/ingest/articles.html index 607d4fd..10e8a8a 100644 --- a/public/admin/pages/ingest/articles.html +++ b/public/admin/pages/ingest/articles.html @@ -4,9 +4,9 @@ Duriin Admin — Articles - - - + + + @@ -134,7 +134,7 @@
- - + + diff --git a/public/admin/pages/ingest/events.html b/public/admin/pages/ingest/events.html index 2ee4870..c5ca569 100644 --- a/public/admin/pages/ingest/events.html +++ b/public/admin/pages/ingest/events.html @@ -4,9 +4,9 @@ Duriin Admin — Events - - - + + + @@ -94,7 +94,7 @@
- - + + diff --git a/public/admin/pages/intelligence/graph.html b/public/admin/pages/intelligence/graph.html index 7219c71..50c3182 100644 --- a/public/admin/pages/intelligence/graph.html +++ b/public/admin/pages/intelligence/graph.html @@ -4,10 +4,10 @@ Duriin Admin — Intelligence / Graph - - - - + + + + @@ -72,9 +72,9 @@
- - - - + + + + diff --git a/public/admin/pages/intelligence/knowledge.html b/public/admin/pages/intelligence/knowledge.html index 7ac6bd7..9bb95b4 100644 --- a/public/admin/pages/intelligence/knowledge.html +++ b/public/admin/pages/intelligence/knowledge.html @@ -4,10 +4,10 @@ Duriin Admin — Intelligence / Knowledge - - - - + + + + @@ -90,8 +90,8 @@
- - - + + + diff --git a/public/admin/pages/intelligence/predictions.html b/public/admin/pages/intelligence/predictions.html index fecc49a..74058b5 100644 --- a/public/admin/pages/intelligence/predictions.html +++ b/public/admin/pages/intelligence/predictions.html @@ -4,10 +4,10 @@ Duriin Admin — Intelligence / Predictions - - - - + + + + @@ -81,8 +81,8 @@
- - - + + + diff --git a/public/admin/pages/intelligence/signals.html b/public/admin/pages/intelligence/signals.html index f81d0e7..399aeb1 100644 --- a/public/admin/pages/intelligence/signals.html +++ b/public/admin/pages/intelligence/signals.html @@ -4,10 +4,10 @@ Duriin Admin — Intelligence / Signals - - - - + + + + @@ -137,8 +137,8 @@
- - - + + + diff --git a/public/admin/pages/sql.html b/public/admin/pages/sql.html index 28ad628..dbd65dc 100644 --- a/public/admin/pages/sql.html +++ b/public/admin/pages/sql.html @@ -4,9 +4,9 @@ Duriin Admin — SQL - - - + + + @@ -45,7 +45,7 @@
- - + + diff --git a/public/admin/pages/stats.html b/public/admin/pages/stats.html index 1261cf0..af9bfb3 100644 --- a/public/admin/pages/stats.html +++ b/public/admin/pages/stats.html @@ -4,10 +4,10 @@ Duriin Admin — Stats - - - - + + + + @@ -74,7 +74,7 @@
- - + + diff --git a/src/routes/admin.js b/src/routes/admin.js index f756c76..0894070 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -251,18 +251,20 @@ async function adminRoutes(fastify) { const where = conditions.length ? `WHERE ${conditions.join(' AND ')}` : ''; - const total = db.prepare(`SELECT COUNT(*) as n FROM articles ${where}`).get(...params).n; + const total = conditions.length + ? null + : (db.prepare("SELECT seq FROM sqlite_sequence WHERE name='articles'").get()?.seq || 0); - params.push(limit, offset); - const rows = db.prepare(` + params.push(limit + 1, offset); + const fetchedRows = db.prepare(` SELECT id, title, url, source, pub_date, ingested_at, content_status, is_index_page, has_embedding, language FROM articles ${where} - ORDER BY ingested_at DESC, id DESC + ORDER BY id DESC LIMIT ? OFFSET ? `).all(...params); - return { total, rows }; + return { total, rows: fetchedRows.slice(0, limit), hasMore: fetchedRows.length > limit }; }); fastify.get('/admin/api/articles/:id', async (request, reply) => { @@ -366,7 +368,7 @@ async function adminRoutes(fastify) { // whitelist sort columns + direction so user input cant break the query const sortMap = { - created_desc: 'e.created_at DESC', + created_desc: 'e.id DESC', created_asc: 'e.created_at ASC', articles_desc: 'article_count DESC', articles_asc: 'article_count ASC', @@ -385,15 +387,16 @@ async function adminRoutes(fastify) { FROM events e ${whereClause} `; const filteredEvents = `SELECT * FROM (${eventProjection}) ${countWhereClause}`; - const totalRow = db.prepare(`SELECT COUNT(*) AS n FROM (${filteredEvents})`) - .get(...whereParams, ...havingParams); - const rows = db.prepare(` + const total = where.length || having.length + ? null + : (db.prepare("SELECT seq FROM sqlite_sequence WHERE name='events'").get()?.seq || 0); + const fetchedRows = db.prepare(` ${filteredEvents} ORDER BY ${orderBy.replaceAll('e.', '')} LIMIT ? OFFSET ? - `).all(...whereParams, ...havingParams, limit, offset); + `).all(...whereParams, ...havingParams, limit + 1, offset); - return { total: totalRow.n, rows }; + return { total, rows: fetchedRows.slice(0, limit), hasMore: fetchedRows.length > limit }; }); fastify.delete('/admin/api/events/:id', async (request, reply) => { @@ -844,10 +847,8 @@ async function adminRoutes(fastify) { const counts = db.prepare(` SELECT COALESCE((SELECT seq FROM sqlite_sequence WHERE name='articles'), 0) AS total, - (SELECT COUNT(*) FROM articles - WHERE content IS NOT NULL AND content != '' - AND is_index_page = 0 AND has_embedding = 1) AS withContent, - (SELECT COUNT(*) FROM articles WHERE has_embedding = 1) AS withEmbedding, + (SELECT COUNT(*) FROM article_embedding_meta) AS withContent, + (SELECT COUNT(*) FROM article_embedding_meta) AS withEmbedding, COALESCE((SELECT seq FROM sqlite_sequence WHERE name='events'), 0) AS eventCount `).get(); statsSummaryCache = { at: Date.now(), value: counts }; @@ -865,13 +866,11 @@ async function adminRoutes(fastify) { const counts = db.prepare(` SELECT (SELECT COUNT(*) FROM articles) as total, - (SELECT COUNT(*) FROM articles - WHERE content IS NOT NULL AND content != '' - AND is_index_page = 0 AND has_embedding = 1) as withContent, - (SELECT COUNT(*) FROM articles WHERE has_embedding = 1) as withEmbedding, + (SELECT COUNT(*) FROM article_embedding_meta) as withContent, + (SELECT COUNT(*) FROM article_embedding_meta) as withEmbedding, (SELECT COUNT(*) FROM events) as eventCount, (SELECT COUNT(*) FROM articles WHERE ingested_at >= datetime('now', '-1 hour')) as ingestedPerHour, - (SELECT COUNT(*) FROM articles WHERE content_attempted_at >= datetime('now', '-1 hour')) as contentPerHour + (SELECT COUNT(*) FROM article_embedding_meta WHERE embedded_at >= datetime('now', '-1 hour')) as contentPerHour `).get(); const bySource = db.prepare(`