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
// match one above, send back React's index.html file.
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 {
IconBrandTabler,
@@ -19,6 +19,7 @@ import {
} from '@tabler/icons-react';
import DataManager from './DataManager';
import './App.css';
import axios from 'axios';
function App() {
const [activeTab, setActiveTab] = useState('dashboard');
@@ -171,34 +172,60 @@ function StatCard({ icon: Icon, color, value, title, subtitle }) {
}
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 (
<div>
<div className="row g-3 mb-4">
<div className="col-md-4">
<StatCard
icon={IconDatabase}
icon={IconWorld}
color="blue"
value={6}
title="Событий"
subtitle="Всего событий"
value={loading ? '...' : stats.domainsCount ?? '—'}
title="Доменов"
subtitle="Всего доменов"
/>
</div>
<div className="col-md-4">
<StatCard
icon={IconAlertTriangle}
color="red"
value={3}
title="Ошибок"
subtitle="Критические ошибки"
icon={IconNetwork}
color="green"
value={loading ? '...' : stats.asnsCount ?? '—'}
title="AS"
subtitle="Всего AS"
/>
</div>
<div className="col-md-4">
<StatCard
icon={IconAlertCircle}
color="yellow"
value={0}
title="Предупреждений"
subtitle="Системные предупреждения"
icon={IconDatabase}
color="orange"
value={loading ? '...' : (stats.lastModified ? new Date(stats.lastModified).toLocaleString() : '—')}
title="Обновление"
subtitle="Последнее изменение S3"
/>
</div>
</div>
@@ -210,77 +237,6 @@ function Dashboard() {
</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>
);
}