From 36c6ef2877026e6dddb3d16f30ef4ffde18c8ad5 Mon Sep 17 00:00:00 2001 From: Aditya Waghamare Date: Sun, 4 Oct 2026 18:23:45 +0530 Subject: [PATCH 1/3] feat(frontend): add star on github button for self-hosted instances - Closes #847 - Files: packages/frontend/src/components/github-star-button.tsx, packages/frontend/src/components/app-sidebar.tsx Signed-off-by: Aditya Waghamare --- .../frontend/src/components/app-sidebar.tsx | 285 ++++-------------- .../src/components/github-star-button.tsx | 59 ++++ 2 files changed, 124 insertions(+), 220 deletions(-) create mode 100644 packages/frontend/src/components/github-star-button.tsx diff --git a/packages/frontend/src/components/app-sidebar.tsx b/packages/frontend/src/components/app-sidebar.tsx index a6605b300..6a6955d7a 100644 --- a/packages/frontend/src/components/app-sidebar.tsx +++ b/packages/frontend/src/components/app-sidebar.tsx @@ -1,4 +1,4 @@ -'use client'; +// File: packages/frontend/src/components/app-sidebar.tsx (Excerpt showing integration) import Link from 'next/link'; import { usePathname } from 'next/navigation'; @@ -6,237 +6,82 @@ import { useState, useRef, useEffect } from 'react'; import { useAuth } from '@/lib/auth-context'; import { LogoIcon } from '@/components/logo-icon'; import { cn } from '@/lib/utils'; +import { GithubStarButton } from '@/components/github-star-button'; -/* ── Inline icons (match the redesign prototype) ── */ -function I({ d, children, ...p }: { d?: string; children?: React.ReactNode } & React.SVGProps) { - return ( - - {d ? : children} - - ); -} -const DashboardIcon = () => ( - -); -const AnalyticsIcon = () => ( - -); -const LogsIcon = () => ; -const ConnectorsIcon = () => ( - -); -const StoreIcon = () => ; -const McpIcon = () => ( - -); -const KgIcon = () => ( - -); -const SkillsIcon = () => ; -const OnboardingIcon = () => ; -const SettingsIcon = () => ( - -); - -type NavLink = { href: string; label: string; icon: () => React.ReactNode }; -type NavGroup = { group: string; items: NavLink[] }; - -const NAV: NavGroup[] = [ - { - group: 'Overview', - items: [ - { href: '/', label: 'Dashboard', icon: DashboardIcon }, - { href: '/analytics', label: 'Analytics', icon: AnalyticsIcon }, - { href: '/logs', label: 'Audit Log', icon: LogsIcon }, - ], - }, - { - group: 'Build', - items: [ - { href: '/connectors', label: 'Connectors', icon: ConnectorsIcon }, - { href: '/connectors/store', label: 'Marketplace', icon: StoreIcon }, - { href: '/mcp-server', label: 'MCP Servers', icon: McpIcon }, - ], - }, - { - group: 'Intelligence', - items: [ - { href: '/knowledge-graph', label: 'Knowledge Graph', icon: KgIcon }, - { href: '/knowledge-graph/skills', label: 'AI Skills', icon: SkillsIcon }, - ], - }, - { - group: 'Get started', - items: [{ href: '/welcome', label: 'Setup & onboarding', icon: OnboardingIcon }], - }, -]; - -// All navigable hrefs, used to resolve the single active item: the longest -// href that prefix-matches the current path wins (so /connectors/store lights -// up "Marketplace" only, /knowledge-graph/skills lights up "AI Skills", etc.). -const ALL_HREFS = [...NAV.flatMap((g) => g.items.map((i) => i.href)), '/settings']; - -function bestHref(pathname: string): string | null { - let best: string | null = null; - for (const href of ALL_HREFS) { - const matches = href === '/' ? pathname === '/' : pathname === href || pathname.startsWith(href + '/'); - if (matches && (!best || href.length > best.length)) best = href; - } - return best; -} - -export function AppSidebar({ mobileOpen, onClose }: { mobileOpen: boolean; onClose: () => void }) { - const pathname = usePathname(); - const { user, orgName, orgs, switchOrg, logout } = useAuth(); - const [wsMenu, setWsMenu] = useState(false); - const wsRef = useRef(null); - - useEffect(() => { - function onClick(e: MouseEvent) { - if (wsRef.current && !wsRef.current.contains(e.target as Node)) setWsMenu(false); - } - document.addEventListener('mousedown', onClick); - return () => document.removeEventListener('mousedown', onClick); - }, []); - - // Exactly one nav item is active: the longest href matching the current path. - const active = bestHref(pathname); - const settingsActive = active === '/settings'; - - const orgInitials = (orgName || user?.email || 'A').slice(0, 2).toUpperCase(); - - const navContent = ( - <> -
-
- -
-
- AnythingMCP -
-
- - - - {/* Footer: Settings + workspace switcher */} -
- {wsMenu && ( -
-
- Switch workspace -
- {(orgs || []).map((org) => { - const isCurrent = org.id === user?.organizationId; - return ( - - ); - })} -
- -
-
- )} - - - - Settings - - - -
- - ); +// ... existing code ... +export function AppSidebar({ mobileOpen, setMobileOpen }: { mobileOpen: boolean; setMobileOpen: (open: boolean) => void }) { + // ... existing code ... return ( <> - {/* Mobile overlay */} + {/* Mobile backdrop */} {mobileOpen && (
setMobileOpen(false)} /> )} + ); diff --git a/packages/frontend/src/components/github-star-button.tsx b/packages/frontend/src/components/github-star-button.tsx new file mode 100644 index 000000000..d8fb153f9 --- /dev/null +++ b/packages/frontend/src/components/github-star-button.tsx @@ -0,0 +1,59 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { useEdition } from '@/lib/use-edition'; + +const STORAGE_KEY = 'amcp:hide-github-star'; + +export function GithubStarButton() { + const { edition } = useEdition(); + const [dismissed, setDismissed] = useState(false); + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + try { + if (localStorage.getItem(STORAGE_KEY) === 'true') { + setDismissed(true); + } + } catch {} + }, []); + + if (!mounted || !edition || dismissed) return null; + + if (process.env.NEXT_PUBLIC_HIDE_GITHUB_STAR === 'true') { + return null; + } + + const handleDismiss = (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + setDismissed(true); + try { + localStorage.setItem(STORAGE_KEY, 'true'); + } catch {} + }; + + return ( + + ); +} From 5428458b7e3458255c9e2a63422a525d7e017a6a Mon Sep 17 00:00:00 2001 From: Aditya Waghamare Date: Tue, 6 Oct 2026 13:54:47 +0000 Subject: [PATCH 2/3] fix(frontend): restore sidebar and refine star on github button - Restore complete app-sidebar.tsx from main and cleanly integrate GithubStarButton - Replace emoji with native SVG star matching sidebar icon stroke style - Enlarge dismiss close button target for better accessibility - Document NEXT_PUBLIC_HIDE_GITHUB_STAR in .env.example Signed-off-by: Aditya Waghamare --- .env.example | 7 + .../frontend/src/components/app-sidebar.tsx | 286 ++++++++++++++---- .../src/components/github-star-button.tsx | 33 +- 3 files changed, 257 insertions(+), 69 deletions(-) diff --git a/.env.example b/.env.example index 1ef7acbb3..b92202ea0 100644 --- a/.env.example +++ b/.env.example @@ -56,6 +56,8 @@ ENCRYPTION_KEY=change-me-in-production-exactly-32 # ── Frontend ───────────────────────────────────────────────────────────────── FRONTEND_URL=http://localhost:3000 NEXT_PUBLIC_API_URL=http://localhost:4000 +# Set to true to hide the "Star on GitHub" button in the sidebar on self-hosted instances +# NEXT_PUBLIC_HIDE_GITHUB_STAR=false NEXTAUTH_URL=http://localhost:3000 NEXTAUTH_SECRET=change-me-in-production @@ -82,6 +84,11 @@ ALLOW_OPEN_REGISTRATION=false # oauth2 — OAuth 2.0 Authorization Code (PKCE) + Client Credentials # both — OAuth 2.0 + Legacy (accepts either) MCP_AUTH_MODE=oauth2 +# PKCE (S256) is required on /authorize. Platforms that cannot send it, such as +# Microsoft Copilot Studio connectors, can be let through by client id. Only +# confidential clients (registered with client_secret_basic/post and a secret) +# are exempted; a listed public client still has to use PKCE. +# OAUTH_PKCE_EXEMPT_CLIENT_IDS= # Legacy auth tokens (used when MCP_AUTH_MODE=legacy or both) # MCP_BEARER_TOKEN=your-secret-token diff --git a/packages/frontend/src/components/app-sidebar.tsx b/packages/frontend/src/components/app-sidebar.tsx index 6a6955d7a..6700c53ab 100644 --- a/packages/frontend/src/components/app-sidebar.tsx +++ b/packages/frontend/src/components/app-sidebar.tsx @@ -1,4 +1,4 @@ -// File: packages/frontend/src/components/app-sidebar.tsx (Excerpt showing integration) +'use client'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; @@ -8,80 +8,238 @@ import { LogoIcon } from '@/components/logo-icon'; import { cn } from '@/lib/utils'; import { GithubStarButton } from '@/components/github-star-button'; -// ... existing code ... +/* ── Inline icons (match the redesign prototype) ── */ +function I({ d, children, ...p }: { d?: string; children?: React.ReactNode } & React.SVGProps) { + return ( + + {d ? : children} + + ); +} +const DashboardIcon = () => ( + +); +const AnalyticsIcon = () => ( + +); +const LogsIcon = () => ; +const ConnectorsIcon = () => ( + +); +const StoreIcon = () => ; +const McpIcon = () => ( + +); +const KgIcon = () => ( + +); +const SkillsIcon = () => ; +const OnboardingIcon = () => ; +const SettingsIcon = () => ( + +); + +type NavLink = { href: string; label: string; icon: () => React.ReactNode }; +type NavGroup = { group: string; items: NavLink[] }; + +const NAV: NavGroup[] = [ + { + group: 'Overview', + items: [ + { href: '/', label: 'Dashboard', icon: DashboardIcon }, + { href: '/analytics', label: 'Analytics', icon: AnalyticsIcon }, + { href: '/logs', label: 'Audit Log', icon: LogsIcon }, + ], + }, + { + group: 'Build', + items: [ + { href: '/connectors', label: 'Connectors', icon: ConnectorsIcon }, + { href: '/connectors/store', label: 'Marketplace', icon: StoreIcon }, + { href: '/mcp-server', label: 'MCP Servers', icon: McpIcon }, + ], + }, + { + group: 'Intelligence', + items: [ + { href: '/knowledge-graph', label: 'Knowledge Graph', icon: KgIcon }, + { href: '/knowledge-graph/skills', label: 'AI Skills', icon: SkillsIcon }, + ], + }, + { + group: 'Get started', + items: [{ href: '/welcome', label: 'Setup & onboarding', icon: OnboardingIcon }], + }, +]; + +// All navigable hrefs, used to resolve the single active item: the longest +// href that prefix-matches the current path wins (so /connectors/store lights +// up "Marketplace" only, /knowledge-graph/skills lights up "AI Skills", etc.). +const ALL_HREFS = [...NAV.flatMap((g) => g.items.map((i) => i.href)), '/settings']; + +function bestHref(pathname: string): string | null { + let best: string | null = null; + for (const href of ALL_HREFS) { + const matches = href === '/' ? pathname === '/' : pathname === href || pathname.startsWith(href + '/'); + if (matches && (!best || href.length > best.length)) best = href; + } + return best; +} + +export function AppSidebar({ mobileOpen, onClose }: { mobileOpen: boolean; onClose: () => void }) { + const pathname = usePathname(); + const { user, orgName, orgs, switchOrg, logout } = useAuth(); + const [wsMenu, setWsMenu] = useState(false); + const wsRef = useRef(null); + + useEffect(() => { + function onClick(e: MouseEvent) { + if (wsRef.current && !wsRef.current.contains(e.target as Node)) setWsMenu(false); + } + document.addEventListener('mousedown', onClick); + return () => document.removeEventListener('mousedown', onClick); + }, []); + + // Exactly one nav item is active: the longest href matching the current path. + const active = bestHref(pathname); + const settingsActive = active === '/settings'; + + const orgInitials = (orgName || user?.email || 'A').slice(0, 2).toUpperCase(); + + const navContent = ( + <> +
+
+ +
+
+ AnythingMCP +
+
+ + + + + + {/* Footer: Settings + workspace switcher */} +
+ {wsMenu && ( +
+
+ Switch workspace +
+ {(orgs || []).map((org) => { + const isCurrent = org.id === user?.organizationId; + return ( + + ); + })} +
+ +
+
+ )} + + + + Settings + + + +
+ + ); -export function AppSidebar({ mobileOpen, setMobileOpen }: { mobileOpen: boolean; setMobileOpen: (open: boolean) => void }) { - // ... existing code ... return ( <> - {/* Mobile backdrop */} + {/* Mobile overlay */} {mobileOpen && (
setMobileOpen(false)} + onClick={onClose} + className="fixed inset-0 z-[60] bg-black/45 md:hidden" /> )} - ); diff --git a/packages/frontend/src/components/github-star-button.tsx b/packages/frontend/src/components/github-star-button.tsx index d8fb153f9..41680832c 100644 --- a/packages/frontend/src/components/github-star-button.tsx +++ b/packages/frontend/src/components/github-star-button.tsx @@ -35,14 +35,26 @@ export function GithubStarButton() { }; return ( -
+ ); From 53a1eba20633f447ac76b19e9c99bf4f2d792723 Mon Sep 17 00:00:00 2001 From: Matteo Morelli Date: Tue, 6 Oct 2026 20:06:13 +0200 Subject: [PATCH 3/3] Drop NEXT_PUBLIC_HIDE_GITHUB_STAR: baked in at build time, it can't be set on the Docker image; the dismiss button covers it --- .env.example | 2 -- packages/frontend/src/components/github-star-button.tsx | 4 ---- 2 files changed, 6 deletions(-) diff --git a/.env.example b/.env.example index b92202ea0..5f436c6cd 100644 --- a/.env.example +++ b/.env.example @@ -56,8 +56,6 @@ ENCRYPTION_KEY=change-me-in-production-exactly-32 # ── Frontend ───────────────────────────────────────────────────────────────── FRONTEND_URL=http://localhost:3000 NEXT_PUBLIC_API_URL=http://localhost:4000 -# Set to true to hide the "Star on GitHub" button in the sidebar on self-hosted instances -# NEXT_PUBLIC_HIDE_GITHUB_STAR=false NEXTAUTH_URL=http://localhost:3000 NEXTAUTH_SECRET=change-me-in-production diff --git a/packages/frontend/src/components/github-star-button.tsx b/packages/frontend/src/components/github-star-button.tsx index 41680832c..5c098cf8c 100644 --- a/packages/frontend/src/components/github-star-button.tsx +++ b/packages/frontend/src/components/github-star-button.tsx @@ -21,10 +21,6 @@ export function GithubStarButton() { if (!mounted || !edition || dismissed) return null; - if (process.env.NEXT_PUBLIC_HIDE_GITHUB_STAR === 'true') { - return null; - } - const handleDismiss = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation();