feat: Implement simple filters management in FilterManager with API integration for fetching, adding, and deleting filters, enhancing user experience and functionality
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 5m46s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 5m46s
This commit is contained in:
@@ -785,6 +785,74 @@ app.post('/api/server-filters/:serverId', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// --- Simple Filters Routes ---
|
||||
|
||||
// Get simple filters
|
||||
app.get('/api/simple-filters', async (req, res) => {
|
||||
const params = {
|
||||
Bucket: BUCKET_NAME,
|
||||
Key: 'simple-filters.json',
|
||||
};
|
||||
|
||||
try {
|
||||
const data = await s3.getObject(params).promise();
|
||||
const fileContent = data.Body.toString('utf-8');
|
||||
let filters = [];
|
||||
|
||||
try {
|
||||
filters = JSON.parse(fileContent);
|
||||
if (!Array.isArray(filters)) {
|
||||
filters = [];
|
||||
}
|
||||
} catch (parseError) {
|
||||
console.error('Error parsing simple filters:', parseError);
|
||||
filters = [];
|
||||
}
|
||||
|
||||
res.json(filters);
|
||||
} catch (error) {
|
||||
if (error.code === 'NoSuchKey') {
|
||||
res.json([]); // Return empty array if file does not exist
|
||||
} else {
|
||||
console.error(error);
|
||||
res.status(500).send('Error reading simple filters from S3');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Update simple filters
|
||||
app.post('/api/simple-filters', async (req, res) => {
|
||||
const { filters } = req.body;
|
||||
|
||||
// Validate filters structure
|
||||
if (!Array.isArray(filters)) {
|
||||
return res.status(400).send('Filters must be an array');
|
||||
}
|
||||
|
||||
// Validate each filter has required fields
|
||||
for (let i = 0; i < filters.length; i++) {
|
||||
const filter = filters[i];
|
||||
if (!filter.community || !filter.gateway) {
|
||||
return res.status(400).send(`Filter at index ${i} is missing required fields`);
|
||||
}
|
||||
}
|
||||
|
||||
const params = {
|
||||
Bucket: BUCKET_NAME,
|
||||
Key: 'simple-filters.json',
|
||||
Body: JSON.stringify(filters, null, 2), // Pretty print JSON
|
||||
ContentType: 'application/json',
|
||||
};
|
||||
|
||||
try {
|
||||
await s3.putObject(params).promise();
|
||||
res.send('Simple filters updated successfully');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).send('Error writing simple filters to S3');
|
||||
}
|
||||
});
|
||||
|
||||
// The "catchall" handler: for any request that doesn't
|
||||
// match one above, send back React's index.html file.
|
||||
app.get('*', (req, res) => {
|
||||
|
||||
+120
-16
@@ -65,6 +65,7 @@ function FilterManager() {
|
||||
|
||||
useEffect(() => {
|
||||
fetchServers();
|
||||
fetchSimpleFilters();
|
||||
}, []);
|
||||
|
||||
// Загружаем фильтры при выборе сервера
|
||||
@@ -102,6 +103,19 @@ function FilterManager() {
|
||||
}
|
||||
};
|
||||
|
||||
const fetchSimpleFilters = async () => {
|
||||
setSimpleLoading(true);
|
||||
try {
|
||||
const response = await axios.get(`${API_URL}/simple-filters`);
|
||||
setSimpleFilters(response.data);
|
||||
} catch (error) {
|
||||
console.error('Error fetching simple filters:', error);
|
||||
setSimpleFilters([]);
|
||||
} finally {
|
||||
setSimpleLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddServer = async () => {
|
||||
if (!newServer.name.trim()) {
|
||||
setError('Имя сервера обязательно для заполнения.');
|
||||
@@ -581,22 +595,54 @@ function FilterManager() {
|
||||
};
|
||||
|
||||
// Функции для работы с фильтрами в упрощённом режиме
|
||||
const handleSimpleAddFilter = () => {
|
||||
const handleSimpleAddFilter = async () => {
|
||||
if (!simpleAddFilter.community.trim() || !simpleAddFilter.gateway.trim()) {
|
||||
setSimpleError('Поля Community и Gateway должны быть заполнены.');
|
||||
return;
|
||||
}
|
||||
setSimpleFilters([...simpleFilters, { ...simpleAddFilter }]);
|
||||
const updatedFilters = [...simpleFilters, { ...simpleAddFilter }];
|
||||
setSimpleFilters(updatedFilters);
|
||||
setSimpleAddFilter({ community: '', gateway: '', description: '' });
|
||||
setSimpleError('');
|
||||
|
||||
// Сохраняем фильтры
|
||||
try {
|
||||
await axios.post(`${API_URL}/simple-filters`, { filters: updatedFilters });
|
||||
setSimpleSuccess('Фильтр успешно добавлен!');
|
||||
setTimeout(() => setSimpleSuccess(''), 3000);
|
||||
} catch (error) {
|
||||
console.error('Error saving simple filters:', error);
|
||||
setSimpleError('Не удалось сохранить фильтр.');
|
||||
}
|
||||
};
|
||||
const handleSimpleDeleteFilter = (filter) => {
|
||||
setSimpleFilters(simpleFilters.filter(f => f !== filter));
|
||||
|
||||
const handleSimpleDeleteFilter = async (filter) => {
|
||||
const updatedFilters = simpleFilters.filter(f => f !== filter);
|
||||
setSimpleFilters(updatedFilters);
|
||||
|
||||
// Сохраняем фильтры
|
||||
try {
|
||||
await axios.post(`${API_URL}/simple-filters`, { filters: updatedFilters });
|
||||
setSimpleSuccess('Фильтр успешно удален!');
|
||||
setTimeout(() => setSimpleSuccess(''), 3000);
|
||||
} catch (error) {
|
||||
console.error('Error saving simple filters:', error);
|
||||
setSimpleError('Не удалось удалить фильтр.');
|
||||
}
|
||||
};
|
||||
const handleSimpleEditFilter = (index, field, value) => {
|
||||
|
||||
const handleSimpleEditFilter = async (index, field, value) => {
|
||||
const updated = [...simpleFilters];
|
||||
updated[index][field] = value;
|
||||
setSimpleFilters(updated);
|
||||
|
||||
// Сохраняем фильтры
|
||||
try {
|
||||
await axios.post(`${API_URL}/simple-filters`, { filters: updated });
|
||||
} catch (error) {
|
||||
console.error('Error saving simple filters:', error);
|
||||
setSimpleError('Не удалось сохранить изменения.');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -1181,9 +1227,21 @@ function FilterManager() {
|
||||
{mode === 'simple' ? (
|
||||
<div className="card mb-4">
|
||||
<div className="card-header">
|
||||
<h3 className="card-title">
|
||||
<IconFilter className="me-2" /> Фильтры (упрощённый режим)
|
||||
</h3>
|
||||
<div className="row align-items-center">
|
||||
<div className="col">
|
||||
<h3 className="card-title">
|
||||
<IconFilter className="me-2" /> Фильтры (упрощённый режим)
|
||||
</h3>
|
||||
<div className="text-muted">
|
||||
Управление фильтрами community/gateway для всех серверов
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-auto">
|
||||
<div className="badge bg-blue-lt text-blue">
|
||||
{simpleFilters.length} фильтров
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
{simpleError && (
|
||||
@@ -1203,7 +1261,16 @@ function FilterManager() {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{simpleFilters.length === 0 ? (
|
||||
{simpleLoading ? (
|
||||
<tr>
|
||||
<td colSpan={4} className="text-center text-muted py-4">
|
||||
<div className="spinner-border spinner-border-sm me-2" role="status">
|
||||
<span className="visually-hidden">Загрузка...</span>
|
||||
</div>
|
||||
Загрузка фильтров...
|
||||
</td>
|
||||
</tr>
|
||||
) : simpleFilters.length === 0 ? (
|
||||
<tr><td colSpan={4} className="text-center text-muted">Нет фильтров</td></tr>
|
||||
) : (
|
||||
simpleFilters.map((filter, idx) => (
|
||||
@@ -1275,13 +1342,50 @@ function FilterManager() {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="btn btn-outline-success"
|
||||
onClick={handleDeploySimpleFiltersToAll}
|
||||
disabled={simpleFilters.length === 0 || simpleLoading}
|
||||
>
|
||||
<IconRocket className="me-2" /> Раскатать фильтры на все серверы
|
||||
</button>
|
||||
<div className="btn-list">
|
||||
<button
|
||||
className="btn btn-outline-success"
|
||||
onClick={handleDeploySimpleFiltersToAll}
|
||||
disabled={simpleFilters.length === 0 || simpleLoading}
|
||||
>
|
||||
<IconRocket className="me-2" /> Раскатать фильтры на все серверы
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-outline-primary"
|
||||
onClick={async () => {
|
||||
try {
|
||||
await axios.post(`${API_URL}/simple-filters`, { filters: simpleFilters });
|
||||
setSimpleSuccess('Фильтры успешно сохранены!');
|
||||
setTimeout(() => setSimpleSuccess(''), 3000);
|
||||
} catch (error) {
|
||||
setSimpleError('Ошибка при сохранении фильтров: ' + (error.response?.data || error.message));
|
||||
}
|
||||
}}
|
||||
disabled={simpleLoading}
|
||||
>
|
||||
<IconDeviceFloppy className="me-2" /> Сохранить фильтры
|
||||
</button>
|
||||
{simpleFilters.length > 0 && (
|
||||
<button
|
||||
className="btn btn-outline-danger"
|
||||
onClick={async () => {
|
||||
if (window.confirm('Вы уверены, что хотите очистить все фильтры?')) {
|
||||
try {
|
||||
await axios.post(`${API_URL}/simple-filters`, { filters: [] });
|
||||
setSimpleFilters([]);
|
||||
setSimpleSuccess('Все фильтры очищены!');
|
||||
setTimeout(() => setSimpleSuccess(''), 3000);
|
||||
} catch (error) {
|
||||
setSimpleError('Ошибка при очистке фильтров: ' + (error.response?.data || error.message));
|
||||
}
|
||||
}
|
||||
}}
|
||||
disabled={simpleLoading}
|
||||
>
|
||||
<IconTrash className="me-2" /> Очистить все
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user