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).'}
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
+
+
+
+
+
+
+
+ Feature
+
+ OpenEdison
+ (Open Source)
+
+
+ EdisonWatch
+ (Commercial)
+
+
+
+
+ {features.map((feature, index) => (
+
+ {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 (
+
onChange(isDark ? 'light' : 'dark')}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault()
+ onChange(isDark ? 'light' : 'dark')
+ }
+ }}
+ >
+ {/* Slider */}
+
+ {isDark ? (
+ // Moon icon
+
+
+
+ ) : (
+ // Sun icon
+
+
+
+ )}
+
+
+ )
+}
+
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 = [