feat: Add ConfirmDialog component for enhanced user confirmation in DataManager, FilterManager, and ServerManager; integrate html-to-image library for image conversion functionality
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 5m35s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 5m35s
This commit is contained in:
Generated
+7
@@ -13,6 +13,7 @@
|
||||
"@tanstack/react-query": "^5.56.2",
|
||||
"@xyflow/react": "^12.3.4",
|
||||
"axios": "^1.10.0",
|
||||
"html-to-image": "^1.11.11",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-router-dom": "^6.30.1",
|
||||
@@ -1688,6 +1689,12 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/html-to-image": {
|
||||
"version": "1.11.13",
|
||||
"resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.11.13.tgz",
|
||||
"integrity": "sha512-cuOPoI7WApyhBElTTb9oqsawRvZ0rHhaHwghRLlTuffoD1B2aDemlCruLeZrUIIdvG7gs9xeELEPm6PhuASqrg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.3.2",
|
||||
"dev": true,
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"@tabler/icons-react": "^3.34.0",
|
||||
"axios": "^1.10.0",
|
||||
"@tanstack/react-query": "^5.56.2",
|
||||
"html-to-image": "^1.11.11",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-router-dom": "^6.30.1",
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
IconUpload,
|
||||
IconAlertTriangle
|
||||
} from '@tabler/icons-react';
|
||||
import ConfirmDialog from './components/ConfirmDialog.jsx'
|
||||
|
||||
const API_URL = '/api';
|
||||
|
||||
@@ -36,6 +37,7 @@ function DataManager({ entityName, entityKey, placeholder }) {
|
||||
const [filterType, setFilterType] = useState('');
|
||||
const editInputRef = useRef(null);
|
||||
const pageSize = 10;
|
||||
const [confirmState, setConfirmState] = useState({ open: false, text: '', onConfirm: null });
|
||||
|
||||
useEffect(() => {
|
||||
fetchItems();
|
||||
@@ -118,14 +120,16 @@ function DataManager({ entityName, entityKey, placeholder }) {
|
||||
setError('Оба поля для массового обновления должны быть заполнены.');
|
||||
return;
|
||||
}
|
||||
const confirm = window.confirm(`Вы уверены, что хотите заменить все шлюзы "${bulkFrom}" на "${bulkTo}"? Это действие необратимо.`);
|
||||
if (confirm) {
|
||||
const updatedItems = items.map(i =>
|
||||
i.type === bulkFrom ? { ...i, type: bulkTo } : i
|
||||
);
|
||||
setItems(updatedItems);
|
||||
setSuccess(`Шлюзы "${bulkFrom}" были успешно заменены на "${bulkTo}". Не забудьте сохранить изменения.`);
|
||||
}
|
||||
setConfirmState({
|
||||
open: true,
|
||||
text: `Вы уверены, что хотите заменить все шлюзы "${bulkFrom}" на "${bulkTo}"? Это действие необратимо.`,
|
||||
onConfirm: () => {
|
||||
const updatedItems = items.map(i => i.type === bulkFrom ? { ...i, type: bulkTo } : i);
|
||||
setItems(updatedItems);
|
||||
setSuccess(`Шлюзы "${bulkFrom}" были успешно заменены на "${bulkTo}". Не забудьте сохранить изменения.`);
|
||||
setConfirmState({ open: false, text: '', onConfirm: null });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Сортировка
|
||||
@@ -500,6 +504,14 @@ function DataManager({ entityName, entityKey, placeholder }) {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ConfirmDialog
|
||||
open={confirmState.open}
|
||||
title="Подтверждение"
|
||||
message={confirmState.text}
|
||||
onCancel={() => setConfirmState({ open: false, text: '', onConfirm: null })}
|
||||
onConfirm={() => confirmState.onConfirm?.()}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import api from './lib/api.js';
|
||||
import ConfirmDialog from './components/ConfirmDialog.jsx';
|
||||
import {
|
||||
IconPlus,
|
||||
IconSearch,
|
||||
@@ -75,6 +76,7 @@ function FilterManager() {
|
||||
const [editingKey, setEditingKey] = useState(null);
|
||||
const [editingDraft, setEditingDraft] = useState({ community: '', gateway: '', description: '' });
|
||||
const [selectedFilterKeys, setSelectedFilterKeys] = useState(new Set());
|
||||
const [confirmState, setConfirmState] = useState({ open: false, text: '', onConfirm: null });
|
||||
|
||||
// Communities directory for autocomplete
|
||||
const [communities, setCommunities] = useState([]);
|
||||
@@ -239,12 +241,15 @@ function FilterManager() {
|
||||
return;
|
||||
}
|
||||
|
||||
const confirm = window.confirm(
|
||||
`Скопировать конфигурацию и фильтры с сервера "${sourceServer.name}" на ${targetServers.length} сервер(ов)?\n\n` +
|
||||
`Целевые серверы: ${targetServers.map(s => s.name).join(', ')}`
|
||||
);
|
||||
|
||||
if (!confirm) return;
|
||||
let proceed = false;
|
||||
await new Promise((resolve) => {
|
||||
setConfirmState({
|
||||
open: true,
|
||||
text: `Скопировать конфигурацию и фильтры с сервера "${sourceServer.name}" на ${targetServers.length} сервер(ов)?\n\nЦелевые серверы: ${targetServers.map(s => s.name).join(', ')}`,
|
||||
onConfirm: () => { proceed = true; setConfirmState({ open: false, text: '', onConfirm: null }); resolve(); },
|
||||
});
|
||||
});
|
||||
if (!proceed) return;
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
@@ -334,12 +339,15 @@ function FilterManager() {
|
||||
return;
|
||||
}
|
||||
|
||||
const confirm = window.confirm(
|
||||
`Развернуть фильтры и конфигурацию с сервера "${selectedServer.name}" на ${enabledServers.length} сервер(ов)?\n\n` +
|
||||
`Целевые серверы: ${enabledServers.map(s => s.name).join(', ')}`
|
||||
);
|
||||
|
||||
if (!confirm) return;
|
||||
let proceed = false;
|
||||
await new Promise((resolve) => {
|
||||
setConfirmState({
|
||||
open: true,
|
||||
text: `Развернуть фильтры и конфигурацию с сервера "${selectedServer.name}" на ${enabledServers.length} сервер(ов)?\n\nЦелевые серверы: ${enabledServers.map(s => s.name).join(', ')}`,
|
||||
onConfirm: () => { proceed = true; setConfirmState({ open: false, text: '', onConfirm: null }); resolve(); },
|
||||
});
|
||||
});
|
||||
if (!proceed) return;
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
@@ -690,8 +698,15 @@ function FilterManager() {
|
||||
|
||||
const deleteSelectedFilters = async () => {
|
||||
if (selectedFilterKeys.size === 0) return;
|
||||
const confirmed = window.confirm(`Удалить выбранные фильтры (${selectedFilterKeys.size})?`);
|
||||
if (!confirmed) return;
|
||||
let proceed = false;
|
||||
await new Promise((resolve) => {
|
||||
setConfirmState({
|
||||
open: true,
|
||||
text: `Удалить выбранные фильтры (${selectedFilterKeys.size})?`,
|
||||
onConfirm: () => { proceed = true; setConfirmState({ open: false, text: '', onConfirm: null }); resolve(); },
|
||||
});
|
||||
});
|
||||
if (!proceed) return;
|
||||
const updated = serverFilters.filter((f) => !selectedFilterKeys.has(filterKey(f)));
|
||||
setServerFilters(updated);
|
||||
setSelectedFilterKeys(new Set());
|
||||
@@ -1679,7 +1694,15 @@ function FilterManager() {
|
||||
<button
|
||||
className="btn btn-outline-danger"
|
||||
onClick={async () => {
|
||||
if (window.confirm('Вы уверены, что хотите очистить все фильтры?')) {
|
||||
let proceed = false;
|
||||
await new Promise((resolve) => {
|
||||
setConfirmState({
|
||||
open: true,
|
||||
text: 'Вы уверены, что хотите очистить все фильтры?',
|
||||
onConfirm: () => { proceed = true; setConfirmState({ open: false, text: '', onConfirm: null }); resolve(); },
|
||||
});
|
||||
});
|
||||
if (proceed) {
|
||||
try {
|
||||
await api.post(`/simple-filters`, { filters: [] });
|
||||
setSimpleFilters([]);
|
||||
@@ -1706,6 +1729,13 @@ function FilterManager() {
|
||||
{/* ... всё как было ... */}
|
||||
</>
|
||||
)}
|
||||
<ConfirmDialog
|
||||
open={confirmState.open}
|
||||
title="Подтверждение"
|
||||
message={confirmState.text}
|
||||
onCancel={() => setConfirmState({ open: false, text: '', onConfirm: null })}
|
||||
onConfirm={() => confirmState.onConfirm?.()}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import api from './lib/api.js';
|
||||
import ConfirmDialog from './components/ConfirmDialog.jsx';
|
||||
import {
|
||||
IconPlus,
|
||||
IconSearch,
|
||||
@@ -92,6 +93,7 @@ function ServerManager() {
|
||||
const [graphTunnelFilters, setGraphTunnelFilters] = useState({ GRE: true, IPSec: true, WireGuard: true, OpenVPN: true });
|
||||
const [graphCountryFilter, setGraphCountryFilter] = useState('');
|
||||
const [graphProviderFilter, setGraphProviderFilter] = useState('');
|
||||
const [confirmState, setConfirmState] = useState({ open: false, text: '', onConfirm: null });
|
||||
|
||||
useEffect(() => {
|
||||
fetchServers();
|
||||
@@ -332,14 +334,16 @@ function ServerManager() {
|
||||
setError('Оба поля для массового обновления должны быть заполнены.');
|
||||
return;
|
||||
}
|
||||
const confirm = window.confirm(`Вы уверены, что хотите заменить все туннели "${bulkFrom}" на "${bulkTo}"? Это действие необратимо.`);
|
||||
if (confirm) {
|
||||
const updatedServers = servers.map(s =>
|
||||
s.tunnel === bulkFrom ? { ...s, tunnel: bulkTo } : s
|
||||
);
|
||||
setServers(updatedServers);
|
||||
setSuccess(`Туннели "${bulkFrom}" были успешно заменены на "${bulkTo}". Не забудьте сохранить изменения.`);
|
||||
}
|
||||
setConfirmState({
|
||||
open: true,
|
||||
text: `Вы уверены, что хотите заменить все туннели "${bulkFrom}" на "${bulkTo}"? Это действие необратимо.`,
|
||||
onConfirm: () => {
|
||||
const updatedServers = servers.map(s => s.tunnel === bulkFrom ? { ...s, tunnel: bulkTo } : s);
|
||||
setServers(updatedServers);
|
||||
setSuccess(`Туннели "${bulkFrom}" были успешно заменены на "${bulkTo}". Не забудьте сохранить изменения.`);
|
||||
setConfirmState({ open: false, text: '', onConfirm: null });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Сортировка
|
||||
@@ -890,6 +894,14 @@ function ServerManager() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ConfirmDialog
|
||||
open={confirmState.open}
|
||||
title="Подтверждение"
|
||||
message={confirmState.text}
|
||||
onCancel={() => setConfirmState({ open: false, text: '', onConfirm: null })}
|
||||
onConfirm={() => confirmState.onConfirm?.()}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user