From 558cf8eaba69f05554548a4c319c566f4eec56a0 Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Tue, 21 Oct 2025 09:14:20 -0400 Subject: [PATCH 1/3] added formula --- .../SettingsComponents/FormulaEditor.jsx | 482 ++++++++++++++++++ src/Components/Layout/SettingsModal.jsx | 21 +- 2 files changed, 502 insertions(+), 1 deletion(-) create mode 100644 src/Components/Layout/SettingsComponents/FormulaEditor.jsx diff --git a/src/Components/Layout/SettingsComponents/FormulaEditor.jsx b/src/Components/Layout/SettingsComponents/FormulaEditor.jsx new file mode 100644 index 0000000..206a843 --- /dev/null +++ b/src/Components/Layout/SettingsComponents/FormulaEditor.jsx @@ -0,0 +1,482 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import { + TextField, Box, Typography, IconButton, Divider, + CircularProgress, Alert, Collapse, Tooltip, Button, + Card, CardContent, Chip, Dialog, DialogTitle, + DialogContent, DialogActions, Snackbar +} from '@mui/material'; +import RefreshIcon from '@mui/icons-material/Refresh'; +import SearchIcon from '@mui/icons-material/Search'; +import EditIcon from '@mui/icons-material/Edit'; +import SaveIcon from '@mui/icons-material/Save'; +import axios from 'axios'; + +const FormulaItem = React.memo(({ data, onEdit }) => { + const formatValue = (value) => { + if (typeof value === 'object' && value !== null) { + return JSON.stringify(value, null, 2); + } + return String(value); + }; + + const getValueColor = (value) => { + if (typeof value === 'boolean') return 'primary'; + if (typeof value === 'number') return 'secondary'; + if (value === null) return 'default'; + return 'info'; + }; + + return ( + + + + + ID: {data.id || 'Без ID'} + + + + + + {data.data.name} + + + + {data.data.desription} + + + + + Параметры: + + + + + + {JSON.stringify(data.data.values?.statusarr, null, 2)} + + + + + + + {JSON.stringify(data.data.values?.warr, null, 2)} + + + + + + + {data.data.formula} + + + + + + + + + + + ); +}); + +const EditFormulaDialog = ({ open, formula, onClose, onSave }) => { + const [editedFormula, setEditedFormula] = useState(''); + + useEffect(() => { + if (formula) { + setEditedFormula(formula.data.formula || ''); + } + }, [formula]); + + const handleSave = () => { + if (formula && editedFormula.trim()) { + onSave(formula.id, editedFormula.trim()); + } + }; + + return ( + + + Редактирование формулы: {formula?.data.name} + + + + {formula?.data.desription} + + + + + Доступные переменные: + + + + + + + + setEditedFormula(e.target.value)} + multiline + rows={6} + fullWidth + variant="outlined" + placeholder="Введите формулу..." + sx={{ mt: 2 }} + /> + + + + + + + ); +}; + +const FormulaEditor = () => { + const [formulas, setFormulas] = useState([]); + const [filter, setFilter] = useState(''); + const [formulaId, setFormulaId] = useState(''); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [refreshing, setRefreshing] = useState(false); + const [editingFormula, setEditingFormula] = useState(null); + const [saveLoading, setSaveLoading] = useState(false); + const [snackbar, setSnackbar] = useState({ open: false, message: '', severity: 'success' }); + + const showSnackbar = (message, severity = 'success') => { + setSnackbar({ open: true, message, severity }); + }; + + const loadFormulas = useCallback(async (id = null) => { + try { + setLoading(true); + setError(null); + + const targetId = id || formulaId; + const res = await axios.get(`http://192.168.2.39:3000/api/formula/7777/options`); + + console.log('Полученные данные:', res.data); + + let formattedData; + + if (Array.isArray(res.data)) { + formattedData = res.data.map((item, index) => ({ + id: item.id || `formula_${index + 1}`, + data: item + })); + } else if (typeof res.data === 'object' && res.data !== null) { + formattedData = [{ + id: targetId, + data: res.data + }]; + } else { + formattedData = [{ + id: targetId, + data: { value: res.data } + }]; + } + + console.log('Форматированные данные:', formattedData); + setFormulas(formattedData); + + } catch (err) { + console.error('Ошибка при загрузке формул:', err); + setError(`Ошибка загрузки: ${err.message}`); + } finally { + setLoading(false); + setRefreshing(false); + } + }, [formulaId]); + + const handleEditFormula = (formula) => { + setEditingFormula(formula); + }; + + const handleSaveFormula = async (formulaId, newFormula) => { + try { + setSaveLoading(true); + + // Обновляем формулу в локальном состоянии + const updatedFormulas = formulas.map(formula => + formula.id === formulaId + ? { ...formula, data: { ...formula.data, formula: newFormula } } + : formula + ); + + setFormulas(updatedFormulas); + setEditingFormula(null); + + showSnackbar('Формула успешно обновлена!'); + + } catch (err) { + console.error('Ошибка при сохранении формулы:', err); + showSnackbar('Ошибка при сохранении формулы', 'error'); + } finally { + setSaveLoading(false); + } + }; + + const handleSendAllFormulas = async () => { + try { + setSaveLoading(true); + + // Преобразуем данные обратно в исходный формат для отправки + const dataToSend = formulas.map(formula => ({ + id: formula.data.id, + name: formula.data.name, + desription: formula.data.desription, + values: formula.data.values, + formula: formula.data.formula + })); + + console.log('Отправляемые данные:', dataToSend); + + const response = await axios.post( + 'http://192.168.2.39:9999/api/integration/3333', + dataToSend, + { + headers: { + 'Content-Type': 'application/json' + } + } + ); + + console.log('Ответ сервера:', response.data); + showSnackbar('Данные успешно отправлены на сервер!'); + + } catch (err) { + console.error('Ошибка при отправке данных:', err); + showSnackbar(`Ошибка отправки: ${err.message}`, 'error'); + } finally { + setSaveLoading(false); + } + }; + + const refreshData = useCallback(() => { + setRefreshing(true); + loadFormulas(); + }, [loadFormulas]); + + const handleFormulaIdChange = (e) => { + setFormulaId(e.target.value); + }; + + const handleLoadClick = () => { + if (formulaId.trim()) { + loadFormulas(formulaId); + } + }; + + const filteredFormulas = formulas.filter(formula => + formula.id.toLowerCase().includes(filter.toLowerCase()) || + JSON.stringify(formula.data).toLowerCase().includes(filter.toLowerCase()) + ); + + useEffect(() => { + loadFormulas(); + }, []); + + return ( + + {/* Загрузка */} + {(loading || refreshing) && ( + + + + )} + + {/* Ошибки */} + + + Повторить + + } + > + {error} + + + + {/* Панель управления */} + + + + Редактор формул + + + + + + + + + + + + + setFilter(e.target.value)} + variant="standard" + placeholder="Введите текст для поиска..." + /> + + + + + + + {/* Список формул */} + + {filteredFormulas.map((formula, index) => ( + + ))} + + {filteredFormulas.length === 0 && !loading && ( + + {filter ? 'Формулы не найдены' : 'Загрузите формулы по ID'} + + )} + + + {/* Статус бар */} + + + Всего формул: {formulas.length} • Отфильтровано: {filteredFormulas.length} + + + + + {/* Диалог редактирования */} + setEditingFormula(null)} + onSave={handleSaveFormula} + /> + + {/* Уведомления */} + setSnackbar({ ...snackbar, open: false })} + > + setSnackbar({ ...snackbar, open: false })} + severity={snackbar.severity} + > + {snackbar.message} + + + + ); +}; + +export default React.memo(FormulaEditor); \ No newline at end of file diff --git a/src/Components/Layout/SettingsModal.jsx b/src/Components/Layout/SettingsModal.jsx index f52cf56..c8ec7a6 100644 --- a/src/Components/Layout/SettingsModal.jsx +++ b/src/Components/Layout/SettingsModal.jsx @@ -21,7 +21,8 @@ import CloseIcon from '@mui/icons-material/Close'; import SaveIcon from '@mui/icons-material/Save'; import MetricRangeEditor from './SettingsComponents/MetricRangeEditor'; import UserManagement from './SettingsComponents/UserManagement'; -import MenuEditor from './SettingsComponents/MenuEditor' +import MenuEditor from './SettingsComponents/MenuEditor'; +import FormulaEditor from './SettingsComponents/FormulaEditor'; const Transition = React.forwardRef(function Transition(props, ref) { return ; @@ -69,6 +70,10 @@ const SettingsModal = ({ open, onClose, onMenuUpdate }) => { hasChanges: false, save: () => Promise.resolve(true) }); + const [formulaEditorState, setFormulaEditorState] = useState({ + hasChanges: false, + save: () => Promise.resolve(true) + }); const handleTabChange = (event, newValue) => { if (hasChanges) { @@ -96,6 +101,10 @@ const SettingsModal = ({ open, onClose, onMenuUpdate }) => { success = success && await metricEditorState.save(); } + if (tabValue === 3 && formulaEditorState.hasChanges) { + success = success && await formulaEditorState.save(); + } + if (success) { setShowSuccess(true); setHasChanges(false); @@ -113,6 +122,11 @@ const SettingsModal = ({ open, onClose, onMenuUpdate }) => { setHasChanges(hasChanges); }; + const handleFormulaEditorChange = ({ hasChanges, saveChanges }) => { + setFormulaEditorState({ hasChanges, save: saveChanges }); + setHasChanges(hasChanges); + }; + const handleClose = () => { if (hasChanges) { setShowConfirmClose(true); @@ -163,6 +177,7 @@ const SettingsModal = ({ open, onClose, onMenuUpdate }) => { + {/* Добавить новые вкладки здесь */} @@ -180,6 +195,10 @@ const SettingsModal = ({ open, onClose, onMenuUpdate }) => { + + + + {/* Добавляйте новые TabPanel для новых вкладок */} From 14d2f3eb68477604d7f465bb6b9019cb540ce893 Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Tue, 2 Dec 2025 04:34:56 -0500 Subject: [PATCH 2/3] version update --- .gitignore | 8 +- .../SettingsComponents/FormulaEditor.jsx | 412 +++++++++--------- 2 files changed, 208 insertions(+), 212 deletions(-) diff --git a/.gitignore b/.gitignore index e7fea06..0afc875 100755 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,10 @@ node_modules .env.local .env.development .env.production -.env.test \ No newline at end of file +.env.test + +# Local configs +vite.config.js +vite.config.local.js +.env.local +*.local.* \ No newline at end of file diff --git a/src/Components/Layout/SettingsComponents/FormulaEditor.jsx b/src/Components/Layout/SettingsComponents/FormulaEditor.jsx index 206a843..830863d 100644 --- a/src/Components/Layout/SettingsComponents/FormulaEditor.jsx +++ b/src/Components/Layout/SettingsComponents/FormulaEditor.jsx @@ -3,117 +3,168 @@ import { TextField, Box, Typography, IconButton, Divider, CircularProgress, Alert, Collapse, Tooltip, Button, Card, CardContent, Chip, Dialog, DialogTitle, - DialogContent, DialogActions, Snackbar + DialogContent, DialogActions, Snackbar, Table, + TableBody, TableCell, TableContainer, TableHead, + TableRow, Paper, Badge } from '@mui/material'; import RefreshIcon from '@mui/icons-material/Refresh'; import SearchIcon from '@mui/icons-material/Search'; import EditIcon from '@mui/icons-material/Edit'; import SaveIcon from '@mui/icons-material/Save'; +import WarningIcon from '@mui/icons-material/Warning'; +import CheckCircleIcon from '@mui/icons-material/CheckCircle'; import axios from 'axios'; -const FormulaItem = React.memo(({ data, onEdit }) => { - const formatValue = (value) => { - if (typeof value === 'object' && value !== null) { - return JSON.stringify(value, null, 2); - } - return String(value); +const FormulaItem = React.memo(({ formula, onEdit }) => { + const getMetricStatusColor = (found) => { + return found ? 'success' : 'error'; }; - const getValueColor = (value) => { - if (typeof value === 'boolean') return 'primary'; - if (typeof value === 'number') return 'secondary'; - if (value === null) return 'default'; - return 'info'; + const formatValue = (value) => { + if (value === undefined) return 'N/A'; + return value.toFixed(2); }; return ( + {/* Заголовок с ID и статусом метрик */} - - ID: {data.id || 'Без ID'} - - + + + {formula.name} + + + ID: {formula.id} + + + + + + + + - - {data.data.name} - - + {/* Описание */} - {data.data.desription} + {formula.description} + {/* Метрики */} + + + Метрики в формуле: + {formula.metadata?.missingMetrics > 0 && ( + + )} + + + + + + + Метрика + Описание + Значение + Статус + + + + {formula.enrichedMetrics?.map((metric, index) => ( + + + + + {metric.originalName} + + + {metric.prometheusName} + + + + + + {metric.description} + + + + + {formatValue(metric.currentValue)} + + + + : } + label={metric.found ? 'Найдена' : 'Не найдена'} + color={getMetricStatusColor(metric.found)} + size="small" + variant={metric.found ? "filled" : "outlined"} + /> + + + ))} + +
+
+
+ + {/* Формула */} - Параметры: + Формула с описанием метрик: - + - - - {JSON.stringify(data.data.values?.statusarr, null, 2)} - - - - - - - {JSON.stringify(data.data.values?.warr, null, 2)} - - - - - - - {data.data.formula} + {formula.humanReadableFormula} - - - + {/* Веса */} + + + Веса (warr): + + + {formula.values?.warr?.map((weight, index) => ( + + ))} +
@@ -125,7 +176,7 @@ const EditFormulaDialog = ({ open, formula, onClose, onSave }) => { useEffect(() => { if (formula) { - setEditedFormula(formula.data.formula || ''); + setEditedFormula(formula.formula || ''); } }, [formula]); @@ -138,11 +189,11 @@ const EditFormulaDialog = ({ open, formula, onClose, onSave }) => { return ( - Редактирование формулы: {formula?.data.name} + Редактирование формулы: {formula?.name} - {formula?.data.desription} + {formula?.description} @@ -185,7 +236,6 @@ const EditFormulaDialog = ({ open, formula, onClose, onSave }) => { const FormulaEditor = () => { const [formulas, setFormulas] = useState([]); const [filter, setFilter] = useState(''); - const [formulaId, setFormulaId] = useState(''); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [refreshing, setRefreshing] = useState(false); @@ -197,46 +247,32 @@ const FormulaEditor = () => { setSnackbar({ open: true, message, severity }); }; - const loadFormulas = useCallback(async (id = null) => { + const loadFormulas = useCallback(async () => { try { setLoading(true); setError(null); - const targetId = id || formulaId; - const res = await axios.get(`http://192.168.2.39:3000/api/formula/7777/options`); - - console.log('Полученные данные:', res.data); - - let formattedData; - - if (Array.isArray(res.data)) { - formattedData = res.data.map((item, index) => ({ - id: item.id || `formula_${index + 1}`, - data: item - })); - } else if (typeof res.data === 'object' && res.data !== null) { - formattedData = [{ - id: targetId, - data: res.data - }]; + const response = await axios.get('http://192.168.2.39:3000/api/enriched-formulas'); + + if (Array.isArray(response.data)) { + setFormulas(response.data); + showSnackbar(`Загружено ${response.data.length} формул`); } else { - formattedData = [{ - id: targetId, - data: { value: res.data } - }]; + throw new Error('Некорректный формат данных'); } - console.log('Форматированные данные:', formattedData); - setFormulas(formattedData); - } catch (err) { console.error('Ошибка при загрузке формул:', err); - setError(`Ошибка загрузки: ${err.message}`); + const errorMessage = axios.isAxiosError(err) + ? `Ошибка сервера: ${err.response?.status} - ${err.response?.data?.message || err.message}` + : `Ошибка загрузки: ${err.message}`; + setError(errorMessage); + showSnackbar(errorMessage, 'error'); } finally { setLoading(false); setRefreshing(false); } - }, [formulaId]); + }, []); const handleEditFormula = (formula) => { setEditingFormula(formula); @@ -246,16 +282,17 @@ const FormulaEditor = () => { try { setSaveLoading(true); - // Обновляем формулу в локальном состоянии - const updatedFormulas = formulas.map(formula => + await axios.post(`http://192.168.2.39:3000/api/formula/${formulaId}/update`, { + formula: newFormula + }); + + setFormulas(prev => prev.map(formula => formula.id === formulaId - ? { ...formula, data: { ...formula.data, formula: newFormula } } + ? { ...formula, formula: newFormula } : formula - ); + )); - setFormulas(updatedFormulas); setEditingFormula(null); - showSnackbar('Формула успешно обновлена!'); } catch (err) { @@ -266,65 +303,25 @@ const FormulaEditor = () => { } }; - const handleSendAllFormulas = async () => { - try { - setSaveLoading(true); - - // Преобразуем данные обратно в исходный формат для отправки - const dataToSend = formulas.map(formula => ({ - id: formula.data.id, - name: formula.data.name, - desription: formula.data.desription, - values: formula.data.values, - formula: formula.data.formula - })); - - console.log('Отправляемые данные:', dataToSend); - - const response = await axios.post( - 'http://192.168.2.39:9999/api/integration/3333', - dataToSend, - { - headers: { - 'Content-Type': 'application/json' - } - } - ); - - console.log('Ответ сервера:', response.data); - showSnackbar('Данные успешно отправлены на сервер!'); - - } catch (err) { - console.error('Ошибка при отправке данных:', err); - showSnackbar(`Ошибка отправки: ${err.message}`, 'error'); - } finally { - setSaveLoading(false); - } - }; - const refreshData = useCallback(() => { setRefreshing(true); loadFormulas(); }, [loadFormulas]); - const handleFormulaIdChange = (e) => { - setFormulaId(e.target.value); - }; - - const handleLoadClick = () => { - if (formulaId.trim()) { - loadFormulas(formulaId); - } - }; - const filteredFormulas = formulas.filter(formula => formula.id.toLowerCase().includes(filter.toLowerCase()) || - JSON.stringify(formula.data).toLowerCase().includes(filter.toLowerCase()) + formula.name.toLowerCase().includes(filter.toLowerCase()) || + formula.description.toLowerCase().includes(filter.toLowerCase()) || + formula.formula.toLowerCase().includes(filter.toLowerCase()) ); + const totalMetrics = formulas.reduce((sum, formula) => sum + (formula.metadata?.totalMetrics || 0), 0); + const foundMetrics = formulas.reduce((sum, formula) => sum + (formula.metadata?.foundMetrics || 0), 0); + const missingMetrics = formulas.reduce((sum, formula) => sum + (formula.metadata?.missingMetrics || 0), 0); + useEffect(() => { loadFormulas(); - }, []); + }, [loadFormulas]); return ( @@ -353,55 +350,53 @@ const FormulaEditor = () => { {/* Панель управления */} - - Редактор формул + + Редактор формул с метриками - - - - - - - + {/* Статистика */} + + + + {missingMetrics > 0 && ( + + )} + + + {/* Поиск */} + setFilter(e.target.value)} - variant="standard" - placeholder="Введите текст для поиска..." + variant="outlined" + placeholder="Введите ID, название или описание..." + size="small" /> - + @@ -409,10 +404,10 @@ const FormulaEditor = () => { {/* Список формул */} - {filteredFormulas.map((formula, index) => ( + {filteredFormulas.map((formula) => ( ))} @@ -424,7 +419,7 @@ const FormulaEditor = () => { py={3} variant="h6" > - {filter ? 'Формулы не найдены' : 'Загрузите формулы по ID'} + {filter ? 'Формулы не найдены' : 'Нет загруженных формул'} )} @@ -444,14 +439,9 @@ const FormulaEditor = () => { Всего формул: {formulas.length} • Отфильтровано: {filteredFormulas.length} - + + Метрики: {foundMetrics}/{totalMetrics} найдено + {/* Диалог редактирования */} From 86baaa29ff3e31709f06c3a2121ef24ddbaca9a3 Mon Sep 17 00:00:00 2001 From: deployer3000 Date: Tue, 2 Dec 2025 12:38:01 +0300 Subject: [PATCH 3/3] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D0=B8=D1=82=D1=8C?= =?UTF-8?q?=20vite.config.js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vite.config.js | 35 ----------------------------------- 1 file changed, 35 deletions(-) delete mode 100755 vite.config.js diff --git a/vite.config.js b/vite.config.js deleted file mode 100755 index 9481f91..0000000 --- a/vite.config.js +++ /dev/null @@ -1,35 +0,0 @@ -import { defineConfig, loadEnv } from 'vite' -import react from '@vitejs/plugin-react' -import svgr from 'vite-plugin-svgr' - -export default defineConfig(({ mode }) => { - // Загружаем переменные окружения - const env = loadEnv(mode, process.cwd()) - - return { - plugins: [react(), svgr()], - server: { - host: true, - allowedHosts: ['dev.msf.enode', 'demo-msf.kis-npo.ru'], - proxy: { - '/ai-api': { - target: env.VITE_AI_API_URL || 'http://192.168.2.39:5134', - changeOrigin: true, - rewrite: (path) => path.replace(/^\/ai-api/, ''), - }, - '/metrics-ws': { - target: env.VITE_WS_URL || 'ws://192.168.2.39:3001', - ws: true, - changeOrigin: true, - }, - '/api': { - target: env.VITE_API_URL || 'http://192.168.2.39:3000', - changeOrigin: true, - bypass(req, res, options) { - console.log('Proxying request:', req.url); - } - } - } - } - } -}) \ No newline at end of file