feat: Add new endpoint to fetch last modified dates of S3 files and update Dashboard to display this information
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 16m53s

This commit is contained in:
2025-07-09 22:43:55 +07:00
parent e0fa98f21a
commit 0390dfd148
2 changed files with 59 additions and 86 deletions
+17
View File
@@ -126,6 +126,23 @@ app.post('/api/asns', async (req, res) => {
} }
}); });
// Новый эндпоинт для получения дат последнего изменения файлов S3
app.get('/api/s3/last-modified', async (req, res) => {
try {
const [domainsHead, asnsHead] = await Promise.all([
s3.headObject({ Bucket: BUCKET_NAME, Key: 'domains.txt' }).promise(),
s3.headObject({ Bucket: BUCKET_NAME, Key: 'asns.txt' }).promise()
]);
res.json({
domainsLastModified: domainsHead.LastModified ? domainsHead.LastModified.toISOString() : null,
asnsLastModified: asnsHead.LastModified ? asnsHead.LastModified.toISOString() : null
});
} catch (error) {
console.error(error);
res.status(500).send('Error fetching last modified dates from 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) => {
+42 -86
View File
@@ -1,4 +1,4 @@
import { useState } from 'react'; import { useState, useEffect } from 'react';
import { Container } from 'react-bootstrap'; import { Container } from 'react-bootstrap';
import { import {
IconBrandTabler, IconBrandTabler,
@@ -19,6 +19,7 @@ import {
} from '@tabler/icons-react'; } from '@tabler/icons-react';
import DataManager from './DataManager'; import DataManager from './DataManager';
import './App.css'; import './App.css';
import axios from 'axios';
function App() { function App() {
const [activeTab, setActiveTab] = useState('dashboard'); const [activeTab, setActiveTab] = useState('dashboard');
@@ -171,34 +172,60 @@ function StatCard({ icon: Icon, color, value, title, subtitle }) {
} }
function Dashboard() { function Dashboard() {
const [stats, setStats] = useState({ domainsCount: null, asnsCount: null, lastModified: null });
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchStats() {
setLoading(true);
try {
const [domainsRes, asnsRes, s3Res] = await Promise.all([
axios.get('/api/domains'),
axios.get('/api/asns'),
axios.get('/api/s3/last-modified')
]);
setStats({
domainsCount: domainsRes.data.length,
asnsCount: asnsRes.data.length,
lastModified: s3Res.data.domainsLastModified
});
} catch (e) {
setStats({ domainsCount: null, asnsCount: null, lastModified: null });
} finally {
setLoading(false);
}
}
fetchStats();
}, []);
return ( return (
<div> <div>
<div className="row g-3 mb-4"> <div className="row g-3 mb-4">
<div className="col-md-4"> <div className="col-md-4">
<StatCard <StatCard
icon={IconDatabase} icon={IconWorld}
color="blue" color="blue"
value={6} value={loading ? '...' : stats.domainsCount ?? '—'}
title="Событий" title="Доменов"
subtitle="Всего событий" subtitle="Всего доменов"
/> />
</div> </div>
<div className="col-md-4"> <div className="col-md-4">
<StatCard <StatCard
icon={IconAlertTriangle} icon={IconNetwork}
color="red" color="green"
value={3} value={loading ? '...' : stats.asnsCount ?? '—'}
title="Ошибок" title="AS"
subtitle="Критические ошибки" subtitle="Всего AS"
/> />
</div> </div>
<div className="col-md-4"> <div className="col-md-4">
<StatCard <StatCard
icon={IconAlertCircle} icon={IconDatabase}
color="yellow" color="orange"
value={0} value={loading ? '...' : (stats.lastModified ? new Date(stats.lastModified).toLocaleString() : '—')}
title="Предупреждений" title="Обновление"
subtitle="Системные предупреждения" subtitle="Последнее изменение S3"
/> />
</div> </div>
</div> </div>
@@ -210,77 +237,6 @@ function Dashboard() {
</div> </div>
</div> </div>
</div> </div>
<div className="row row-deck">
<div className="col-sm-6 col-lg-3">
<div className="card">
<div className="card-body">
<div className="h1 mb-3">1,234</div>
<div className="d-flex align-items-center">
<IconWorld className="icon me-2" />
<span className="text-muted">Всего доменов</span>
</div>
</div>
</div>
</div>
<div className="col-sm-6 col-lg-3">
<div className="card">
<div className="card-body">
<div className="h1 mb-3">567</div>
<div className="d-flex align-items-center">
<IconNetwork className="icon me-2" />
<span className="text-muted">Всего AS</span>
</div>
</div>
</div>
</div>
<div className="col-sm-6 col-lg-3">
<div className="card">
<div className="card-body">
<div className="h1 mb-3">12</div>
<div className="d-flex align-items-center">
<IconCloud className="icon me-2" />
<span className="text-muted">Активные шлюзы</span>
</div>
</div>
</div>
</div>
<div className="col-sm-6 col-lg-3">
<div className="card">
<div className="card-body">
<div className="h1 mb-3">3</div>
<div className="d-flex align-items-center">
<IconActivity className="icon me-2" />
<span className="text-muted">Последние изменения</span>
</div>
</div>
</div>
</div>
</div>
<div className="card mt-4">
<div className="card-body">
<h3 className="card-title mb-3">Добро пожаловать в S3 Lists Manager</h3>
<div className="row">
<div className="col-md-6 mb-3">
<div className="d-flex align-items-center">
<IconWorld className="icon me-2" size={32} />
<div>
<div className="fw-bold">Управление доменами</div>
<div className="text-muted">Добавляйте, редактируйте и управляйте списками доменов</div>
</div>
</div>
</div>
<div className="col-md-6 mb-3">
<div className="d-flex align-items-center">
<IconNetwork className="icon me-2" size={32} />
<div>
<div className="fw-bold">Управление AS</div>
<div className="text-muted">Добавляйте, редактируйте и управляйте списками AS</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div> </div>
); );
} }