Initial commit: LLM Automation Docs & Remediation Engine v2.0
Features: - Automated datacenter documentation generation - MCP integration for device connectivity - Auto-remediation engine with safety checks - Multi-factor reliability scoring (0-100%) - Human feedback learning loop - Pattern recognition and continuous improvement - Agentic chat support with AI - API for ticket resolution - Frontend React with Material-UI - CI/CD pipelines (GitLab + Gitea) - Docker & Kubernetes deployment - Complete documentation and guides v2.0 Highlights: - Auto-remediation with write operations (disabled by default) - Reliability calculator with 4-factor scoring - Human feedback system for continuous learning - Pattern-based progressive automation - Approval workflow for critical actions - Full audit trail and rollback capability
This commit is contained in:
29
frontend/package.json
Normal file
29
frontend/package.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "datacenter-docs-frontend",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.21.0",
|
||||
"axios": "^1.6.0",
|
||||
"@mui/material": "^5.15.0",
|
||||
"@mui/icons-material": "^5.15.0",
|
||||
"@emotion/react": "^11.11.0",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"socket.io-client": "^4.6.0",
|
||||
"markdown-it": "^14.0.0",
|
||||
"date-fns": "^3.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"vite": "^5.0.10",
|
||||
"@types/react": "^18.2.48",
|
||||
"@types/react-dom": "^18.2.18"
|
||||
}
|
||||
}
|
||||
386
frontend/src/App.jsx
Normal file
386
frontend/src/App.jsx
Normal file
@@ -0,0 +1,386 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import {
|
||||
AppBar, Toolbar, Typography, Container, Box, Paper,
|
||||
TextField, Button, List, ListItem, ListItemText,
|
||||
CircularProgress, Chip, Grid, Card, CardContent,
|
||||
Tabs, Tab, Divider, IconButton
|
||||
} from '@mui/material';
|
||||
import {
|
||||
Send as SendIcon,
|
||||
Search as SearchIcon,
|
||||
Description as DocIcon,
|
||||
Support as SupportIcon,
|
||||
CloudUpload as UploadIcon
|
||||
} from '@mui/icons-material';
|
||||
import axios from 'axios';
|
||||
import io from 'socket.io-client';
|
||||
|
||||
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000';
|
||||
const CHAT_URL = import.meta.env.VITE_CHAT_URL || 'http://localhost:8001';
|
||||
|
||||
function App() {
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
|
||||
return (
|
||||
<Box sx={{ flexGrow: 1 }}>
|
||||
<AppBar position="static">
|
||||
<Toolbar>
|
||||
<SupportIcon sx={{ mr: 2 }} />
|
||||
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
|
||||
Datacenter Documentation System
|
||||
</Typography>
|
||||
<Chip label="AI Powered" color="secondary" size="small" />
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
|
||||
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
||||
<Tabs value={activeTab} onChange={(e, v) => setActiveTab(v)}>
|
||||
<Tab label="Chat Support" />
|
||||
<Tab label="Ticket Resolution" />
|
||||
<Tab label="Documentation Search" />
|
||||
</Tabs>
|
||||
</Box>
|
||||
|
||||
<Container maxWidth="xl" sx={{ mt: 4, mb: 4 }}>
|
||||
{activeTab === 0 && <ChatInterface />}
|
||||
{activeTab === 1 && <TicketInterface />}
|
||||
{activeTab === 2 && <SearchInterface />}
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// Chat Interface Component
|
||||
function ChatInterface() {
|
||||
const [messages, setMessages] = useState([]);
|
||||
const [input, setInput] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [socket, setSocket] = useState(null);
|
||||
const messagesEndRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const newSocket = io(CHAT_URL);
|
||||
setSocket(newSocket);
|
||||
|
||||
newSocket.on('message', (data) => {
|
||||
setMessages(prev => [...prev, {
|
||||
role: 'assistant',
|
||||
content: data.message,
|
||||
related_docs: data.related_docs,
|
||||
timestamp: new Date()
|
||||
}]);
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
return () => newSocket.close();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
}, [messages]);
|
||||
|
||||
const sendMessage = () => {
|
||||
if (!input.trim() || !socket) return;
|
||||
|
||||
const userMessage = {
|
||||
role: 'user',
|
||||
content: input,
|
||||
timestamp: new Date()
|
||||
};
|
||||
|
||||
setMessages(prev => [...prev, userMessage]);
|
||||
setLoading(true);
|
||||
|
||||
socket.emit('chat', { message: input, history: messages });
|
||||
setInput('');
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} md={8}>
|
||||
<Paper sx={{ height: '70vh', display: 'flex', flexDirection: 'column' }}>
|
||||
<Box sx={{ p: 2, bgcolor: 'primary.main', color: 'white' }}>
|
||||
<Typography variant="h6">Technical Support Chat</Typography>
|
||||
<Typography variant="caption">
|
||||
AI-powered assistant with access to datacenter documentation
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ flexGrow: 1, overflow: 'auto', p: 2 }}>
|
||||
<List>
|
||||
{messages.map((msg, idx) => (
|
||||
<ListItem key={idx} sx={{
|
||||
justifyContent: msg.role === 'user' ? 'flex-end' : 'flex-start'
|
||||
}}>
|
||||
<Paper sx={{
|
||||
p: 2,
|
||||
maxWidth: '70%',
|
||||
bgcolor: msg.role === 'user' ? 'primary.light' : 'grey.100'
|
||||
}}>
|
||||
<Typography variant="body1">{msg.content}</Typography>
|
||||
{msg.related_docs && (
|
||||
<Box sx={{ mt: 1 }}>
|
||||
{msg.related_docs.map((doc, i) => (
|
||||
<Chip key={i} label={doc.section} size="small" sx={{ mr: 0.5 }} />
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{msg.timestamp.toLocaleTimeString()}
|
||||
</Typography>
|
||||
</Paper>
|
||||
</ListItem>
|
||||
))}
|
||||
{loading && (
|
||||
<ListItem>
|
||||
<CircularProgress size={24} />
|
||||
<Typography sx={{ ml: 2 }}>AI is searching documentation...</Typography>
|
||||
</ListItem>
|
||||
)}
|
||||
<div ref={messagesEndRef} />
|
||||
</List>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ p: 2, borderTop: 1, borderColor: 'divider' }}>
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<TextField
|
||||
fullWidth
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
|
||||
placeholder="Ask about infrastructure, procedures, troubleshooting..."
|
||||
disabled={loading}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={sendMessage}
|
||||
disabled={loading || !input.trim()}
|
||||
endIcon={<SendIcon />}
|
||||
>
|
||||
Send
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} md={4}>
|
||||
<Paper sx={{ p: 2 }}>
|
||||
<Typography variant="h6" gutterBottom>Quick Actions</Typography>
|
||||
<Button fullWidth variant="outlined" startIcon={<UploadIcon />} sx={{ mb: 1 }}>
|
||||
Upload Ticket
|
||||
</Button>
|
||||
<Button fullWidth variant="outlined" startIcon={<DocIcon />}>
|
||||
Browse Documentation
|
||||
</Button>
|
||||
|
||||
<Divider sx={{ my: 2 }} />
|
||||
|
||||
<Typography variant="subtitle2" gutterBottom>Example Questions</Typography>
|
||||
<List dense>
|
||||
<ListItem button onClick={() => setInput('How do I check UPS status?')}>
|
||||
<ListItemText primary="How do I check UPS status?" />
|
||||
</ListItem>
|
||||
<ListItem button onClick={() => setInput('What are the backup schedules?')}>
|
||||
<ListItemText primary="What are the backup schedules?" />
|
||||
</ListItem>
|
||||
<ListItem button onClick={() => setInput('How to troubleshoot VLAN connectivity?')}>
|
||||
<ListItemText primary="Troubleshoot VLAN issues" />
|
||||
</ListItem>
|
||||
</List>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
// Ticket Resolution Interface
|
||||
function TicketInterface() {
|
||||
const [ticketData, setTicketData] = useState({
|
||||
ticket_id: '',
|
||||
title: '',
|
||||
description: '',
|
||||
priority: 'medium',
|
||||
category: ''
|
||||
});
|
||||
const [result, setResult] = useState(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const submitTicket = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await axios.post(`${API_URL}/api/v1/tickets`, ticketData);
|
||||
setResult(response.data);
|
||||
|
||||
// Poll for resolution
|
||||
const ticketId = response.data.ticket_id;
|
||||
const pollInterval = setInterval(async () => {
|
||||
const statusResponse = await axios.get(`${API_URL}/api/v1/tickets/${ticketId}`);
|
||||
if (statusResponse.data.status === 'resolved') {
|
||||
setResult(statusResponse.data);
|
||||
clearInterval(pollInterval);
|
||||
setLoading(false);
|
||||
}
|
||||
}, 2000);
|
||||
} catch (error) {
|
||||
console.error('Error submitting ticket:', error);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Paper sx={{ p: 3 }}>
|
||||
<Typography variant="h6" gutterBottom>Submit Ticket for Auto-Resolution</Typography>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Ticket ID"
|
||||
value={ticketData.ticket_id}
|
||||
onChange={(e) => setTicketData({...ticketData, ticket_id: e.target.value})}
|
||||
margin="normal"
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Title"
|
||||
value={ticketData.title}
|
||||
onChange={(e) => setTicketData({...ticketData, title: e.target.value})}
|
||||
margin="normal"
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
multiline
|
||||
rows={4}
|
||||
label="Description"
|
||||
value={ticketData.description}
|
||||
onChange={(e) => setTicketData({...ticketData, description: e.target.value})}
|
||||
margin="normal"
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
select
|
||||
label="Category"
|
||||
value={ticketData.category}
|
||||
onChange={(e) => setTicketData({...ticketData, category: e.target.value})}
|
||||
margin="normal"
|
||||
SelectProps={{ native: true }}
|
||||
>
|
||||
<option value="">Select...</option>
|
||||
<option value="network">Network</option>
|
||||
<option value="server">Server</option>
|
||||
<option value="storage">Storage</option>
|
||||
<option value="security">Security</option>
|
||||
<option value="backup">Backup</option>
|
||||
</TextField>
|
||||
<Button
|
||||
fullWidth
|
||||
variant="contained"
|
||||
onClick={submitTicket}
|
||||
disabled={loading}
|
||||
sx={{ mt: 2 }}
|
||||
>
|
||||
{loading ? <CircularProgress size={24} /> : 'Submit & Auto-Resolve'}
|
||||
</Button>
|
||||
</Paper>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} md={6}>
|
||||
{result && (
|
||||
<Paper sx={{ p: 3 }}>
|
||||
<Typography variant="h6" gutterBottom>Resolution</Typography>
|
||||
<Chip
|
||||
label={result.status}
|
||||
color={result.status === 'resolved' ? 'success' : 'warning'}
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
<Typography variant="body1" paragraph>{result.resolution}</Typography>
|
||||
|
||||
<Typography variant="subtitle2" gutterBottom>Suggested Actions:</Typography>
|
||||
<List dense>
|
||||
{result.suggested_actions?.map((action, idx) => (
|
||||
<ListItem key={idx}>
|
||||
<ListItemText primary={`${idx + 1}. ${action}`} />
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<Typography variant="caption">
|
||||
Confidence Score: {(result.confidence_score * 100).toFixed(0)}% |
|
||||
Processing Time: {result.processing_time?.toFixed(2)}s
|
||||
</Typography>
|
||||
</Box>
|
||||
</Paper>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
// Search Interface
|
||||
function SearchInterface() {
|
||||
const [query, setQuery] = useState('');
|
||||
const [results, setResults] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const search = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await axios.post(`${API_URL}/api/v1/documentation/search`, {
|
||||
query,
|
||||
limit: 10
|
||||
});
|
||||
setResults(response.data);
|
||||
} catch (error) {
|
||||
console.error('Search error:', error);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Paper sx={{ p: 2, mb: 3 }}>
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<TextField
|
||||
fullWidth
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
onKeyPress={(e) => e.key === 'Enter' && search()}
|
||||
placeholder="Search documentation..."
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={search}
|
||||
disabled={loading}
|
||||
startIcon={<SearchIcon />}
|
||||
>
|
||||
Search
|
||||
</Button>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
{loading && <CircularProgress />}
|
||||
|
||||
<Grid container spacing={2}>
|
||||
{results.map((result, idx) => (
|
||||
<Grid item xs={12} key={idx}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="h6">{result.title}</Typography>
|
||||
<Chip label={result.section} size="small" sx={{ mb: 1 }} />
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{result.content}
|
||||
</Typography>
|
||||
<Typography variant="caption">
|
||||
Relevance: {(result.relevance_score * 100).toFixed(0)}%
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
668
frontend/src/App_Enhanced.jsx
Normal file
668
frontend/src/App_Enhanced.jsx
Normal file
@@ -0,0 +1,668 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
AppBar, Toolbar, Typography, Container, Box, Paper,
|
||||
TextField, Button, List, ListItem, ListItemText,
|
||||
CircularProgress, Chip, Grid, Card, CardContent,
|
||||
Tabs, Tab, Divider, IconButton, Switch, FormControlLabel,
|
||||
Alert, AlertTitle, Dialog, DialogTitle, DialogContent,
|
||||
DialogActions, Rating, LinearProgress, Tooltip
|
||||
} from '@mui/material';
|
||||
import {
|
||||
Send as SendIcon,
|
||||
ThumbUp, ThumbDown, Warning as WarningIcon,
|
||||
CheckCircle, Info, Shield, Speed, TrendingUp
|
||||
} from '@mui/icons-material';
|
||||
import axios from 'axios';
|
||||
|
||||
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000';
|
||||
|
||||
function App() {
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
|
||||
return (
|
||||
<Box sx={{ flexGrow: 1 }}>
|
||||
<AppBar position="static" sx={{ bgcolor: '#1976d2' }}>
|
||||
<Toolbar>
|
||||
<Shield sx={{ mr: 2 }} />
|
||||
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
|
||||
Datacenter AI System - Auto-Remediation Enabled
|
||||
</Typography>
|
||||
<Chip label="AI Powered" color="secondary" size="small" sx={{ mr: 1 }} />
|
||||
<Chip label="v2.0" color="success" size="small" />
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
|
||||
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
||||
<Tabs value={activeTab} onChange={(e, v) => setActiveTab(v)}>
|
||||
<Tab label="Submit Ticket" />
|
||||
<Tab label="Ticket Status" />
|
||||
<Tab label="Feedback Center" />
|
||||
<Tab label="Analytics" />
|
||||
</Tabs>
|
||||
</Box>
|
||||
|
||||
<Container maxWidth="xl" sx={{ mt: 4, mb: 4 }}>
|
||||
{activeTab === 0 && <TicketSubmitInterface />}
|
||||
{activeTab === 1 && <TicketStatusInterface />}
|
||||
{activeTab === 2 && <FeedbackCenter />}
|
||||
{activeTab === 3 && <AnalyticsDashboard />}
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// Ticket Submit Interface with Auto-Remediation Toggle
|
||||
function TicketSubmitInterface() {
|
||||
const [ticketData, setTicketData] = useState({
|
||||
ticket_id: '',
|
||||
title: '',
|
||||
description: '',
|
||||
priority: 'medium',
|
||||
category: '',
|
||||
enable_auto_remediation: false // DEFAULT: DISABLED
|
||||
});
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [result, setResult] = useState(null);
|
||||
const [showWarning, setShowWarning] = useState(false);
|
||||
|
||||
const submitTicket = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await axios.post(`${API_URL}/api/v1/tickets`, ticketData);
|
||||
setResult(response.data);
|
||||
|
||||
// Poll for updates
|
||||
const ticketId = response.data.ticket_id;
|
||||
const pollInterval = setInterval(async () => {
|
||||
const statusResponse = await axios.get(`${API_URL}/api/v1/tickets/${ticketId}`);
|
||||
setResult(statusResponse.data);
|
||||
|
||||
if (statusResponse.data.status === 'resolved' ||
|
||||
statusResponse.data.status === 'failed') {
|
||||
clearInterval(pollInterval);
|
||||
setLoading(false);
|
||||
}
|
||||
}, 3000);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Paper sx={{ p: 3 }}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Submit Ticket for AI Resolution
|
||||
</Typography>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Ticket ID"
|
||||
value={ticketData.ticket_id}
|
||||
onChange={(e) => setTicketData({...ticketData, ticket_id: e.target.value})}
|
||||
margin="normal"
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Title"
|
||||
value={ticketData.title}
|
||||
onChange={(e) => setTicketData({...ticketData, title: e.target.value})}
|
||||
margin="normal"
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
multiline
|
||||
rows={4}
|
||||
label="Problem Description"
|
||||
value={ticketData.description}
|
||||
onChange={(e) => setTicketData({...ticketData, description: e.target.value})}
|
||||
margin="normal"
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
select
|
||||
label="Category"
|
||||
value={ticketData.category}
|
||||
onChange={(e) => setTicketData({...ticketData, category: e.target.value})}
|
||||
margin="normal"
|
||||
SelectProps={{ native: true }}
|
||||
>
|
||||
<option value="">Select...</option>
|
||||
<option value="network">Network</option>
|
||||
<option value="server">Server</option>
|
||||
<option value="storage">Storage</option>
|
||||
<option value="security">Security</option>
|
||||
<option value="backup">Backup</option>
|
||||
</TextField>
|
||||
|
||||
<Divider sx={{ my: 2 }} />
|
||||
|
||||
<Box sx={{ bgcolor: '#fff3e0', p: 2, borderRadius: 1, mb: 2 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
|
||||
<WarningIcon color="warning" sx={{ mr: 1 }} />
|
||||
<Typography variant="subtitle2" color="warning.dark">
|
||||
Auto-Remediation Control
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={ticketData.enable_auto_remediation}
|
||||
onChange={(e) => {
|
||||
setTicketData({
|
||||
...ticketData,
|
||||
enable_auto_remediation: e.target.checked
|
||||
});
|
||||
if (e.target.checked) setShowWarning(true);
|
||||
}}
|
||||
color="warning"
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<Typography variant="body2">
|
||||
Enable Auto-Remediation (Write Operations)
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
|
||||
<Typography variant="caption" display="block" color="text.secondary">
|
||||
When enabled, AI can automatically execute fixes on your infrastructure.
|
||||
Default: DISABLED for safety. Only enable if you trust AI decisions.
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Button
|
||||
fullWidth
|
||||
variant="contained"
|
||||
onClick={submitTicket}
|
||||
disabled={loading}
|
||||
sx={{ mt: 2 }}
|
||||
>
|
||||
{loading ? <CircularProgress size={24} /> : 'Submit Ticket'}
|
||||
</Button>
|
||||
</Paper>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} md={6}>
|
||||
{result && <TicketResultDisplay result={result} />}
|
||||
</Grid>
|
||||
|
||||
{/* Warning Dialog */}
|
||||
<Dialog open={showWarning} onClose={() => setShowWarning(false)}>
|
||||
<DialogTitle>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<WarningIcon color="warning" sx={{ mr: 1 }} />
|
||||
Auto-Remediation Warning
|
||||
</Box>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Alert severity="warning" sx={{ mb: 2 }}>
|
||||
You are enabling auto-remediation. This means:
|
||||
</Alert>
|
||||
<List dense>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary="✓ AI can execute WRITE operations on your infrastructure"
|
||||
secondary="Includes: restarting services, modifying configs, scaling resources"
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary="✓ Actions are based on reliability scores and learned patterns"
|
||||
secondary="Only high-confidence actions with ≥85% reliability are auto-executed"
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary="✓ All actions are logged and can be rolled back"
|
||||
secondary="Safety checks and pre/post validation included"
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary="⚠️ Critical actions require human approval"
|
||||
secondary="Destructive operations always need manual confirmation"
|
||||
/>
|
||||
</ListItem>
|
||||
</List>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setShowWarning(false)}>
|
||||
I Understand
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
// Enhanced Ticket Result Display
|
||||
function TicketResultDisplay({ result }) {
|
||||
const getConfidenceColor = (level) => {
|
||||
const colors = {
|
||||
'very_high': 'success',
|
||||
'high': 'info',
|
||||
'medium': 'warning',
|
||||
'low': 'error'
|
||||
};
|
||||
return colors[level] || 'default';
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper sx={{ p: 3 }}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Resolution & Analysis
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Chip
|
||||
label={result.status}
|
||||
color={result.status === 'resolved' ? 'success' : 'warning'}
|
||||
sx={{ mr: 1 }}
|
||||
/>
|
||||
{result.auto_remediation_enabled && (
|
||||
<Chip
|
||||
icon={<Shield />}
|
||||
label="Auto-Remediation Enabled"
|
||||
color="warning"
|
||||
size="small"
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Reliability Scores */}
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" gutterBottom>
|
||||
AI Confidence & Reliability
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 0.5 }}>
|
||||
<Typography variant="body2">AI Confidence</Typography>
|
||||
<Typography variant="body2" fontWeight="bold">
|
||||
{(result.confidence_score * 100).toFixed(0)}%
|
||||
</Typography>
|
||||
</Box>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={result.confidence_score * 100}
|
||||
color="primary"
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{result.reliability_score && (
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 0.5 }}>
|
||||
<Typography variant="body2">Reliability Score</Typography>
|
||||
<Chip
|
||||
label={result.confidence_level || 'calculating'}
|
||||
color={getConfidenceColor(result.confidence_level)}
|
||||
size="small"
|
||||
/>
|
||||
</Box>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={result.reliability_score}
|
||||
color={result.reliability_score >= 85 ? 'success' : 'warning'}
|
||||
/>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
Based on: AI confidence, historical success, feedback, patterns
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Resolution */}
|
||||
<Typography variant="subtitle2" gutterBottom>Resolution:</Typography>
|
||||
<Typography variant="body1" paragraph>{result.resolution}</Typography>
|
||||
|
||||
{/* Suggested Actions */}
|
||||
{result.suggested_actions?.length > 0 && (
|
||||
<>
|
||||
<Typography variant="subtitle2" gutterBottom>Suggested Actions:</Typography>
|
||||
<List dense>
|
||||
{result.suggested_actions.map((action, idx) => (
|
||||
<ListItem key={idx}>
|
||||
<ListItemText primary={`${idx + 1}. ${action.action || action}`} />
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Auto-Remediation Status */}
|
||||
{result.auto_remediation_enabled && (
|
||||
<Alert
|
||||
severity={result.auto_remediation_executed ? 'success' : 'info'}
|
||||
sx={{ mt: 2 }}
|
||||
>
|
||||
<AlertTitle>
|
||||
{result.auto_remediation_executed ? 'Auto-Remediation Executed' : 'Auto-Remediation Status'}
|
||||
</AlertTitle>
|
||||
{result.remediation_decision && (
|
||||
<Typography variant="body2">
|
||||
{result.remediation_decision.allowed
|
||||
? `✓ Actions approved for execution (${result.remediation_decision.action_type})`
|
||||
: `✗ Actions require manual intervention: ${result.remediation_decision.reasoning.join(', ')}`
|
||||
}
|
||||
</Typography>
|
||||
)}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<Typography variant="caption">
|
||||
Processing Time: {result.processing_time?.toFixed(2)}s
|
||||
</Typography>
|
||||
</Box>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
// Ticket Status Interface
|
||||
function TicketStatusInterface() {
|
||||
const [ticketId, setTicketId] = useState('');
|
||||
const [ticket, setTicket] = useState(null);
|
||||
const [logs, setLogs] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const fetchTicket = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await axios.get(`${API_URL}/api/v1/tickets/${ticketId}`);
|
||||
setTicket(response.data);
|
||||
|
||||
// Fetch logs if auto-remediation was executed
|
||||
if (response.data.auto_remediation_executed) {
|
||||
const logsResponse = await axios.get(`${API_URL}/api/v1/tickets/${ticketId}/remediation-logs`);
|
||||
setLogs(logsResponse.data.logs);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<Paper sx={{ p: 2 }}>
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<TextField
|
||||
fullWidth
|
||||
value={ticketId}
|
||||
onChange={(e) => setTicketId(e.target.value)}
|
||||
placeholder="Enter Ticket ID"
|
||||
/>
|
||||
<Button variant="contained" onClick={fetchTicket} disabled={loading}>
|
||||
Fetch
|
||||
</Button>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Grid>
|
||||
|
||||
{ticket && (
|
||||
<>
|
||||
<Grid item xs={12} md={8}>
|
||||
<TicketResultDisplay result={ticket} />
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} md={4}>
|
||||
<FeedbackForm ticketId={ticketId} />
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
|
||||
{logs.length > 0 && (
|
||||
<Grid item xs={12}>
|
||||
<RemediationLogsDisplay logs={logs} />
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
// Feedback Form
|
||||
function FeedbackForm({ ticketId }) {
|
||||
const [feedback, setFeedback] = useState({
|
||||
feedback_type: 'positive',
|
||||
rating: 5,
|
||||
was_helpful: true,
|
||||
resolution_accurate: true,
|
||||
actions_worked: true,
|
||||
comment: ''
|
||||
});
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
|
||||
const submitFeedback = async () => {
|
||||
try {
|
||||
await axios.post(`${API_URL}/api/v1/feedback`, {
|
||||
ticket_id: ticketId,
|
||||
...feedback
|
||||
});
|
||||
setSubmitted(true);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper sx={{ p: 3 }}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
<ThumbUp sx={{ mr: 1, verticalAlign: 'middle' }} />
|
||||
Provide Feedback
|
||||
</Typography>
|
||||
|
||||
{submitted ? (
|
||||
<Alert severity="success">
|
||||
<AlertTitle>Thank You!</AlertTitle>
|
||||
Your feedback helps improve the AI system.
|
||||
</Alert>
|
||||
) : (
|
||||
<>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
Was this resolution helpful?
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ my: 2 }}>
|
||||
<Rating
|
||||
value={feedback.rating}
|
||||
onChange={(e, value) => setFeedback({...feedback, rating: value})}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={feedback.resolution_accurate}
|
||||
onChange={(e) => setFeedback({
|
||||
...feedback,
|
||||
resolution_accurate: e.target.checked,
|
||||
feedback_type: e.target.checked ? 'positive' : 'negative'
|
||||
})}
|
||||
/>
|
||||
}
|
||||
label="Resolution was accurate"
|
||||
/>
|
||||
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={feedback.actions_worked}
|
||||
onChange={(e) => setFeedback({...feedback, actions_worked: e.target.checked})}
|
||||
/>
|
||||
}
|
||||
label="Suggested actions worked"
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
multiline
|
||||
rows={3}
|
||||
label="Comments (optional)"
|
||||
value={feedback.comment}
|
||||
onChange={(e) => setFeedback({...feedback, comment: e.target.value})}
|
||||
margin="normal"
|
||||
/>
|
||||
|
||||
<Button
|
||||
fullWidth
|
||||
variant="contained"
|
||||
onClick={submitFeedback}
|
||||
sx={{ mt: 2 }}
|
||||
>
|
||||
Submit Feedback
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
// Remediation Logs Display
|
||||
function RemediationLogsDisplay({ logs }) {
|
||||
return (
|
||||
<Paper sx={{ p: 3 }}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Auto-Remediation Execution Logs
|
||||
</Typography>
|
||||
|
||||
<List>
|
||||
{logs.map((log, idx) => (
|
||||
<ListItem key={idx} divider>
|
||||
<ListItemText
|
||||
primary={
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
{log.success ?
|
||||
<CheckCircle color="success" fontSize="small" /> :
|
||||
<WarningIcon color="error" fontSize="small" />
|
||||
}
|
||||
<Typography variant="body2">{log.action}</Typography>
|
||||
<Chip label={log.type} size="small" />
|
||||
</Box>
|
||||
}
|
||||
secondary={
|
||||
<>
|
||||
<Typography variant="caption" display="block">
|
||||
Target: {log.target_system} / {log.target_resource}
|
||||
</Typography>
|
||||
<Typography variant="caption" display="block">
|
||||
Executed: {new Date(log.executed_at).toLocaleString()}
|
||||
</Typography>
|
||||
{log.error && (
|
||||
<Typography variant="caption" color="error" display="block">
|
||||
Error: {log.error}
|
||||
</Typography>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
// Feedback Center
|
||||
function FeedbackCenter() {
|
||||
// Implementation for viewing all feedback and metrics
|
||||
return (
|
||||
<Paper sx={{ p: 3 }}>
|
||||
<Typography variant="h6">Feedback Center</Typography>
|
||||
<Typography variant="body2">
|
||||
View all feedback, improve AI accuracy, and track pattern learning.
|
||||
</Typography>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
// Analytics Dashboard
|
||||
function AnalyticsDashboard() {
|
||||
const [stats, setStats] = useState(null);
|
||||
const [autoRemStats, setAutoRemStats] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchStats();
|
||||
}, []);
|
||||
|
||||
const fetchStats = async () => {
|
||||
try {
|
||||
const [reliability, autoRem] = await Promise.all([
|
||||
axios.get(`${API_URL}/api/v1/stats/reliability`),
|
||||
axios.get(`${API_URL}/api/v1/stats/auto-remediation`)
|
||||
]);
|
||||
setStats(reliability.data);
|
||||
setAutoRemStats(autoRem.data);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} md={3}>
|
||||
<StatCard
|
||||
title="Avg Reliability"
|
||||
value={`${stats?.avg_reliability || 0}%`}
|
||||
icon={<Speed />}
|
||||
color="primary"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={3}>
|
||||
<StatCard
|
||||
title="Avg Confidence"
|
||||
value={`${stats?.avg_confidence || 0}%`}
|
||||
icon={<TrendingUp />}
|
||||
color="success"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={3}>
|
||||
<StatCard
|
||||
title="Resolution Rate"
|
||||
value={`${stats?.resolution_rate || 0}%`}
|
||||
icon={<CheckCircle />}
|
||||
color="info"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={3}>
|
||||
<StatCard
|
||||
title="Auto-Rem Success"
|
||||
value={`${autoRemStats?.success_rate || 0}%`}
|
||||
icon={<Shield />}
|
||||
color="warning"
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
function StatCard({ title, value, icon, color }) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Box>
|
||||
<Typography color="text.secondary" gutterBottom variant="body2">
|
||||
{title}
|
||||
</Typography>
|
||||
<Typography variant="h4">
|
||||
{value}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ color: `${color}.main` }}>
|
||||
{icon}
|
||||
</Box>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
Reference in New Issue
Block a user