rcgit

/ karakeep

Commit e2c303ac

SHA e2c303acb087cab2e1ed74b08796693e2108bc7e
Author MohamedBassem <me at mbassem dot com>
Author Date 2025-08-21 18:24 +0300
Committer MohamedBassem <me at mbassem dot com>
Commit Date 2025-08-21 18:24 +0300
Parent(s) ff33b310e219 (diff)
Tree f4bcd7dc4e70

patch snapshot

feat: A redesigned background jobs page. #1551
File + - Graph
M apps/web/app/admin/background_jobs/page.tsx +1 -6
M apps/web/components/admin/AdminCard.tsx +14 -2
M apps/web/components/admin/BackgroundJobs.tsx +420 -170
M apps/web/lib/i18n/locales/en/translation.json +64 -24
4 file(s) changed, 499 insertions(+), 202 deletions(-)

apps/web/app/admin/background_jobs/page.tsx

diff --git a/apps/web/app/admin/background_jobs/page.tsx b/apps/web/app/admin/background_jobs/page.tsx
index 92b9e370..6a13dd64 100644
--- a/apps/web/app/admin/background_jobs/page.tsx
+++ b/apps/web/app/admin/background_jobs/page.tsx
@@ -1,10 +1,5 @@
-import { AdminCard } from "@/components/admin/AdminCard";
 import BackgroundJobs from "@/components/admin/BackgroundJobs";
 
 export default function BackgroundJobsPage() {
-  return (
-    <AdminCard>
-      <BackgroundJobs />
-    </AdminCard>
-  );
+  return <BackgroundJobs />;
 }

apps/web/components/admin/AdminCard.tsx

diff --git a/apps/web/components/admin/AdminCard.tsx b/apps/web/components/admin/AdminCard.tsx
index 3a52b5e5..87c10076 100644
--- a/apps/web/components/admin/AdminCard.tsx
+++ b/apps/web/components/admin/AdminCard.tsx
@@ -1,3 +1,15 @@
-export function AdminCard({ children }: { children: React.ReactNode }) {
-  return <div className="rounded-md border bg-background p-4">{children}</div>;
+import { cn } from "@/lib/utils";
+
+export function AdminCard({
+  className,
+  children,
+}: {
+  className?: string;
+  children: React.ReactNode;
+}) {
+  return (
+    <div className={cn("rounded-md border bg-background p-4", className)}>
+      {children}
+    </div>
+  );
 }

apps/web/components/admin/BackgroundJobs.tsx

diff --git a/apps/web/components/admin/BackgroundJobs.tsx b/apps/web/components/admin/BackgroundJobs.tsx
index ac5885ef..56d3531f 100644
--- a/apps/web/components/admin/BackgroundJobs.tsx
+++ b/apps/web/components/admin/BackgroundJobs.tsx
@@ -1,23 +1,249 @@
 "use client";
 
 import { ActionButton } from "@/components/ui/action-button";
+import { Badge } from "@/components/ui/badge";
+import {
+  Card,
+  CardContent,
+  CardDescription,
+  CardHeader,
+  CardTitle,
+} from "@/components/ui/card";
+import { Skeleton } from "@/components/ui/skeleton";
 import { toast } from "@/components/ui/use-toast";
 import { useTranslation } from "@/lib/i18n/client";
 import { api } from "@/lib/trpc";
 import { keepPreviousData } from "@tanstack/react-query";
-
-import LoadingSpinner from "../ui/spinner";
 import {
-  Table,
-  TableBody,
-  TableCell,
-  TableHead,
-  TableHeader,
-  TableRow,
-} from "../ui/table";
+  Activity,
+  AlertTriangle,
+  Clock,
+  Database,
+  Globe,
+  HelpCircle,
+  Image,
+  RefreshCw,
+  Rss,
+  Search,
+  Sparkle,
+  Video,
+  Webhook,
+} from "lucide-react";
+
+import { AdminCard } from "./AdminCard";
+
+interface JobStats {
+  queued: number;
+  pending?: number;
+  failed?: number;
+}
+
+interface JobAction {
+  label: string;
+  onClick: () => void;
+  loading: boolean;
+}
+
+function JobStatusExplanation() {
+  const { t } = useTranslation();
+
+  return (
+    <Card className="border-blue-200 bg-blue-50/50 dark:border-blue-700 dark:bg-blue-900/10">
+      <CardHeader className="pb-4">
+        <CardTitle className="flex items-center gap-2 text-lg text-blue-900 dark:text-blue-200">
+          <HelpCircle className="h-5 w-5 text-blue-600 dark:text-blue-400" />
+          {t("admin.background_jobs.status.title")}
+        </CardTitle>
+      </CardHeader>
+      <CardContent>
+        <div className="grid gap-4 md:grid-cols-3">
+          <div className="flex items-start gap-3">
+            <div className="flex h-8 w-8 items-center justify-center">
+              <Clock className="h-4 w-4 text-blue-600 dark:text-blue-400" />
+            </div>
+            <div>
+              <h4 className="font-medium text-blue-900 dark:text-blue-200">
+                {t("admin.background_jobs.status.queued.title")}
+              </h4>
+              <p className="text-sm text-blue-700 dark:text-blue-300">
+                {t("admin.background_jobs.status.queued.description")}
+              </p>
+            </div>
+          </div>
+          <div className="flex items-start gap-3">
+            <div className="flex h-8 w-8 items-center justify-center">
+              <RefreshCw className="h-4 w-4 text-yellow-600 dark:text-yellow-400" />
+            </div>
+            <div>
+              <h4 className="font-medium text-yellow-900 dark:text-yellow-400">
+                {t("admin.background_jobs.status.unprocessed.title")}
+              </h4>
+              <p className="text-sm text-yellow-700 dark:text-yellow-500">
+                {t("admin.background_jobs.status.unprocessed.description")}
+              </p>
+            </div>
+          </div>
+          <div className="flex items-start gap-3">
+            <div className="flex h-8 w-8 items-center justify-center">
+              <AlertTriangle className="h-4 w-4 text-red-600 dark:text-red-500" />
+            </div>
+            <div>
+              <h4 className="font-medium text-red-900 dark:text-red-500">
+                {t("admin.background_jobs.status.failed.title")}
+              </h4>
+              <p className="text-sm text-red-700 dark:text-red-500">
+                {t("admin.background_jobs.status.failed.description")}
+              </p>
+            </div>
+          </div>
+        </div>
+      </CardContent>
+    </Card>
+  );
+}
+
+function JobCardSkeleton() {
+  return (
+    <Card className="relative overflow-hidden">
+      <CardHeader className="pb-4">
+        <div className="flex items-center justify-between">
+          <div className="flex items-center gap-3">
+            <Skeleton className="h-5 w-5" />
+            <Skeleton className="h-6 w-32" />
+          </div>
+          <Skeleton className="h-6 w-16" />
+        </div>
+        <Skeleton className="mt-2 h-4 w-3/4" />
+      </CardHeader>
+      <CardContent className="space-y-5">
+        <div className="flex flex-wrap items-center gap-4">
+          <div className="flex items-center gap-2">
+            <Skeleton className="h-4 w-4" />
+            <Skeleton className="h-4 w-16" />
+            <Skeleton className="h-6 w-8" />
+          </div>
+          <div className="flex items-center gap-2">
+            <Skeleton className="h-4 w-4" />
+            <Skeleton className="h-4 w-20" />
+            <Skeleton className="h-6 w-8" />
+          </div>
+        </div>
+
+        <div className="space-y-2">
+          <div className="flex justify-between">
+            <Skeleton className="h-3 w-20" />
+            <Skeleton className="h-3 w-16" />
+          </div>
+          <Skeleton className="h-2 w-full" />
+        </div>
 
-function AdminActions() {
+        <div className="space-y-3 border-t pt-4">
+          <Skeleton className="h-4 w-32" />
+          <div className="grid gap-2">
+            <Skeleton className="h-9 w-full" />
+            <Skeleton className="h-9 w-full" />
+          </div>
+        </div>
+      </CardContent>
+    </Card>
+  );
+}
+
+function JobCard({
+  title,
+  icon: Icon,
+  stats,
+  description,
+  actions = [],
+}: {
+  title: string;
+  icon: React.ComponentType<{ className?: string }>;
+  stats: JobStats;
+  description: string;
+  actions?: JobAction[];
+}) {
+  const { t } = useTranslation();
+  const total = stats.queued + (stats.pending || 0) + (stats.failed || 0);
+  const hasActivity = total > 0;
+
+  return (
+    <Card className="relative overflow-hidden">
+      <CardHeader className="pb-4">
+        <div className="flex items-center justify-between">
+          <div className="flex items-center gap-3">
+            <Icon
+              className={`h-5 w-5 ${hasActivity ? "text-primary" : "text-muted-foreground"}`}
+            />
+            <CardTitle className="text-lg">{title}</CardTitle>
+          </div>
+          {hasActivity && (
+            <Badge variant="secondary">
+              <Activity className="mr-1 h-3 w-3" />
+              {t("admin.background_jobs.active")}
+            </Badge>
+          )}
+        </div>
+        <CardDescription className="mt-2 text-sm">
+          {description}
+        </CardDescription>
+      </CardHeader>
+      <CardContent className="space-y-5">
+        <div className="flex flex-wrap items-center gap-4 text-sm">
+          <div className="flex items-center gap-2">
+            <Clock className="h-4 w-4 text-blue-500" />
+            <span className="font-medium">
+              {t("admin.background_jobs.status.queued.title")}
+            </span>
+            <Badge variant="outline">{stats.queued}</Badge>
+          </div>
+          {stats.pending !== undefined && (
+            <div className="flex items-center gap-2">
+              <RefreshCw className="h-4 w-4 text-yellow-500" />
+              <span className="font-medium">
+                {t("admin.background_jobs.status.unprocessed.title")}
+              </span>
+              <Badge variant="outline">{stats.pending}</Badge>
+            </div>
+          )}
+          {stats.failed !== undefined && stats.failed > 0 && (
+            <div className="flex items-center gap-2">
+              <AlertTriangle className="h-4 w-4 text-red-500" />
+              <span className="font-medium">
+                {t("admin.background_jobs.status.failed.title")}
+              </span>
+              <Badge variant="outline">{stats.failed}</Badge>
+            </div>
+          )}
+        </div>
+
+        {actions.length > 0 && (
+          <div className="space-y-3 border-t pt-4">
+            <h4 className="text-sm font-medium text-muted-foreground">
+              {t("admin.background_jobs.available_actions")}
+            </h4>
+            <div className="grid gap-2">
+              {actions.map((action, index) => (
+                <ActionButton
+                  key={index}
+                  variant="secondary"
+                  loading={action.loading}
+                  onClick={action.onClick}
+                  className="h-auto justify-start px-3 py-2.5 text-left text-sm"
+                >
+                  {action.label}
+                </ActionButton>
+              ))}
+            </div>
+          </div>
+        )}
+      </CardContent>
+    </Card>
+  );
+}
+
+function useJobActions() {
   const { t } = useTranslation();
+
   const { mutate: recrawlLinks, isPending: isRecrawlPending } =
     api.admin.recrawlLinks.useMutation({
       onSuccess: () => {
@@ -95,93 +321,88 @@ function AdminActions() {
       },
     });
 
-  return (
-    <div className="flex flex-col gap-2 sm:w-1/2">
-      <ActionButton
-        variant="destructive"
-        loading={isRecrawlPending}
-        onClick={() =>
-          recrawlLinks({ crawlStatus: "failure", runInference: true })
-        }
-      >
-        {t("admin.actions.recrawl_failed_links_only")}
-      </ActionButton>
-      <ActionButton
-        variant="destructive"
-        loading={isRecrawlPending}
-        onClick={() => recrawlLinks({ crawlStatus: "all", runInference: true })}
-      >
-        {t("admin.actions.recrawl_all_links")}
-      </ActionButton>
-      <ActionButton
-        variant="destructive"
-        loading={isRecrawlPending}
-        onClick={() =>
-          recrawlLinks({ crawlStatus: "all", runInference: false })
-        }
-      >
-        {t("admin.actions.recrawl_all_links")} (
-        {t("admin.actions.without_inference")})
-      </ActionButton>
-      <ActionButton
-        variant="destructive"
-        loading={isInferencePending}
-        onClick={() =>
-          reRunInferenceOnAllBookmarks({ type: "tag", status: "failure" })
-        }
-      >
-        {t("admin.actions.regenerate_ai_tags_for_failed_bookmarks_only")}
-      </ActionButton>
-      <ActionButton
-        variant="destructive"
-        loading={isInferencePending}
-        onClick={() =>
-          reRunInferenceOnAllBookmarks({ type: "tag", status: "all" })
-        }
-      >
-        {t("admin.actions.regenerate_ai_tags_for_all_bookmarks")}
-      </ActionButton>
-      <ActionButton
-        variant="destructive"
-        loading={isInferencePending}
-        onClick={() =>
-          reRunInferenceOnAllBookmarks({ type: "summarize", status: "failure" })
-        }
-      >
-        {t("admin.actions.regenerate_ai_summaries_for_failed_bookmarks_only")}
-      </ActionButton>
-      <ActionButton
-        variant="destructive"
-        loading={isInferencePending}
-        onClick={() =>
-          reRunInferenceOnAllBookmarks({ type: "summarize", status: "all" })
-        }
-      >
-        {t("admin.actions.regenerate_ai_summaries_for_all_bookmarks")}
-      </ActionButton>
-      <ActionButton
-        variant="destructive"
-        loading={isReindexPending}
-        onClick={() => reindexBookmarks()}
-      >
-        {t("admin.actions.reindex_all_bookmarks")}
-      </ActionButton>
-      <ActionButton
-        variant="destructive"
-        loading={isReprocessingPending}
-        onClick={() => reprocessAssetsFixMode()}
-      >
-        {t("admin.actions.reprocess_assets_fix_mode")}
-      </ActionButton>
-      <ActionButton
-        variant="destructive"
-        loading={isTidyAssetsPending}
-        onClick={() => tidyAssets()}
-      >
-        {t("admin.actions.compact_assets")}
-      </ActionButton>
-    </div>
-  );
+  return {
+    crawlActions: [
+      {
+        label: t("admin.background_jobs.actions.recrawl_failed_links_only"),
+        onClick: () =>
+          recrawlLinks({ crawlStatus: "failure", runInference: true }),
+        variant: "secondary" as const,
+        loading: isRecrawlPending,
+      },
+      {
+        label: t("admin.background_jobs.actions.recrawl_all_links"),
+        onClick: () => recrawlLinks({ crawlStatus: "all", runInference: true }),
+        loading: isRecrawlPending,
+      },
+      {
+        label: `${t("admin.background_jobs.actions.recrawl_all_links")} (${t("admin.background_jobs.actions.without_inference")})`,
+        onClick: () =>
+          recrawlLinks({ crawlStatus: "all", runInference: false }),
+        loading: isRecrawlPending,
+      },
+    ],
+    inferenceActions: [
+      {
+        label: t(
+          "admin.background_jobs.actions.regenerate_ai_tags_for_failed_bookmarks_only",
+        ),
+        onClick: () =>
+          reRunInferenceOnAllBookmarks({ type: "tag", status: "failure" }),
+        variant: "secondary" as const,
+        loading: isInferencePending,
+      },
+      {
+        label: t(
+          "admin.background_jobs.actions.regenerate_ai_tags_for_all_bookmarks",
+        ),
+        onClick: () =>
+          reRunInferenceOnAllBookmarks({ type: "tag", status: "all" }),
+        loading: isInferencePending,
+      },
+      {
+        label: t(
+          "admin.background_jobs.actions.regenerate_ai_summaries_for_failed_bookmarks_only",
+        ),
+        onClick: () =>
+          reRunInferenceOnAllBookmarks({
+            type: "summarize",
+            status: "failure",
+          }),
+        variant: "secondary" as const,
+        loading: isInferencePending,
+      },
+      {
+        label: t(
+          "admin.background_jobs.actions.regenerate_ai_summaries_for_all_bookmarks",
+        ),
+        onClick: () =>
+          reRunInferenceOnAllBookmarks({ type: "summarize", status: "all" }),
+        loading: isInferencePending,
+      },
+    ],
+    indexingActions: [
+      {
+        label: t("admin.background_jobs.actions.reindex_all_bookmarks"),
+        onClick: () => reindexBookmarks(),
+        loading: isReindexPending,
+      },
+    ],
+    assetPreprocessingActions: [
+      {
+        label: t("admin.background_jobs.actions.reprocess_assets_fix_mode"),
+        onClick: () => reprocessAssetsFixMode(),
+        loading: isReprocessingPending,
+      },
+    ],
+    tidyAssetsActions: [
+      {
+        label: t("admin.background_jobs.actions.clean_assets"),
+        onClick: () => tidyAssets(),
+        loading: isTidyAssetsPending,
+      },
+    ],
+  };
 }
 
 export default function BackgroundJobs() {
@@ -194,84 +415,113 @@ export default function BackgroundJobs() {
     },
   );
 
+  const actions = useJobActions();
+
   if (!serverStats) {
-    return <LoadingSpinner />;
+    return (
+      <div className="space-y-6">
+        <div className="space-y-2">
+          <Skeleton className="h-8 w-64" />
+          <Skeleton className="h-5 w-96" />
+        </div>
+
+        <div className="grid gap-6 xl:grid-cols-2">
+          {Array.from({ length: 8 }).map((_, index) => (
+            <JobCardSkeleton key={index} />
+          ))}
+        </div>
+      </div>
+    );
   }
 
+  const jobs = [
+    {
+      title: t("admin.background_jobs.jobs.crawler.title"),
+      icon: Globe,
+      stats: serverStats.crawlStats,
+      description: t("admin.background_jobs.jobs.crawler.description"),
+      actions: actions.crawlActions,
+    },
+    {
+      title: t("admin.background_jobs.jobs.inference.title"),
+      icon: Sparkle,
+      stats: serverStats.inferenceStats,
+      description: t("admin.background_jobs.jobs.inference.description"),
+      actions: actions.inferenceActions,
+    },
+    {
+      title: t("admin.background_jobs.jobs.indexing.title"),
+      icon: Search,
+      stats: { queued: serverStats.indexingStats.queued },
+      description: t("admin.background_jobs.jobs.indexing.description"),
+      actions: actions.indexingActions,
+    },
+    {
+      title: t("admin.background_jobs.jobs.asset_preprocessing.title"),
+      icon: Image,
+      stats: { queued: serverStats.assetPreprocessingStats.queued },
+      description: t(
+        "admin.background_jobs.jobs.asset_preprocessing.description",
+      ),
+      actions: actions.assetPreprocessingActions,
+    },
+    {
+      title: t("admin.background_jobs.jobs.tidy_assets.title"),
+      icon: Database,
+      stats: { queued: serverStats.tidyAssetsStats.queued },
+      description: t("admin.background_jobs.jobs.tidy_assets.description"),
+      actions: actions.tidyAssetsActions,
+    },
+    {
+      title: t("admin.background_jobs.jobs.video.title"),
+      icon: Video,
+      stats: { queued: serverStats.videoStats.queued },
+      description: t("admin.background_jobs.jobs.video.description"),
+      actions: [],
+    },
+    {
+      title: t("admin.background_jobs.jobs.webhook.title"),
+      icon: Webhook,
+      stats: { queued: serverStats.webhookStats.queued },
+      description: t("admin.background_jobs.jobs.webhook.description"),
+      actions: [],
+    },
+    {
+      title: t("admin.background_jobs.jobs.feed.title"),
+      icon: Rss,
+      stats: { queued: serverStats.feedStats.queued },
+      description: t("admin.background_jobs.jobs.feed.description"),
+      actions: [],
+    },
+  ];
+
   return (
-    <div className="flex flex-col gap-4">
-      <div className="mb-2 text-xl font-medium">
-        {t("admin.background_jobs.background_jobs")}
-      </div>
-      <div className="sm:w-1/2">
-        <Table className="rounded-md border">
-          <TableHeader className="bg-gray-200">
-            <TableHead>{t("admin.background_jobs.job")}</TableHead>
-            <TableHead>{t("admin.background_jobs.queued")}</TableHead>
-            <TableHead>{t("admin.background_jobs.pending")}</TableHead>
-            <TableHead>{t("admin.background_jobs.failed")}</TableHead>
-          </TableHeader>
-          <TableBody>
-            <TableRow>
-              <TableCell className="lg:w-2/3">
-                {t("admin.background_jobs.crawler_jobs")}
-              </TableCell>
-              <TableCell>{serverStats.crawlStats.queued}</TableCell>
-              <TableCell>{serverStats.crawlStats.pending}</TableCell>
-              <TableCell>{serverStats.crawlStats.failed}</TableCell>
-            </TableRow>
-            <TableRow>
-              <TableCell>{t("admin.background_jobs.indexing_jobs")}</TableCell>
-              <TableCell>{serverStats.indexingStats.queued}</TableCell>
-              <TableCell>-</TableCell>
-              <TableCell>-</TableCell>
-            </TableRow>
-            <TableRow>
-              <TableCell>{t("admin.background_jobs.inference_jobs")}</TableCell>
-              <TableCell>{serverStats.inferenceStats.queued}</TableCell>
-              <TableCell>{serverStats.inferenceStats.pending}</TableCell>
-              <TableCell>{serverStats.inferenceStats.failed}</TableCell>
-            </TableRow>
-            <TableRow>
-              <TableCell>
-                {t("admin.background_jobs.tidy_assets_jobs")}
-              </TableCell>
-              <TableCell>{serverStats.tidyAssetsStats.queued}</TableCell>
-              <TableCell>-</TableCell>
-              <TableCell>-</TableCell>
-            </TableRow>
-            <TableRow>
-              <TableCell>{t("admin.background_jobs.video_jobs")}</TableCell>
-              <TableCell>{serverStats.videoStats.queued}</TableCell>
-              <TableCell>-</TableCell>
-              <TableCell>-</TableCell>
-            </TableRow>
-            <TableRow>
-              <TableCell>{t("admin.background_jobs.webhook_jobs")}</TableCell>
-              <TableCell>{serverStats.webhookStats.queued}</TableCell>
-              <TableCell>-</TableCell>
-              <TableCell>-</TableCell>
-            </TableRow>
-            <TableRow>
-              <TableCell>
-                {t("admin.background_jobs.asset_preprocessing_jobs")}
-              </TableCell>
-              <TableCell>
-                {serverStats.assetPreprocessingStats.queued}
-              </TableCell>
-              <TableCell>-</TableCell>
-              <TableCell>-</TableCell>
-            </TableRow>
-            <TableRow>
-              <TableCell>{t("admin.background_jobs.feed_jobs")}</TableCell>
-              <TableCell>{serverStats.feedStats.queued}</TableCell>
-              <TableCell>-</TableCell>
-              <TableCell>-</TableCell>
-            </TableRow>
-          </TableBody>
-        </Table>
+    <div className="space-y-6">
+      <AdminCard className="space-y-6">
+        <div className="space-y-2">
+          <h2 className="text-2xl font-semibold tracking-tight">
+            {t("admin.background_jobs.background_jobs")}
+          </h2>
+          <p className="text-muted-foreground">
+            {t("admin.background_jobs.monitor_and_manage")}
+          </p>
+        </div>
+
+        <JobStatusExplanation />
+      </AdminCard>
+
+      <div className="grid gap-6 xl:grid-cols-2">
+        {jobs.map((job, index) => (
+          <JobCard
+            key={index}
+            title={job.title}
+            icon={job.icon}
+            stats={job.stats}
+            description={job.description}
+            actions={job.actions}
+          />
+        ))}
       </div>
-      <AdminActions />
     </div>
   );
 }

apps/web/lib/i18n/locales/en/translation.json

diff --git a/apps/web/lib/i18n/locales/en/translation.json b/apps/web/lib/i18n/locales/en/translation.json
index 10b2f390..21268320 100644
--- a/apps/web/lib/i18n/locales/en/translation.json
+++ b/apps/web/lib/i18n/locales/en/translation.json
@@ -317,31 +317,71 @@
       "server_version": "Server Version"
     },
     "background_jobs": {
+      "jobs": {
+        "crawler": {
+          "title": "Crawler Jobs",
+          "description": "Web crawling and content extraction from URLs"
+        },
+        "inference": {
+          "title": "Inference Jobs",
+          "description": "AI-powered tagging and summarization of content"
+        },
+        "indexing": {
+          "title": "Indexing Jobs",
+          "description": "Search index updates"
+        },
+        "asset_preprocessing": {
+          "title": "Asset Preprocessing Jobs",
+          "description": "Image and document preprocessing (screenshots, text extraction, etc.)"
+        },
+        "tidy_assets": {
+          "title": "Tidy Assets Jobs",
+          "description": "Asset cleanup and storage optimization"
+        },
+        "video": {
+          "title": "Video Download Jobs",
+          "description": "Video extraction and download"
+        },
+        "webhook": {
+          "title": "Webhook Jobs",
+          "description": "External webhook notifications"
+        },
+        "feed": {
+          "title": "RSS Feed Jobs",
+          "description": "RSS feed processing and content updates"
+        }
+      },
       "background_jobs": "Background Jobs",
-      "crawler_jobs": "Crawler Jobs",
-      "indexing_jobs": "Indexing Jobs",
-      "inference_jobs": "Inference Jobs",
-      "tidy_assets_jobs": "Tidy Assets Jobs",
-      "video_jobs": "Video Download Jobs",
-      "webhook_jobs": "Webhook Jobs",
-      "asset_preprocessing_jobs": "Asset Preprocessing Jobs",
-      "feed_jobs": "RSS Feed Jobs",
-      "job": "Job",
-      "queued": "Queued",
-      "pending": "Pending",
-      "failed": "Failed"
-    },
-    "actions": {
-      "recrawl_failed_links_only": "Recrawl Failed Links Only",
-      "recrawl_all_links": "Recrawl All Links",
-      "without_inference": "Without Inference",
-      "regenerate_ai_tags_for_failed_bookmarks_only": "Regenerate AI Tags for Failed Bookmarks Only",
-      "regenerate_ai_tags_for_all_bookmarks": "Regenerate AI Tags for All Bookmarks",
-      "regenerate_ai_summaries_for_failed_bookmarks_only": "Regenerate AI Summaries for Failed Bookmarks Only",
-      "regenerate_ai_summaries_for_all_bookmarks": "Regenerate AI Summaries for All Bookmarks",
-      "reindex_all_bookmarks": "Reindex All Bookmarks",
-      "compact_assets": "Compact Assets",
-      "reprocess_assets_fix_mode": "Reprocess Assets (Fix Mode)"
+      "monitor_and_manage": "Monitor and manage background job queues and system processing tasks",
+      "active": "Active",
+      "available_actions": "Available Actions",
+      "status": {
+        "title": "Understanding Job States",
+        "queued": {
+          "title": "Queued",
+          "description": "Jobs waiting in line to be processed. They will start automatically when resources are available."
+        },
+        "unprocessed": {
+          "title": "Unprocessed",
+          "description": "Bookmarks that have not yet been processed. They are most likely already queued for processing, if not, you might need to manually re-enqueue them."
+        },
+        "failed": {
+          "title": "Failed",
+          "description": "Bookmarks that encountered errors during processing. These may need manual attention."
+        }
+      },
+      "actions": {
+        "recrawl_failed_links_only": "Recrawl Failed Links Only",
+        "recrawl_all_links": "Recrawl All Links",
+        "without_inference": "Without Inference",
+        "regenerate_ai_tags_for_failed_bookmarks_only": "Regenerate AI Tags for Failed Bookmarks Only",
+        "regenerate_ai_tags_for_all_bookmarks": "Regenerate AI Tags for All Bookmarks",
+        "regenerate_ai_summaries_for_failed_bookmarks_only": "Regenerate AI Summaries for Failed Bookmarks Only",
+        "regenerate_ai_summaries_for_all_bookmarks": "Regenerate AI Summaries for All Bookmarks",
+        "reindex_all_bookmarks": "Reindex All Bookmarks",
+        "clean_assets": "Clean Dangling Assets & Re-sync Metadata",
+        "reprocess_assets_fix_mode": "Reprocess Unprocessed Assets"
+      }
     },
     "users_list": {
       "users_list": "Users List",