Compare commits
5 Commits
v1.6.2
...
a220494bcf
| Author | SHA1 | Date | |
|---|---|---|---|
| a220494bcf | |||
| 5bda1dd616 | |||
| 3e333291c6 | |||
| aa62528862 | |||
| ac155d8843 |
@@ -56,7 +56,7 @@ LLM_API_VERSION=
|
|||||||
REDIS_URL=redis://localhost:6379/0
|
REDIS_URL=redis://localhost:6379/0
|
||||||
|
|
||||||
# UI default page size (number of events shown per page)
|
# UI default page size (number of events shown per page)
|
||||||
DEFAULT_PAGE_SIZE=25
|
DEFAULT_PAGE_SIZE=24
|
||||||
|
|
||||||
# Optional: privacy / access control
|
# Optional: privacy / access control
|
||||||
# Hide entire services from users without PRIVACY_SERVICE_ROLES
|
# Hide entire services from users without PRIVACY_SERVICE_ROLES
|
||||||
|
|||||||
29
ROADMAP.md
29
ROADMAP.md
@@ -72,3 +72,32 @@ Goal: add AI-powered analysis and external tool integration.
|
|||||||
## Completed in this PR
|
## Completed in this PR
|
||||||
All Phase 5 items marked done were implemented in v1.3.0–v1.5.0.
|
All Phase 5 items marked done were implemented in v1.3.0–v1.5.0.
|
||||||
Redis caching + async queue implemented in v1.6.0, switched to Valkey.
|
Redis caching + async queue implemented in v1.6.0, switched to Valkey.
|
||||||
|
UI polish (topbar, footer, clickable pills) in v1.6.1–v1.6.4.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 6: Multi-Tenancy (Premium) ⏸️
|
||||||
|
Goal: allow MSPs to manage multiple client tenants from a single deployment.
|
||||||
|
|
||||||
|
Status: **Planned — not started**. Architecture designed, pending validation of core features (SIEM export, alerting) in production first.
|
||||||
|
|
||||||
|
### Architecture
|
||||||
|
- Row-level isolation: `tenant_id` field on every MongoDB document
|
||||||
|
- Each tenant has their own Microsoft Entra tenant + app registration credentials
|
||||||
|
- Auth: user's JWT `tid` claim maps to tenant config automatically
|
||||||
|
- Super-admin role for MSP staff to access all tenants
|
||||||
|
|
||||||
|
### Implementation phases
|
||||||
|
- **Phase 6.1** (2–3 days): Tenant model & registry, tenant-aware data layer, per-tenant Graph API auth
|
||||||
|
- **Phase 6.2** (1 day): Tenant-scoped API routes, tenant-specific config endpoints
|
||||||
|
- **Phase 6.3** (2 days): Frontend tenant switcher, tenant name display, admin page
|
||||||
|
- **Phase 6.4** (1 day): License gating — signed JWT `LICENSE_KEY` gates multi-tenant mode
|
||||||
|
|
||||||
|
### Licensing model
|
||||||
|
- Single-tenant: remains MIT/free
|
||||||
|
- Multi-tenant: premium feature requiring a signed license key
|
||||||
|
- License key is a JWT with claims: `plan`, `max_tenants`, `exp`, `features`
|
||||||
|
- Offline license generation tool included
|
||||||
|
|
||||||
|
### Effort estimate
|
||||||
|
~7–9 days total. Deferred until SIEM export and alerting are battle-tested.
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class Settings(BaseSettings):
|
|||||||
REDIS_URL: str = "redis://localhost:6379/0"
|
REDIS_URL: str = "redis://localhost:6379/0"
|
||||||
|
|
||||||
# UI defaults
|
# UI defaults
|
||||||
DEFAULT_PAGE_SIZE: int = 25
|
DEFAULT_PAGE_SIZE: int = 24
|
||||||
|
|
||||||
|
|
||||||
_settings = Settings()
|
_settings = Settings()
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Admin Operations Center</title>
|
<title>Admin Operations Center</title>
|
||||||
<link rel="stylesheet" href="/style.css?v=10" />
|
<link rel="stylesheet" href="/style.css?v=12" />
|
||||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||||
<script src="https://alcdn.msauth.net/browser/2.37.0/js/msal-browser.min.js" crossorigin="anonymous"></script>
|
<script src="https://alcdn.msauth.net/browser/2.37.0/js/msal-browser.min.js" crossorigin="anonymous"></script>
|
||||||
</head>
|
</head>
|
||||||
@@ -305,7 +305,7 @@
|
|||||||
accessToken: null,
|
accessToken: null,
|
||||||
authScopes: [],
|
authScopes: [],
|
||||||
filters: {
|
filters: {
|
||||||
actor: '', selectedServices: [], search: '', operation: '', result: '', start: '', end: '', limit: 25, includeTags: '', excludeTags: '',
|
actor: '', selectedServices: [], search: '', operation: '', result: '', start: '', end: '', limit: 24, includeTags: '', excludeTags: '',
|
||||||
},
|
},
|
||||||
options: { actors: [], services: [], operations: [], results: [] },
|
options: { actors: [], services: [], operations: [], results: [] },
|
||||||
savedSearches: [],
|
savedSearches: [],
|
||||||
@@ -398,6 +398,8 @@
|
|||||||
this.aiFeaturesEnabled = featBody.ai_features_enabled !== false;
|
this.aiFeaturesEnabled = featBody.ai_features_enabled !== false;
|
||||||
if (featBody.default_page_size) {
|
if (featBody.default_page_size) {
|
||||||
this.filters.limit = featBody.default_page_size;
|
this.filters.limit = featBody.default_page_size;
|
||||||
|
} else {
|
||||||
|
this.filters.limit = 24;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.aiFeaturesEnabled = true;
|
this.aiFeaturesEnabled = true;
|
||||||
@@ -567,9 +569,8 @@
|
|||||||
|
|
||||||
const saved = localStorage.getItem('aoc_filters');
|
const saved = localStorage.getItem('aoc_filters');
|
||||||
if (!saved && this.options.services.length) {
|
if (!saved && this.options.services.length) {
|
||||||
// Default: exclude noisy high-volume services
|
// Default: show all services (privacy controls handle exclusions server-side)
|
||||||
const noisy = ['Exchange', 'SharePoint', 'Teams'];
|
this.filters.selectedServices = [...this.options.services];
|
||||||
this.filters.selectedServices = this.options.services.filter((s) => !noisy.includes(s));
|
|
||||||
} else if (saved) {
|
} else if (saved) {
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(saved);
|
const parsed = JSON.parse(saved);
|
||||||
@@ -663,8 +664,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
clearFilters() {
|
clearFilters() {
|
||||||
const noisy = ['Exchange', 'SharePoint', 'Teams'];
|
this.filters = { actor: '', selectedServices: [...this.options.services], search: '', operation: '', result: '', start: '', end: '', limit: 24, includeTags: '', excludeTags: '' };
|
||||||
this.filters = { actor: '', selectedServices: this.options.services.filter((s) => !noisy.includes(s)), search: '', operation: '', result: '', start: '', end: '', limit: 25, includeTags: '', excludeTags: '' };
|
|
||||||
this.saveFilters();
|
this.saveFilters();
|
||||||
this.resetPagination();
|
this.resetPagination();
|
||||||
this.loadEvents();
|
this.loadEvents();
|
||||||
@@ -672,11 +672,7 @@
|
|||||||
|
|
||||||
filterByService(service) {
|
filterByService(service) {
|
||||||
if (!service) return;
|
if (!service) return;
|
||||||
if (!this.filters.selectedServices.includes(service)) {
|
this.filters.selectedServices = [service];
|
||||||
this.filters.selectedServices = [service];
|
|
||||||
} else {
|
|
||||||
this.filters.selectedServices = this.filters.selectedServices.filter((s) => s !== service);
|
|
||||||
}
|
|
||||||
this.saveFilters();
|
this.saveFilters();
|
||||||
this.resetPagination();
|
this.resetPagination();
|
||||||
this.loadEvents();
|
this.loadEvents();
|
||||||
|
|||||||
Reference in New Issue
Block a user