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
|
// The "catchall" handler: for any request that doesn't
|
||||||
// match one above, send back React's index.html file.
|
// match one above, send back React's index.html file.
|
||||||
app.get('*', (req, res) => {
|
app.get('*', (req, res) => {
|
||||||
|
|||||||
+120
-16
@@ -65,6 +65,7 @@ function FilterManager() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchServers();
|
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 () => {
|
const handleAddServer = async () => {
|
||||||
if (!newServer.name.trim()) {
|
if (!newServer.name.trim()) {
|
||||||
setError('Имя сервера обязательно для заполнения.');
|
setError('Имя сервера обязательно для заполнения.');
|
||||||
@@ -581,22 +595,54 @@ function FilterManager() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Функции для работы с фильтрами в упрощённом режиме
|
// Функции для работы с фильтрами в упрощённом режиме
|
||||||
const handleSimpleAddFilter = () => {
|
const handleSimpleAddFilter = async () => {
|
||||||
if (!simpleAddFilter.community.trim() || !simpleAddFilter.gateway.trim()) {
|
if (!simpleAddFilter.community.trim() || !simpleAddFilter.gateway.trim()) {
|
||||||
setSimpleError('Поля Community и Gateway должны быть заполнены.');
|
setSimpleError('Поля Community и Gateway должны быть заполнены.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setSimpleFilters([...simpleFilters, { ...simpleAddFilter }]);
|
const updatedFilters = [...simpleFilters, { ...simpleAddFilter }];
|
||||||
|
setSimpleFilters(updatedFilters);
|
||||||
setSimpleAddFilter({ community: '', gateway: '', description: '' });
|
setSimpleAddFilter({ community: '', gateway: '', description: '' });
|
||||||
setSimpleError('');
|
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];
|
const updated = [...simpleFilters];
|
||||||
updated[index][field] = value;
|
updated[index][field] = value;
|
||||||
setSimpleFilters(updated);
|
setSimpleFilters(updated);
|
||||||
|
|
||||||
|
// Сохраняем фильтры
|
||||||
|
try {
|
||||||
|
await axios.post(`${API_URL}/simple-filters`, { filters: updated });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving simple filters:', error);
|
||||||
|
setSimpleError('Не удалось сохранить изменения.');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -1181,9 +1227,21 @@ function FilterManager() {
|
|||||||
{mode === 'simple' ? (
|
{mode === 'simple' ? (
|
||||||
<div className="card mb-4">
|
<div className="card mb-4">
|
||||||
<div className="card-header">
|
<div className="card-header">
|
||||||
<h3 className="card-title">
|
<div className="row align-items-center">
|
||||||
<IconFilter className="me-2" /> Фильтры (упрощённый режим)
|
<div className="col">
|
||||||
</h3>
|
<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>
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
{simpleError && (
|
{simpleError && (
|
||||||
@@ -1203,7 +1261,16 @@ function FilterManager() {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<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>
|
<tr><td colSpan={4} className="text-center text-muted">Нет фильтров</td></tr>
|
||||||
) : (
|
) : (
|
||||||
simpleFilters.map((filter, idx) => (
|
simpleFilters.map((filter, idx) => (
|
||||||
@@ -1275,13 +1342,50 @@ function FilterManager() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<div className="btn-list">
|
||||||
className="btn btn-outline-success"
|
<button
|
||||||
onClick={handleDeploySimpleFiltersToAll}
|
className="btn btn-outline-success"
|
||||||
disabled={simpleFilters.length === 0 || simpleLoading}
|
onClick={handleDeploySimpleFiltersToAll}
|
||||||
>
|
disabled={simpleFilters.length === 0 || simpleLoading}
|
||||||
<IconRocket className="me-2" /> Раскатать фильтры на все серверы
|
>
|
||||||
</button>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
Reference in New Issue
Block a user