A lexicon-driven AppView for ATProto. happyview.dev
backfill firehose jetstream atproto appview oauth lexicon
8
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add about page for debugging

Trezy b3fea95d 85d2c303

+100 -1
+8 -1
src/server.rs
··· 95 95 } 96 96 97 97 async fn config_endpoint(State(state): State<AppState>) -> Json<serde_json::Value> { 98 - Json(serde_json::json!({ "public_url": state.config.public_url })) 98 + Json(serde_json::json!({ 99 + "public_url": state.config.public_url, 100 + "version": env!("CARGO_PKG_VERSION"), 101 + "database_backend": format!("{:?}", state.config.database_backend).to_lowercase(), 102 + "jetstream_url": state.config.jetstream_url, 103 + "relay_url": state.config.relay_url, 104 + "plc_url": state.config.plc_url, 105 + })) 99 106 } 100 107 101 108 async fn client_metadata(State(state): State<AppState>) -> Json<serde_json::Value> {
+83
web/src/app/dashboard/about/page.tsx
··· 1 + "use client" 2 + 3 + import { useEffect, useState } from "react" 4 + 5 + import { SiteHeader } from "@/components/site-header" 6 + import { 7 + Card, 8 + CardContent, 9 + CardDescription, 10 + CardHeader, 11 + CardTitle, 12 + } from "@/components/ui/card" 13 + 14 + interface ConfigInfo { 15 + version: string 16 + public_url: string 17 + database_backend: string 18 + jetstream_url: string 19 + relay_url: string 20 + plc_url: string 21 + } 22 + 23 + export default function AboutPage() { 24 + const [config, setConfig] = useState<ConfigInfo | null>(null) 25 + const [error, setError] = useState<string | null>(null) 26 + 27 + useEffect(() => { 28 + fetch("/config", { credentials: "same-origin" }) 29 + .then((r) => { 30 + if (!r.ok) throw new Error("Failed to load config") 31 + return r.json() 32 + }) 33 + .then(setConfig) 34 + .catch((e) => setError(e.message)) 35 + }, []) 36 + 37 + const rows: { label: string; value: string | undefined }[] = config 38 + ? [ 39 + { label: "Version", value: config.version }, 40 + { label: "Public URL", value: config.public_url }, 41 + { label: "Database", value: config.database_backend }, 42 + { label: "Jetstream", value: config.jetstream_url }, 43 + { label: "Relay", value: config.relay_url }, 44 + { label: "PLC Directory", value: config.plc_url }, 45 + ] 46 + : [] 47 + 48 + return ( 49 + <> 50 + <SiteHeader title="About" /> 51 + <div className="flex flex-1 flex-col gap-4 p-4 md:p-6 max-w-3xl"> 52 + {error && <p className="text-destructive text-sm">{error}</p>} 53 + 54 + <Card> 55 + <CardHeader> 56 + <CardTitle>HappyView</CardTitle> 57 + <CardDescription> 58 + ATProto AppView instance information 59 + </CardDescription> 60 + </CardHeader> 61 + <CardContent> 62 + {config ? ( 63 + <dl className="grid grid-cols-[auto_1fr] gap-x-6 gap-y-3 text-sm"> 64 + {rows.map((row) => ( 65 + <div key={row.label} className="contents"> 66 + <dt className="text-muted-foreground font-medium"> 67 + {row.label} 68 + </dt> 69 + <dd className="font-mono">{row.value ?? "—"}</dd> 70 + </div> 71 + ))} 72 + </dl> 73 + ) : ( 74 + !error && ( 75 + <p className="text-muted-foreground text-sm">Loading...</p> 76 + ) 77 + )} 78 + </CardContent> 79 + </Card> 80 + </div> 81 + </> 82 + ) 83 + }
+9
web/src/components/app-sidebar.tsx
··· 17 17 IconLink, 18 18 IconPuzzle, 19 19 IconLockAccess, 20 + IconInfoCircle, 20 21 } from "@tabler/icons-react" 21 22 import Image from "next/image" 22 23 import Link from "next/link" ··· 159 160 </SidebarContent> 160 161 <SidebarFooter> 161 162 <SidebarMenu> 163 + <SidebarMenuItem> 164 + <SidebarMenuButton asChild tooltip="About" isActive={pathname === "/dashboard/about"}> 165 + <Link href="/dashboard/about"> 166 + <IconInfoCircle /> 167 + <span>About</span> 168 + </Link> 169 + </SidebarMenuButton> 170 + </SidebarMenuItem> 162 171 <SidebarMenuItem> 163 172 <SidebarMenuButton onClick={logout} tooltip="Log out"> 164 173 <IconLogout />