@@ -4,13 +4,14 @@ import Editor from '@monaco-editor/react'
44import { useSessions } from './hooks'
55import type { Session , OAuthServerInfo , OAuthStatusResponse , OAuthAuthorizeRequest , OAuthStatus , ToolSchemasResponse , ToolSchemaEntry } from './types'
66import { SessionTable } from './components/SessionTable'
7- import { Toggle } from './components/Toggle'
7+ import { Toggle , ThemeToggle } from './components/Toggle'
88import { Modal } from './components/Modal'
99import AgentDataflow from './components/AgentDataflow'
1010import Stats from './components/Stats'
1111import Kpis from './components/Kpis'
1212import DateRangeSlider from './components/DateRangeSlider'
1313import { ComparisonTable } from './components/ComparisonTable'
14+ import { EnterpriseFeature } from './components/EnterpriseFeature'
1415
1516// Embedding/Electron detection
1617const isEmbedded = ( ( ) => {
@@ -338,10 +339,10 @@ export function App(): React.JSX.Element {
338339
339340 const projectRoot = ( globalThis as any ) . __PROJECT_ROOT__ || ''
340341
341- const [ view , setView ] = useState < 'sessions' | 'configs' | 'manager' | 'observability' | 'agents' | 'comparison' > ( ( ) => {
342+ const [ view , setView ] = useState < 'sessions' | 'configs' | 'manager' | 'observability' | 'agents' | 'comparison' | 'users' | 'roles' > ( ( ) => {
342343 try {
343344 const saved = safeLocalStorage . getItem ( 'app_view' )
344- if ( saved === 'sessions' || saved === 'configs' || saved === 'manager' || saved === 'observability' || saved === 'agents' || saved === 'comparison' ) {
345+ if ( saved === 'sessions' || saved === 'configs' || saved === 'manager' || saved === 'observability' || saved === 'agents' || saved === 'comparison' || saved === 'users' || saved === 'roles' ) {
345346 return saved
346347 }
347348 } catch { /* ignore */ }
@@ -350,7 +351,7 @@ export function App(): React.JSX.Element {
350351 const [ hasUnsavedChanges , setHasUnsavedChanges ] = useState ( false )
351352
352353 // Handle view changes with unsaved changes warning
353- const handleViewChange = ( newView : 'sessions' | 'configs' | 'manager' | 'observability' | 'agents' | 'comparison' ) => {
354+ const handleViewChange = ( newView : 'sessions' | 'configs' | 'manager' | 'observability' | 'agents' | 'comparison' | 'users' | 'roles' ) => {
354355 if ( hasUnsavedChanges && view === 'configs' ) {
355356 const confirmed = window . confirm ( 'You have unsaved changes in the JSON editor. Are you sure you want to switch views? Your changes will be lost.' )
356357 if ( ! confirmed ) return
@@ -737,24 +738,49 @@ export function App(): React.JSX.Element {
737738 { view === 'manager' && 'Manage MCP servers, tools, and permissions with a guided interface.' }
738739 { view === 'agents' && 'Monitor agent identities, sessions, and permission overrides.' }
739740 { view === 'comparison' && 'Feature comparison between OpenEdison (Open Source) and EdisonWatch (Commercial).' }
741+ { view === 'users' && 'Multi-user management and access control (Enterprise feature).' }
742+ { view === 'roles' && 'Role-based access control and permission management (Enterprise feature).' }
740743 </ p >
741744 </ div >
742745 < div className = "flex gap-2 items-center" >
743746 < div className = "hidden sm:flex border border-app-border rounded overflow-hidden" >
744747 < button className = { `px-3 py-1 text-sm ${ view === 'sessions' ? 'text-app-accent border-r border-app-border bg-app-accent/10' : '' } ` } onClick = { ( ) => handleViewChange ( 'sessions' ) } > Sessions</ button >
745748 < button className = { `px-3 py-1 text-sm ${ view === 'agents' ? 'text-app-accent border-r border-app-border bg-app-accent/10' : '' } ` } onClick = { ( ) => handleViewChange ( 'agents' ) } > Agents</ button >
749+ < button className = { `px-3 py-1 text-sm ${ view === 'users' ? 'text-app-accent border-r border-app-border bg-app-accent/10' : '' } ` } onClick = { ( ) => handleViewChange ( 'users' ) } >
750+ < span className = "inline-flex items-center gap-1" >
751+ Users
752+ < svg className = "w-3 h-3 text-amber-400" fill = "currentColor" viewBox = "0 0 20 20" >
753+ < path fillRule = "evenodd" d = "M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clipRule = "evenodd" />
754+ </ svg >
755+ </ span >
756+ </ button >
757+ < button className = { `px-3 py-1 text-sm ${ view === 'roles' ? 'text-app-accent border-r border-app-border bg-app-accent/10' : '' } ` } onClick = { ( ) => handleViewChange ( 'roles' ) } >
758+ < span className = "inline-flex items-center gap-1" >
759+ Roles
760+ < svg className = "w-3 h-3 text-amber-400" fill = "currentColor" viewBox = "0 0 20 20" >
761+ < path fillRule = "evenodd" d = "M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clipRule = "evenodd" />
762+ </ svg >
763+ </ span >
764+ </ button >
746765 < button className = { `px-3 py-1 text-sm ${ view === 'configs' ? 'text-app-accent border-r border-app-border bg-app-accent/10' : '' } ` } onClick = { ( ) => handleViewChange ( 'configs' ) } > Raw Config</ button >
747766 < button className = { `px-3 py-1 text-sm ${ view === 'manager' ? 'text-app-accent border-r border-app-border bg-app-accent/10' : '' } ` } onClick = { ( ) => handleViewChange ( 'manager' ) } > Server Manager</ button >
748767 < button className = { `px-3 py-1 text-sm ${ view === 'comparison' ? 'text-app-accent border-r border-app-border bg-app-accent/10' : '' } ` } onClick = { ( ) => handleViewChange ( 'comparison' ) } > Comparison</ button >
749768 < button className = { `px-3 py-1 text-sm ${ view === 'observability' ? 'text-app-accent bg-app-accent/10' : '' } ` } onClick = { ( ) => handleViewChange ( 'observability' ) } > Observability</ button >
750769 </ div >
751770 { /* Hide theme switch when embedded in Electron (exposed via window.__ELECTRON_EMBED__) */ }
752771 { ! ( window as any ) . __ELECTRON_EMBED__ && ( new URLSearchParams ( location . search ) . get ( 'embed' ) !== 'electron' ) && (
753- < button className = "button" onClick = { ( ) => setTheme ( ( t ) => ( t === 'light' ? 'dark' : 'light' ) ) } >
754- { theme === 'light' ? 'Dark' : 'Light' } mode
755- </ button >
772+ < ThemeToggle theme = { theme } onChange = { setTheme } />
756773 ) }
757- < button className = "button" onClick = { ( ) => location . reload ( ) } > Refresh</ button >
774+ < button
775+ className = "button flex items-center justify-center"
776+ onClick = { ( ) => location . reload ( ) }
777+ aria-label = "Refresh page"
778+ title = "Refresh page"
779+ >
780+ < svg className = "w-5 h-5" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
781+ < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
782+ </ svg >
783+ </ button >
758784 </ div >
759785 </ div >
760786
@@ -845,6 +871,16 @@ export function App(): React.JSX.Element {
845871 < AgentsView sessions = { uiSessions } />
846872 ) : view === 'comparison' ? (
847873 < ComparisonTable />
874+ ) : view === 'users' ? (
875+ < EnterpriseFeature
876+ featureName = "User Management"
877+ description = "Manage multiple users, roles, and permissions across your organization with enterprise-grade access control."
878+ />
879+ ) : view === 'roles' ? (
880+ < EnterpriseFeature
881+ featureName = "Role-Based Access Control"
882+ description = "Define custom roles and granular permissions to control what users can access and manage across your MCP infrastructure."
883+ />
848884 ) : (
849885 < div className = "space-y-4" >
850886 < Kpis sessions = { timeFiltered } prevSessions = { prevTimeFiltered } />
0 commit comments