diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7b84bc5..2457268 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -92,7 +92,6 @@ "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -1277,7 +1276,6 @@ "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -1480,7 +1478,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001726", "electron-to-chromium": "^1.5.173", @@ -1530,7 +1527,6 @@ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz", "integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==", "license": "MIT", - "peer": true, "dependencies": { "@kurkle/color": "^0.3.0" }, @@ -2418,7 +2414,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -2621,7 +2616,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -2644,7 +2638,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -3011,7 +3004,6 @@ "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -3166,7 +3158,6 @@ "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index a28e977..21d29da 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -4,12 +4,13 @@ import Editor from '@monaco-editor/react' import { useSessions } from './hooks' import type { Session, OAuthServerInfo, OAuthStatusResponse, OAuthAuthorizeRequest, OAuthStatus, ToolSchemasResponse, ToolSchemaEntry } from './types' import { SessionTable } from './components/SessionTable' -import { Toggle } from './components/Toggle' +import { Toggle, ThemeToggle } from './components/Toggle' import { Modal } from './components/Modal' import AgentDataflow from './components/AgentDataflow' import Stats from './components/Stats' import Kpis from './components/Kpis' import DateRangeSlider from './components/DateRangeSlider' +import { EnterpriseFeature } from './components/EnterpriseFeature' // Embedding/Electron detection const isEmbedded = (() => { @@ -337,10 +338,10 @@ export function App(): React.JSX.Element { const projectRoot = (globalThis as any).__PROJECT_ROOT__ || '' - const [view, setView] = useState<'sessions' | 'configs' | 'manager' | 'observability' | 'agents'>(() => { + const [view, setView] = useState<'sessions' | 'configs' | 'manager' | 'observability' | 'agents' | 'users' | 'roles'>(() => { try { const saved = safeLocalStorage.getItem('app_view') - if (saved === 'sessions' || saved === 'configs' || saved === 'manager' || saved === 'observability' || saved === 'agents') { + if (saved === 'sessions' || saved === 'configs' || saved === 'manager' || saved === 'observability' || saved === 'agents' || saved === 'users' || saved === 'roles') { return saved } } catch { /* ignore */ } @@ -349,7 +350,7 @@ export function App(): React.JSX.Element { const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false) // Handle view changes with unsaved changes warning - const handleViewChange = (newView: 'sessions' | 'configs' | 'manager' | 'observability' | 'agents') => { + const handleViewChange = (newView: 'sessions' | 'configs' | 'manager' | 'observability' | 'agents' | 'users' | 'roles') => { if (hasUnsavedChanges && view === 'configs') { 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.') if (!confirmed) return @@ -735,23 +736,48 @@ export function App(): React.JSX.Element { {view === 'configs' && 'Direct JSON editing for configuration and permission files.'} {view === 'manager' && 'Manage MCP servers, tools, and permissions with a guided interface.'} {view === 'agents' && 'Monitor agent identities, sessions, and permission overrides.'} + {view === 'users' && 'Multi-user management and access control (Enterprise feature).'} + {view === 'roles' && 'Role-based access control and permission management (Enterprise feature).'}

+ + - +
{/* Hide theme switch when embedded in Electron (exposed via window.__ELECTRON_EMBED__) */} {!(window as any).__ELECTRON_EMBED__ && (new URLSearchParams(location.search).get('embed') !== 'electron') && ( - + )} - +
@@ -840,6 +866,16 @@ export function App(): React.JSX.Element { ) : view === 'agents' ? ( + ) : view === 'users' ? ( + + ) : view === 'roles' ? ( + ) : (
diff --git a/frontend/src/components/ComparisonTable.tsx b/frontend/src/components/ComparisonTable.tsx new file mode 100644 index 0000000..03a98a0 --- /dev/null +++ b/frontend/src/components/ComparisonTable.tsx @@ -0,0 +1,110 @@ +interface Feature { + name: string + openEdison: boolean + edisonWatch: boolean +} + +const features: Feature[] = [ + { name: 'Single User', openEdison: true, edisonWatch: true }, + { name: 'MCP Security Controls', openEdison: true, edisonWatch: true }, + { name: 'Lethal Trifecta Detection', openEdison: true, edisonWatch: true }, + { name: 'Tool/Resource Permissions', openEdison: true, edisonWatch: true }, + { name: 'Multi-Tenancy', openEdison: false, edisonWatch: true }, + { name: 'SIEM Integration', openEdison: false, edisonWatch: true }, + { name: 'SSO (Single Sign-On)', openEdison: false, edisonWatch: true }, + { name: 'Client Software for Auto-Enforcement', openEdison: false, edisonWatch: true }, +] + +function CheckIcon() { + return +} + +function CrossIcon() { + return +} + +export function ComparisonTable() { + return ( +
+
+

OpenEdison vs EdisonWatch

+

+ EdisonWatch adds Multi-Tenancy, SIEM, SSO, and Auto-Enforcement +

+
+ +
+ + + + + + + + + + {features.map((feature, index) => ( + + + + + + ))} + +
Feature + OpenEdison
+ (Open Source) +
+ EdisonWatch
+ (Commercial) +
{feature.name} + {feature.openEdison ? : } + + {feature.edisonWatch ? : } +
+
+ +
+

Enterprise Features Exclusive to EdisonWatch

+
    +
  • Multi-Tenancy: Support for multiple isolated users and organizations
  • +
  • SIEM Integration: Enterprise security information and event management
  • +
  • SSO (Single Sign-On): Integration with enterprise identity providers
  • +
  • Client Software for Auto-Enforcement: Automated policy enforcement at the client level
  • +
+
+ +
+
+

Interested in EdisonWatch Enterprise?

+

+ Schedule a personalized demo to see how EdisonWatch can secure your organization's AI agents with enterprise-grade features. +

+ + Book a Demo Call + +
+
+ +
+

+ For more information about EdisonWatch commercial licensing, please visit{' '} + + edisonwatch.com + +

+
+
+ ) +} diff --git a/frontend/src/components/DateRangeSlider.tsx b/frontend/src/components/DateRangeSlider.tsx index 27adbb0..7fa734d 100644 --- a/frontend/src/components/DateRangeSlider.tsx +++ b/frontend/src/components/DateRangeSlider.tsx @@ -306,7 +306,7 @@ export function DateRangeSlider({
+
+ {/* Lock Icon */} +
+ + + +
+ + {/* Heading */} +
+
+ + + + Enterprise Feature +
+

{featureName}

+

{description}

+
+ + {/* Feature List */} +
+

Available in EdisonWatch Enterprise

+
    +
  • + + + + Multi-Tenancy: Manage multiple isolated users and organizations +
  • +
  • + + + + SSO Integration: Enterprise identity provider support +
  • +
  • + + + + SIEM Integration: Enterprise security monitoring +
  • +
  • + + + + Client Software: Auto-enforcement at the client level +
  • +
+
+ + {/* CTA */} + +
+
+ ) +} diff --git a/frontend/src/components/Kpis.tsx b/frontend/src/components/Kpis.tsx index 62cb300..10f7792 100644 --- a/frontend/src/components/Kpis.tsx +++ b/frontend/src/components/Kpis.tsx @@ -116,7 +116,7 @@ export default function Kpis({ sessions, prevSessions }: { sessions: Session[]; return (
- } /> + } /> } /> diff --git a/frontend/src/components/Stats.tsx b/frontend/src/components/Stats.tsx index 93c6b26..6e7ddf3 100644 --- a/frontend/src/components/Stats.tsx +++ b/frontend/src/components/Stats.tsx @@ -45,14 +45,14 @@ const CrosshairPlugin = { ChartJS.register(CrosshairPlugin as any) const COLOR_PALETTE = [ - { fill: 'rgba(99,102,241,0.6)', stroke: 'rgba(99,102,241,1)' }, // indigo + { fill: 'rgba(195,255,253,0.6)', stroke: 'rgba(195,255,253,1)' }, // core cyan { fill: 'rgba(34,197,94,0.6)', stroke: 'rgba(34,197,94,1)' }, // green { fill: 'rgba(59,130,246,0.6)', stroke: 'rgba(59,130,246,1)' }, // blue { fill: 'rgba(244,114,182,0.6)', stroke: 'rgba(244,114,182,1)' }, // pink { fill: 'rgba(251,191,36,0.6)', stroke: 'rgba(251,191,36,1)' }, // amber { fill: 'rgba(248,113,113,0.6)', stroke: 'rgba(248,113,113,1)' }, // red { fill: 'rgba(14,165,233,0.6)', stroke: 'rgba(14,165,233,1)' }, // sky - { fill: 'rgba(139,92,246,0.6)', stroke: 'rgba(139,92,246,1)' }, // violet + { fill: 'rgba(155,164,166,0.6)', stroke: 'rgba(155,164,166,1)' }, // graphene grey ] type Bucket = { label: string; value: number } @@ -377,8 +377,8 @@ export function Stats({ sessions, onTimeRangeChange, onHoverTimeChange, rangeSta { label: 'Calls', data: callsValues, - borderColor: '#8b5cf6', - backgroundColor: 'rgba(139,92,246,0.2)', + borderColor: '#C3FFFD', + backgroundColor: 'rgba(195,255,253,0.2)', tension: 0, cubicInterpolationMode: 'monotone' as any, pointRadius: (callsValues.length <= 1 ? 3 : 0), @@ -459,7 +459,7 @@ export function Stats({ sessions, onTimeRangeChange, onHoverTimeChange, rangeSta b.label), - datasets: [{ label: 'sessions', data: sessionLenHist.map(b => b.value), backgroundColor: '#8b5cf6', borderColor: '#7c3aed' }], + datasets: [{ label: 'sessions', data: sessionLenHist.map(b => b.value), backgroundColor: '#C3FFFD', borderColor: '#9BA4A6' }], }} options={{ responsive: true, maintainAspectRatio: false, @@ -581,7 +581,7 @@ export function Stats({ sessions, onTimeRangeChange, onHoverTimeChange, rangeSta ) : ( b.label), - datasets: [{ label: 'sessions', data: sessionLenHist.map(b => b.value), backgroundColor: '#8b5cf6', borderColor: '#7c3aed' }], + datasets: [{ label: 'sessions', data: sessionLenHist.map(b => b.value), backgroundColor: '#C3FFFD', borderColor: '#9BA4A6' }], }} options={{ responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } } }} />)}
diff --git a/frontend/src/components/Toggle.tsx b/frontend/src/components/Toggle.tsx index 53690b5..3c17d35 100644 --- a/frontend/src/components/Toggle.tsx +++ b/frontend/src/components/Toggle.tsx @@ -22,4 +22,46 @@ export function Toggle({ checked, onChange }: { checked: boolean; onChange: (nex ) } +export function ThemeToggle({ theme, onChange }: { theme: 'light' | 'dark' | 'blue'; onChange: (theme: 'light' | 'dark') => void }) { + const isDark = theme === 'dark' || theme === 'blue' + + return ( + + ) +} + diff --git a/frontend/src/index.css b/frontend/src/index.css index 856f8aa..f7001a2 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -3,13 +3,19 @@ @tailwind utilities; :root { - --bg: #0b0c10; - --card: #111318; - --border: #1f2430; - --text: #e6e6e6; - --muted: #a0a7b4; - --accent: #7c3aed; - /* inspired accent */ + /* Brand Colors */ + --baseline-black: #000000; + --graphene-grey: #9BA4A6; + --core-cyan: #C3FFFD; + --wavelength-white: #F9F9F9; + + /* Applied Theme */ + --bg: #000000; + --card: #0a0a0a; + --border: #1a1a1a; + --text: #F9F9F9; + --muted: #9BA4A6; + --accent: #C3FFFD; --success: #10b981; --warning: #f59e0b; --danger: #ef4444; @@ -17,36 +23,38 @@ /* Explicit theme overrides via data-theme attribute */ [data-theme="dark"] { - --bg: #0b0c10; - --card: #111318; - --border: #1f2430; - --text: #e6e6e6; - --muted: #a0a7b4; + --bg: #000000; + --card: #0a0a0a; + --border: #1a1a1a; + --text: #F9F9F9; + --muted: #9BA4A6; } [data-theme="light"] { - --bg: #f8fafc; + --bg: #F9F9F9; --card: #ffffff; - --border: #e5e7eb; - --text: #0f172a; - --muted: #475569; + --border: #9BA4A6; + --text: #000000; + --muted: #9BA4A6; + --accent: #00B8B8; } [data-theme="blue"] { - --bg: #1e293b; - --card: #2d3b4e; - --border: #3d4f66; - --text: #f1f5f9; - --muted: #94a3b8; + --bg: #0a1419; + --card: #0f1e26; + --border: #1a3340; + --text: #C3FFFD; + --muted: #9BA4A6; } @media (prefers-color-scheme: light) { :root { - --bg: #f8fafc; + --bg: #F9F9F9; --card: #ffffff; - --border: #e5e7eb; - --text: #0f172a; - --muted: #475569; + --border: #9BA4A6; + --text: #000000; + --muted: #9BA4A6; + --accent: #00B8B8; } } @@ -94,7 +102,7 @@ body { padding: 2px 8px; border-radius: 999px; border: 1px solid var(--border); - background: rgba(124, 58, 237, 0.08); + background: color-mix(in srgb, var(--accent) 8%, transparent); color: var(--text); } diff --git a/pyproject.toml b/pyproject.toml index b3d84ea..b15a151 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "open-edison" -version = "0.1.133" +version = "0.1.134" description = "Open-source MCP security, aggregation, and monitoring. Single-user, self-hosted MCP proxy." readme = "README.md" authors = [