mirror of
https://github.com/juherr/kill-the-news.git
synced 2026-06-21 06:13:48 +00:00
feat(monitoring): add stats counters API and public status page
Add GET /api/stats exposing cumulative counters (feeds created/deleted, emails received/rejected, recent date-times) plus live values (active feeds, active WebSub subscriptions). Counters persist in a stats:counters KV singleton and are incremented at the email-processing chokepoint and feed create/delete paths. Replace the / → /admin redirect with a public status page rendering these figures with a link to the admin. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import { Hono } from "hono";
|
||||
import { z } from "zod";
|
||||
import { Env, FeedConfig, FeedMetadata } from "../../types";
|
||||
import { generateFeedId } from "../../utils/id-generator";
|
||||
import { bumpCounters } from "../../utils/stats";
|
||||
import { waitUntilSafe } from "../../utils/worker";
|
||||
import { feedRssUrl, feedEmailAddress } from "../../utils/urls";
|
||||
import { logger } from "../../lib/logger";
|
||||
@@ -191,6 +192,11 @@ feedsRouter.post("/create", async (c) => {
|
||||
expiresAt,
|
||||
);
|
||||
|
||||
await bumpCounters(emailStorage, {
|
||||
feeds_created: 1,
|
||||
last_feed_created_at: new Date().toISOString(),
|
||||
});
|
||||
|
||||
if (isJson) {
|
||||
return c.json({
|
||||
feedId,
|
||||
@@ -528,7 +534,10 @@ feedsRouter.post("/:feedId/delete", async (c) => {
|
||||
|
||||
try {
|
||||
await deleteFeedFast(emailStorage, feedId);
|
||||
await removeFeedFromList(emailStorage, feedId);
|
||||
const removed = await removeFeedFromList(emailStorage, feedId);
|
||||
if (removed) {
|
||||
await bumpCounters(emailStorage, { feeds_deleted: 1 });
|
||||
}
|
||||
|
||||
waitUntilSafe(
|
||||
c,
|
||||
@@ -658,6 +667,9 @@ feedsRouter.post("/bulk-delete", async (c) => {
|
||||
}
|
||||
|
||||
const deletedFeedIds = await removeFeedsFromListBulk(emailStorage, okIds);
|
||||
if (deletedFeedIds.length > 0) {
|
||||
await bumpCounters(emailStorage, { feeds_deleted: deletedFeedIds.length });
|
||||
}
|
||||
|
||||
const removed = new Set(deletedFeedIds);
|
||||
okIds.forEach((feedId) => {
|
||||
@@ -707,6 +719,9 @@ feedsRouter.post("/bulk-delete", async (c) => {
|
||||
}
|
||||
|
||||
const deletedFeedIds = await removeFeedsFromListBulk(emailStorage, okIds);
|
||||
if (deletedFeedIds.length > 0) {
|
||||
await bumpCounters(emailStorage, { feeds_deleted: deletedFeedIds.length });
|
||||
}
|
||||
|
||||
return c.redirect(
|
||||
`${redirectBase}&message=bulkDeleted&count=${deletedFeedIds.length}`,
|
||||
|
||||
@@ -8,10 +8,11 @@ const designSystem = [variablesCss, layoutCss, componentsCss, utilitiesCss].join
|
||||
|
||||
type LayoutProps = {
|
||||
title: string;
|
||||
label?: string;
|
||||
children: import("hono/jsx").Child;
|
||||
};
|
||||
|
||||
export const Layout = ({ title, children }: LayoutProps) => {
|
||||
export const Layout = ({ title, label = "admin", children }: LayoutProps) => {
|
||||
return (
|
||||
<html>
|
||||
<head>
|
||||
@@ -38,7 +39,7 @@ export const Layout = ({ title, children }: LayoutProps) => {
|
||||
<a href="https://kill-the.news/" class="site-header-logo" target="_blank" rel="noopener">
|
||||
kill-the-news
|
||||
</a>
|
||||
<span class="site-header-label">admin</span>
|
||||
<span class="site-header-label">{label}</span>
|
||||
</header>
|
||||
{children}
|
||||
<footer class="site-footer">
|
||||
|
||||
Reference in New Issue
Block a user