Compare commits

..

No commits in common. "08fde58a30932baa8c93bf3f784fb1309d7ee24f" and "d7c40ee04b7019162b86c72f806c70ceb02e7508" have entirely different histories.

10 changed files with 495 additions and 16396 deletions

16693
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -110,7 +110,7 @@ function App() {
const handleLogout = async () => { const handleLogout = async () => {
try { try {
await axios.post(`/api/auth/logout`, null, { await axios.post(`${import.meta.env.VITE_BACK_URL}/api/auth/logout`, null, {
withCredentials: true, withCredentials: true,
}); });

View File

@ -7,7 +7,7 @@ class MetricsService {
this.subscriptions = new Map(); this.subscriptions = new Map();
this.pendingRequests = new Map(); this.pendingRequests = new Map();
window.addEventListener('beforeunload', this.cleanupAll.bind(this)); window.addEventListener('beforeunload', this.cleanupAll.bind(this));
window.addEventListener('pagehide', this.cleanupAll.bind(this)); window.addEventListener('pagehide', this.cleanupAll.bind(this));
window.addEventListener('beforeunload', () => { window.addEventListener('beforeunload', () => {
this.cleanupAll(); this.cleanupAll();
@ -42,6 +42,7 @@ class MetricsService {
}); });
this.socket.on('metrics-data', ({ metric, data, requestId }) => { this.socket.on('metrics-data', ({ metric, data, requestId }) => {
console.log('Incoming metric update:', metric);
if (requestId && this.pendingRequests.has(requestId)) { if (requestId && this.pendingRequests.has(requestId)) {
const { resolve } = this.pendingRequests.get(requestId); const { resolve } = this.pendingRequests.get(requestId);
resolve(data); resolve(data);

View File

@ -115,7 +115,7 @@ const MetricRangeEditor = ({ onSave }) => {
const loadRanges = useCallback(async () => { const loadRanges = useCallback(async () => {
try { try {
setLoading(true); setLoading(true);
const res = await axios.get(`/api/ranges/list`); const res = await axios.get(`${import.meta.env.VITE_BACK_URL}/api/ranges/list`);
setRanges( setRanges(
Object.entries(res.data).map(([name, r]) => ({ Object.entries(res.data).map(([name, r]) => ({
name, name,
@ -184,7 +184,7 @@ const MetricRangeEditor = ({ onSave }) => {
const saveChanges = useCallback(async () => { const saveChanges = useCallback(async () => {
try { try {
setLoading(true); setLoading(true);
await axios.post(`/api/ranges/update`, ranges); await axios.post(`${import.meta.env.VITE_BACK_URL}/api/ranges/update`, ranges);
setHasChanges(false); setHasChanges(false);
setSuccess(true); setSuccess(true);
setTimeout(() => setSuccess(false), 3000); setTimeout(() => setSuccess(false), 3000);

View File

@ -44,7 +44,7 @@ const SidebarMenuWrapper = ({ isDarkMode, setIsDarkMode, onMenuSelect, user }) =
setLoading(true); setLoading(true);
const headers = lastModified ? { 'If-Modified-Since': lastModified } : {}; const headers = lastModified ? { 'If-Modified-Since': lastModified } : {};
const response = await axios.get(`/api/menu/full`, { const response = await axios.get(`${import.meta.env.VITE_BACK_URL}/api/menu/full`, {
headers, headers,
validateStatus: status => status === 200 || status === 304 validateStatus: status => status === 200 || status === 304
}); });
@ -78,13 +78,13 @@ const SidebarMenuWrapper = ({ isDarkMode, setIsDarkMode, onMenuSelect, user }) =
const checkForUpdates = async () => { const checkForUpdates = async () => {
try { try {
setBackgroundLoading(true); setBackgroundLoading(true);
const response = await axios.get(`/api/menu/check-updates`, { const response = await axios.get(`${import.meta.env.VITE_BACK_URL}/api/menu/check-updates`, {
headers: { 'If-Modified-Since': lastModified } headers: { 'If-Modified-Since': lastModified }
}); });
if (response.data.hasUpdates) { if (response.data.hasUpdates) {
// Если есть обновления, загружаем их в фоне // Если есть обновления, загружаем их в фоне
const updateResponse = await axios.get(`/api/menu/full`); const updateResponse = await axios.get(`${import.meta.env.VITE_BACK_URL}/api/menu/full`);
setMenuData(updateResponse.data); setMenuData(updateResponse.data);
setLastModified(updateResponse.headers['last-modified']); setLastModified(updateResponse.headers['last-modified']);
@ -108,7 +108,7 @@ const SidebarMenuWrapper = ({ isDarkMode, setIsDarkMode, onMenuSelect, user }) =
const handleSaveChanges = async (updatedItem) => { const handleSaveChanges = async (updatedItem) => {
try { try {
const response = await axios.put( const response = await axios.put(
`/api/menu/${updatedItem.id}`, `${import.meta.env.VITE_BACK_URL}/api/menu/${updatedItem.id}`,
updatedItem, updatedItem,
{ {
headers: { headers: {

View File

@ -1,25 +1,65 @@
/*import React, { useState } from 'react';
import { Button, CircularProgress, Alert, Box } from '@mui/material';
import axios from 'axios';
const AIAnalysisButton = ({ onAnalysisComplete }) => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [result, setResult] = useState(null);
const handleAnalyze = async () => {
setLoading(true);
setError(null);
try {
const response = await axios.post('/api/clickhouse/send-to-ai');
setResult(response.data);
if (onAnalysisComplete) {
onAnalysisComplete(response.data);
}
} catch (err) {
setError(err.response?.data?.message || err.message);
} finally {
setLoading(false);
}
};
return (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<Button
variant="contained"
color="primary"
onClick={handleAnalyze}
disabled={loading}
startIcon={loading ? <CircularProgress size={20} /> : null}
sx={{
minWidth: '180px',
backgroundColor: '#4caf50',
'&:hover': {
backgroundColor: '#388e3c',
}
}}
>
{loading ? 'Analyzing...' : 'AI Analysis'}
</Button>
{error && (
<Alert severity="error" sx={{ mt: 1 }}>{error}</Alert>
)}
</Box>
);
};
export default AIAnalysisButton; */
import React, { useState } from 'react'; import React, { useState } from 'react';
import { import { Button, CircularProgress, Alert, Box } from '@mui/material';
Button,
CircularProgress,
Alert,
Box,
Dialog,
DialogTitle,
DialogContent,
DialogContentText,
DialogActions,
Typography,
IconButton
} from '@mui/material';
import CloseIcon from '@mui/icons-material/Close';
import axios from 'axios'; import axios from 'axios';
const AIAnalysisButton = ({ onAnalysisComplete }) => { const AIAnalysisButton = ({ onAnalysisComplete }) => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState(null); const [error, setError] = useState(null);
const [result, setResult] = useState(null); const [result, setResult] = useState(null);
const [openModal, setOpenModal] = useState(false);
const handleAnalyze = async () => { const handleAnalyze = async () => {
setLoading(true); setLoading(true);
@ -28,12 +68,9 @@ const AIAnalysisButton = ({ onAnalysisComplete }) => {
try { try {
// 1. Получаем данные из ClickHouse // 1. Получаем данные из ClickHouse
console.log('Запрашиваем данные из ClickHouse...');
const metricsResponse = await axios.get('/api/clickhouse'); const metricsResponse = await axios.get('/api/clickhouse');
console.log('Получены данные из ClickHouse:', metricsResponse.data);
// 2. Отправляем в AI API // 2. Отправляем в AI API
console.log('Отправляем данные в AI API:', metricsResponse.data);
const aiResponse = await axios.post( const aiResponse = await axios.post(
'/ai-api/api/metrics/rest', '/ai-api/api/metrics/rest',
metricsResponse.data, metricsResponse.data,
@ -43,23 +80,20 @@ const AIAnalysisButton = ({ onAnalysisComplete }) => {
}, },
} }
); );
console.log('Ответ от AI API:', aiResponse.data);
setResult(aiResponse.data); setResult(aiResponse.data);
setOpenModal(true);
if (onAnalysisComplete) { if (onAnalysisComplete) {
onAnalysisComplete(aiResponse.data); onAnalysisComplete(aiResponse.data);
} }
} catch (err) { } catch (err) {
console.error("Детали ошибки:", err.response?.data); console.error("Детали ошибки 422:", err.response?.data);
setError(err.response?.data?.message || JSON.stringify(err.response?.data)) || "Ошибка при анализе данных"; setError(err.response?.data?.message || JSON.stringify(err.response?.data) || "Ошибка валидации данных");
} finally { } finally {
setLoading(false); setLoading(false);
} }
};
const handleCloseModal = () => {
setOpenModal(false);
}; };
return ( return (
@ -87,63 +121,11 @@ const AIAnalysisButton = ({ onAnalysisComplete }) => {
</Alert> </Alert>
)} )}
{result && !loading && (
<Alert severity="success" sx={{ mt: 1 }}>
{/* Модальное окно с результатом */} Анализ завершен! Результат в консоли.
<Dialog </Alert>
open={openModal} )}
onClose={handleCloseModal}
fullWidth={true}
maxWidth="lg"
scroll="paper"
>
<DialogTitle sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
Результат AI-анализа
<IconButton
aria-label="close"
onClick={handleCloseModal}
sx={{
color: (theme) => theme.palette.grey[500],
}}
>
<CloseIcon />
</IconButton>
</DialogTitle>
<DialogContent dividers>
{result ? (
<>
<Typography variant="h6" gutterBottom>Данные анализа:</Typography>
<Box
component="pre"
sx={{
p: 2,
bgcolor: '#f5f5f5',
borderRadius: 1,
overflow: 'auto',
maxHeight: '60vh',
whiteSpace: 'pre-wrap',
wordWrap: 'break-word'
}}
>
{JSON.stringify(result, null, 2)}
</Box>
</>
) : (
<DialogContentText>Нет данных для отображения</DialogContentText>
)}
</DialogContent>
<DialogActions>
<Button onClick={handleCloseModal}>Закрыть</Button>
<Button
onClick={() => {
navigator.clipboard.writeText(JSON.stringify(result, null, 2));
alert('Результат скопирован в буфер обмена');
}}
>
Копировать
</Button>
</DialogActions>
</Dialog>
</Box> </Box>
); );
}; };

View File

@ -24,8 +24,7 @@ const LoginModal = ({ onLogin, onClose }) => {
e.preventDefault(); e.preventDefault();
try { try {
const { data } = await axios.post( const { data } = await axios.post(
//`${import.meta.env.VITE_BACK_URL}/api/auth/login`, `${import.meta.env.VITE_BACK_URL}/api/auth/login`,
'/api/auth/login',
{ login: username, password }, { login: username, password },
{ {
withCredentials: true, withCredentials: true,

View File

@ -3,8 +3,7 @@ import axios from "axios"
export const checkAuth = async () => { export const checkAuth = async () => {
try { try {
const { data } = await axios.get( const { data } = await axios.get(
//`${import.meta.env.VITE_BACK_URL}/api/auth/check`, `${import.meta.env.VITE_BACK_URL}/api/auth/check`,
'/api/auth/check',
{ {
withCredentials: true, withCredentials: true,
headers: { headers: {

View File

@ -44,12 +44,12 @@ const MetricsAnalyzer = () => {
setError(null); setError(null);
// 1. Сначала загружаем метрики // 1. Сначала загружаем метрики
const metricsResponse = await axios.get(`/api/metrics/all-values`); const metricsResponse = await axios.get(`${import.meta.env.VITE_BACK_URL}/api/metrics/all-values`);
setMetrics(metricsResponse.data); setMetrics(metricsResponse.data);
// 2. Преобразуем и отправляем на анализ // 2. Преобразуем и отправляем на анализ
const requestData = transformMetricsForAnalysis(metricsResponse.data); const requestData = transformMetricsForAnalysis(metricsResponse.data);
const analysisResponse = await axios.get(`:5134/api/metrics/rest`, { const analysisResponse = await axios.get(`${import.meta.env.VITE_BACK_URL}:5134/api/metrics/rest`, {
data: requestData, data: requestData,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',

View File

@ -7,16 +7,5 @@ export default defineConfig({
server: { server: {
host: true, host: true,
allowedHosts: ['dev.msf.enode', 'demo-msf.kis-npo.ru'], allowedHosts: ['dev.msf.enode', 'demo-msf.kis-npo.ru'],
proxy: {
'/ai-api': {
target: 'http://192.168.2.39:5134',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/ai-api/, ''),
},
'/api': {
target: 'http://192.168.2.39:3000',
changeOrigin: true,
}
}
} }
}); });