Spaces:
Paused
Paused
feat: add email inbox hook and dynamic API URLs for cloud deployment
Browse files- Update useLiveData.ts with dynamic API/WebSocket URL detection
- Add useEmailInbox hook for email polling
- Add email source type and endpoint mapping
- Point to new HuggingFace Space (widgettdc-api)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
apps/backend/src/index.ts
CHANGED
|
@@ -72,6 +72,7 @@ import { memoryRouter } from './services/memory/memoryController.js';
|
|
| 72 |
import { sragRouter } from './services/srag/sragController.js';
|
| 73 |
import { evolutionRouter } from './services/evolution/evolutionController.js';
|
| 74 |
import { palRouter } from './services/pal/palController.js';
|
|
|
|
| 75 |
import {
|
| 76 |
cmaContextHandler,
|
| 77 |
cmaIngestHandler,
|
|
|
|
| 72 |
import { sragRouter } from './services/srag/sragController.js';
|
| 73 |
import { evolutionRouter } from './services/evolution/evolutionController.js';
|
| 74 |
import { palRouter } from './services/pal/palController.js';
|
| 75 |
+
import datasourcesRouter from './routes/datasources.js';
|
| 76 |
import {
|
| 77 |
cmaContextHandler,
|
| 78 |
cmaIngestHandler,
|
apps/matrix-frontend/src/hooks/useLiveData.ts
CHANGED
|
@@ -11,7 +11,35 @@
|
|
| 11 |
|
| 12 |
import { useState, useEffect, useCallback, useRef } from 'react';
|
| 13 |
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
// Source types that widgets can request
|
| 17 |
export type DataSourceType =
|
|
@@ -24,7 +52,8 @@ export type DataSourceType =
|
|
| 24 |
| 'systemHealth' // System services health
|
| 25 |
| 'metrics' // Platform metrics
|
| 26 |
| 'osint' // External OSINT feeds (future)
|
| 27 |
-
| 'threatIntel'
|
|
|
|
| 28 |
|
| 29 |
// Recommended external sources that can be fetched on demand
|
| 30 |
export interface RecommendedSource {
|
|
@@ -68,6 +97,7 @@ const sourceEndpoints: Record<DataSourceType, string> = {
|
|
| 68 |
metrics: '/sys/metrics',
|
| 69 |
osint: '/acquisition/osint',
|
| 70 |
threatIntel: '/acquisition/threats',
|
|
|
|
| 71 |
};
|
| 72 |
|
| 73 |
// Recommended sources per data type
|
|
@@ -100,6 +130,10 @@ const recommendedSourcesMap: Record<DataSourceType, RecommendedSource[]> = {
|
|
| 100 |
{ id: 'otx', name: 'AlienVault OTX', description: 'Open Threat Exchange IOCs', category: 'threatIntel', requiresApproval: true, isAvailable: false },
|
| 101 |
{ id: 'mitre', name: 'MITRE ATT&CK', description: 'Adversary tactics & techniques', category: 'threatIntel', requiresApproval: false, isAvailable: true },
|
| 102 |
],
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
};
|
| 104 |
|
| 105 |
export function useLiveData<T = any>(options: UseLiveDataOptions<T>): UseLiveDataResult<T> {
|
|
@@ -222,7 +256,7 @@ export function useLiveData<T = any>(options: UseLiveDataOptions<T>): UseLiveDat
|
|
| 222 |
useEffect(() => {
|
| 223 |
if (!enabled || wsEvents.length === 0) return;
|
| 224 |
|
| 225 |
-
const wsUrl =
|
| 226 |
|
| 227 |
try {
|
| 228 |
wsRef.current = new WebSocket(wsUrl);
|
|
@@ -332,3 +366,17 @@ export function useDecisionHistory(pollInterval = 30000) {
|
|
| 332 |
transform: (data) => data.decisions || [],
|
| 333 |
});
|
| 334 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
import { useState, useEffect, useCallback, useRef } from 'react';
|
| 13 |
|
| 14 |
+
// Dynamic API base - use environment variable or HuggingFace Spaces URL in production
|
| 15 |
+
const getApiBase = () => {
|
| 16 |
+
// Check for Vite environment variable first
|
| 17 |
+
if (typeof import.meta !== 'undefined' && import.meta.env?.VITE_API_URL) {
|
| 18 |
+
return `${import.meta.env.VITE_API_URL}/api`;
|
| 19 |
+
}
|
| 20 |
+
// Production default: HuggingFace Spaces backend
|
| 21 |
+
if (typeof window !== 'undefined' && window.location.hostname !== 'localhost') {
|
| 22 |
+
return 'https://kraft102-widgettdc-api.hf.space/api';
|
| 23 |
+
}
|
| 24 |
+
// Development default
|
| 25 |
+
return 'http://localhost:3001/api';
|
| 26 |
+
};
|
| 27 |
+
|
| 28 |
+
const getWsBase = () => {
|
| 29 |
+
// Check for Vite environment variable first
|
| 30 |
+
if (typeof import.meta !== 'undefined' && import.meta.env?.VITE_WS_URL) {
|
| 31 |
+
return import.meta.env.VITE_WS_URL;
|
| 32 |
+
}
|
| 33 |
+
// Production default: HuggingFace Spaces backend
|
| 34 |
+
if (typeof window !== 'undefined' && window.location.hostname !== 'localhost') {
|
| 35 |
+
return 'wss://kraft102-widgettdc-api.hf.space';
|
| 36 |
+
}
|
| 37 |
+
// Development default
|
| 38 |
+
return 'ws://localhost:3001';
|
| 39 |
+
};
|
| 40 |
+
|
| 41 |
+
const API_BASE = getApiBase();
|
| 42 |
+
const WS_BASE = getWsBase();
|
| 43 |
|
| 44 |
// Source types that widgets can request
|
| 45 |
export type DataSourceType =
|
|
|
|
| 52 |
| 'systemHealth' // System services health
|
| 53 |
| 'metrics' // Platform metrics
|
| 54 |
| 'osint' // External OSINT feeds (future)
|
| 55 |
+
| 'threatIntel' // Threat intelligence (future)
|
| 56 |
+
| 'email'; // Email inbox data
|
| 57 |
|
| 58 |
// Recommended external sources that can be fetched on demand
|
| 59 |
export interface RecommendedSource {
|
|
|
|
| 97 |
metrics: '/sys/metrics',
|
| 98 |
osint: '/acquisition/osint',
|
| 99 |
threatIntel: '/acquisition/threats',
|
| 100 |
+
email: '/email/inbox',
|
| 101 |
};
|
| 102 |
|
| 103 |
// Recommended sources per data type
|
|
|
|
| 130 |
{ id: 'otx', name: 'AlienVault OTX', description: 'Open Threat Exchange IOCs', category: 'threatIntel', requiresApproval: true, isAvailable: false },
|
| 131 |
{ id: 'mitre', name: 'MITRE ATT&CK', description: 'Adversary tactics & techniques', category: 'threatIntel', requiresApproval: false, isAvailable: true },
|
| 132 |
],
|
| 133 |
+
email: [
|
| 134 |
+
{ id: 'outlook', name: 'Outlook/Office365', description: 'Microsoft email via IMAP', category: 'internal', requiresApproval: false, isAvailable: true },
|
| 135 |
+
{ id: 'gmail', name: 'Gmail', description: 'Google email via IMAP', category: 'internal', requiresApproval: false, isAvailable: true },
|
| 136 |
+
],
|
| 137 |
};
|
| 138 |
|
| 139 |
export function useLiveData<T = any>(options: UseLiveDataOptions<T>): UseLiveDataResult<T> {
|
|
|
|
| 256 |
useEffect(() => {
|
| 257 |
if (!enabled || wsEvents.length === 0) return;
|
| 258 |
|
| 259 |
+
const wsUrl = `${WS_BASE}/mcp/ws`;
|
| 260 |
|
| 261 |
try {
|
| 262 |
wsRef.current = new WebSocket(wsUrl);
|
|
|
|
| 366 |
transform: (data) => data.decisions || [],
|
| 367 |
});
|
| 368 |
}
|
| 369 |
+
|
| 370 |
+
export function useEmailInbox(pollInterval = 60000) {
|
| 371 |
+
return useLiveData({
|
| 372 |
+
sourceType: 'email',
|
| 373 |
+
pollInterval,
|
| 374 |
+
wsEvents: ['email:refresh'],
|
| 375 |
+
transform: (data) => ({
|
| 376 |
+
emails: data.emails || [],
|
| 377 |
+
unreadCount: data.unreadCount || 0,
|
| 378 |
+
source: data.source || 'unknown',
|
| 379 |
+
lastFetch: data.lastFetch || null,
|
| 380 |
+
}),
|
| 381 |
+
});
|
| 382 |
+
}
|