aboutsummaryrefslogtreecommitdiffstats
path: root/apps/web
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web')
-rw-r--r--apps/web/app/settings/stats/page.tsx90
-rw-r--r--apps/web/lib/i18n/locales/en/translation.json4
2 files changed, 94 insertions, 0 deletions
diff --git a/apps/web/app/settings/stats/page.tsx b/apps/web/app/settings/stats/page.tsx
index 599e5362..944d1c59 100644
--- a/apps/web/app/settings/stats/page.tsx
+++ b/apps/web/app/settings/stats/page.tsx
@@ -11,18 +11,30 @@ import {
Archive,
BarChart3,
BookOpen,
+ Chrome,
Clock,
+ Code,
Database,
FileText,
Globe,
Hash,
Heart,
+ HelpCircle,
Highlighter,
Image,
Link,
List,
+ Rss,
+ Smartphone,
TrendingUp,
+ Upload,
+ Zap,
} from "lucide-react";
+import { z } from "zod";
+
+import { zBookmarkSourceSchema } from "@karakeep/shared/types/bookmarks";
+
+type BookmarkSource = z.infer<typeof zBookmarkSourceSchema>;
function formatBytes(bytes: number): string {
if (bytes === 0) return "0 Bytes";
@@ -47,6 +59,45 @@ const hourLabels = Array.from({ length: 24 }, (_, i) =>
i === 0 ? "12 AM" : i < 12 ? `${i} AM` : i === 12 ? "12 PM" : `${i - 12} PM`,
);
+function formatSourceName(source: BookmarkSource | null): string {
+ if (!source) return "Unknown";
+ const sourceMap: Record<BookmarkSource, string> = {
+ api: "API",
+ web: "Web",
+ extension: "Browser Extension",
+ cli: "CLI",
+ mobile: "Mobile App",
+ singlefile: "SingleFile",
+ rss: "RSS Feed",
+ import: "Import",
+ };
+ return sourceMap[source];
+}
+
+function getSourceIcon(source: BookmarkSource | null): React.ReactNode {
+ const iconProps = { className: "h-4 w-4 text-muted-foreground" };
+ switch (source) {
+ case "api":
+ return <Zap {...iconProps} />;
+ case "web":
+ return <Globe {...iconProps} />;
+ case "extension":
+ return <Chrome {...iconProps} />;
+ case "cli":
+ return <Code {...iconProps} />;
+ case "mobile":
+ return <Smartphone {...iconProps} />;
+ case "singlefile":
+ return <FileText {...iconProps} />;
+ case "rss":
+ return <Rss {...iconProps} />;
+ case "import":
+ return <Upload {...iconProps} />;
+ default:
+ return <HelpCircle {...iconProps} />;
+ }
+}
+
function SimpleBarChart({
data,
maxValue,
@@ -439,6 +490,45 @@ export default function StatsPage() {
)}
</CardContent>
</Card>
+
+ {/* Bookmark Sources */}
+ <Card>
+ <CardHeader>
+ <CardTitle className="flex items-center gap-2">
+ <Zap className="h-5 w-5" />
+ {t("settings.stats.bookmark_sources.title")}
+ </CardTitle>
+ </CardHeader>
+ <CardContent>
+ {stats.bookmarksBySource.length > 0 ? (
+ <div className="space-y-3">
+ {stats.bookmarksBySource.map(
+ (source: {
+ source: BookmarkSource | null;
+ count: number;
+ }) => (
+ <div
+ key={source.source || "unknown"}
+ className="flex items-center justify-between"
+ >
+ <div className="flex items-center gap-2">
+ {getSourceIcon(source.source)}
+ <span className="max-w-[200px] truncate text-sm">
+ {formatSourceName(source.source)}
+ </span>
+ </div>
+ <Badge variant="secondary">{source.count}</Badge>
+ </div>
+ ),
+ )}
+ </div>
+ ) : (
+ <p className="text-sm text-muted-foreground">
+ {t("settings.stats.bookmark_sources.empty")}
+ </p>
+ )}
+ </CardContent>
+ </Card>
</div>
{/* Activity Patterns */}
diff --git a/apps/web/lib/i18n/locales/en/translation.json b/apps/web/lib/i18n/locales/en/translation.json
index f582cd9b..4fc58be7 100644
--- a/apps/web/lib/i18n/locales/en/translation.json
+++ b/apps/web/lib/i18n/locales/en/translation.json
@@ -181,6 +181,10 @@
},
"storage_breakdown": {
"title": "Storage Breakdown"
+ },
+ "bookmark_sources": {
+ "title": "Bookmark Sources",
+ "empty": "No source data available"
}
},
"ai": {