chore: update config values, fix script path, and enhance timeline styles

pull/62/head
SawGoD 2025-07-27 13:44:43 +03:00
parent 51ed0aa1bc
commit 7dd042ef7c
6 changed files with 284 additions and 26 deletions

View File

@ -8,11 +8,12 @@ window.Config = {
'm800673107-e0c2ebe9751e77346e8481a0', // Read-Only ключ
'm800673135-585a7f95c55b61c43bc818b4', // Read-Only ключ
'm800911467-ae3c9c2dc001bd9dc4a6bd1a', // Read-Only ключ
'm801031885-db86f05252c99d9bc8d58a76', // Read-Only ключ
// 'm800679644-4ee3480057a34ce157103cba', // Read-Only ключ
],
// Количество дней в логах
CountDays: 45,
CountDays: 24,
// Показывать ли ссылки на проверяемые сайты
ShowLink: false,

View File

@ -33,7 +33,7 @@
<link href="https://i.ibb.co/Df6CnYZg/a56f1a2d6cea.png" rel="icon" sizes="16x16" type="image/png">
<link href="https://i.ibb.co/SXCgp0LM/6c403bc2fb73.png" rel="icon" sizes="32x32" type="image/png">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH">
<script src="./config.js"></script>
<script src="/uptime/config.js"></script>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" class="d-none">

View File

@ -1,4 +1,4 @@
const CACHE_NAME = 'uptime-status-v2.0.1.1752084531934'
const CACHE_NAME = 'uptime-status-v2.0.1.1753557287951'
const CONFIG_FILE = '/config.js'
// Устанавливаем Service Worker

View File

@ -284,37 +284,131 @@
}
.timeline {
display: flex;
gap: 2px;
position: relative;
height: 40px;
padding: 4px;
background-color: var(--my-dark-gray-color);
border-radius: var(--bs-border-radius);
overflow: hidden;
padding: 12px 0;
overflow: visible;
}
.timeline-item {
flex: 1;
min-width: 3px;
border-radius: 2px;
.timeline-graph {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
.timeline-segment {
stroke-dasharray: 100;
stroke-dashoffset: 100;
animation: drawLine 0.5s ease-out forwards;
}
@keyframes drawLine {
to {
stroke-dashoffset: 0;
}
}
.timeline-point {
position: absolute;
width: 24px;
height: 24px;
border-radius: 50%;
cursor: pointer;
transition: all 0.2s ease;
transition: all 0.3s ease;
transform: translate(-50%, -50%);
z-index: 2;
border: 3px solid #fff;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
}
.timeline-point-animate {
opacity: 0;
transform: translate(-50%, -50%) scale(0.3);
animation: timelinePointAppear 0.4s ease-out forwards;
}
.timeline-item:hover {
transform: scaleY(1.1);
@keyframes timelinePointAppear {
0% {
opacity: 0;
transform: translate(-50%, -50%) scale(0.3);
}
50% {
opacity: 1;
transform: translate(-50%, -50%) scale(1.2);
}
100% {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
}
.timeline-item.ok {
.timeline-point:hover {
z-index: 10;
}
.timeline-point:hover::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 100%;
height: 100%;
border-radius: 50%;
transform: translate(-50%, -50%);
animation: pointPing 1.2s ease-in-out infinite;
pointer-events: none;
}
.timeline-point.ok:hover::before {
background-color: #1aad3a;
box-shadow: 0 0 15px rgba(26, 173, 58, 0.4);
}
.timeline-point.down:hover::before {
background-color: #ea4e43;
box-shadow: 0 0 15px rgba(234, 78, 67, 0.4);
}
.timeline-point.none:hover::before {
background-color: var(--my-alpha-gray-color);
box-shadow: 0 0 8px rgba(0, 0, 0, 0.2);
}
@keyframes pointPing {
0%, 100% {
transform: translate(-50%, -50%) scale(1);
opacity: 0.8;
}
50% {
transform: translate(-50%, -50%) scale(1.5);
opacity: 0.3;
}
}
.timeline-point.ok {
background-color: #1aad3a;
}
.timeline-item.down {
.timeline-point.down {
background-color: #ea4e43;
}
.timeline-item.none {
.timeline-point.none {
background-color: var(--my-alpha-gray-color);
border: none;
box-shadow: none;
width: 16px;
height: 16px;
}
.timeline-point.latest-ok {
box-shadow: 0 0 15px rgba(26, 173, 58, 0.6), 0 2px 6px rgba(0, 0, 0, 0.2);
}
.timeline-point.latest-down {
box-shadow: 0 0 15px rgba(234, 78, 67, 0.6), 0 2px 6px rgba(0, 0, 0, 0.2);
}
.timeline-labels {
@ -345,6 +439,70 @@
// Tooltip стили
.tooltip {
font-size: 0.875rem;
border-radius: 8px !important;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
font-family: inherit !important;
line-height: 1.4 !important;
padding: 8px 12px !important;
max-width: 250px !important;
}
// Стилизация ReactTooltip
.__react_component_tooltip {
border-radius: 8px !important;
background-color: var(--bs-dark) !important;
color: var(--bs-light) !important;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
font-family: inherit !important;
font-size: 0.875rem !important;
line-height: 1.4 !important;
padding: 8px 12px !important;
max-width: 250px !important;
opacity: 0.95 !important;
z-index: 1000 !important;
}
.__react_component_tooltip.type-dark {
background-color: var(--bs-dark) !important;
color: var(--bs-light) !important;
}
.__react_component_tooltip.type-dark.place-top:after {
border-top-color: var(--bs-dark) !important;
}
.__react_component_tooltip.type-dark.place-bottom:after {
border-bottom-color: var(--bs-dark) !important;
}
.__react_component_tooltip.type-dark.place-left:after {
border-left-color: var(--bs-dark) !important;
}
.__react_component_tooltip.type-dark.place-right:after {
border-right-color: var(--bs-dark) !important;
}
// Поддержка темной темы для tooltip
[data-bs-theme="dark"] .__react_component_tooltip {
background-color: rgba(248, 249, 250, 0.95) !important;
color: var(--bs-dark) !important;
}
[data-bs-theme="dark"] .__react_component_tooltip.type-dark.place-top:after {
border-top-color: rgba(248, 249, 250, 0.95) !important;
}
[data-bs-theme="dark"] .__react_component_tooltip.type-dark.place-bottom:after {
border-bottom-color: rgba(248, 249, 250, 0.95) !important;
}
[data-bs-theme="dark"] .__react_component_tooltip.type-dark.place-left:after {
border-left-color: rgba(248, 249, 250, 0.95) !important;
}
[data-bs-theme="dark"] .__react_component_tooltip.type-dark.place-right:after {
border-right-color: rgba(248, 249, 250, 0.95) !important;
}
// Логотипы для разных тем

View File

@ -8,6 +8,10 @@ import UptimeRobot from './uptimerobot'
const AppContent = () => {
const { t } = useLanguage()
const apikeys = useMemo(() => {
if (!window.Config) {
console.error('window.Config не найден. Убедитесь, что config.js загружен.')
return []
}
const { ApiKeys } = window.Config
if (Array.isArray(ApiKeys)) return ApiKeys
if (typeof ApiKeys === 'string') return [ApiKeys]

View File

@ -87,28 +87,123 @@ const UptimeRobot = ({ apikey }) => {
<div className="timeline-container mb-3">
<div className="timeline">
{site.daily
.slice()
.reverse()
.map((data, index) => {
{(() => {
// Создаем точки для SVG линии-графика
const svgPoints = site.daily
.slice()
.reverse()
.map((data, index) => {
let yPercent = 50 // По умолчанию по центру
if (data.uptime >= 100) {
yPercent = 35 // Выше центра для OK
} else if (data.uptime <= 0 && data.down.times === 0) {
yPercent = 50 // По центру для нет данных
} else {
yPercent = 65 // Ниже центра для DOWN
}
const xPercent = (index / (site.daily.length - 1)) * 100
return {
x: xPercent,
y: yPercent,
status: data.uptime >= 100 ? 'ok' : data.uptime <= 0 && data.down.times === 0 ? 'none' : 'down',
}
})
// Создаем сегменты линии с разными цветами
const segments = svgPoints.slice(0, -1).map((point, index) => {
const nextPoint = svgPoints[index + 1]
// Определяем цвет сегмента на основе статусов точек
let segmentColor = 'var(--my-alpha-gray-color)'
if (point.status === 'ok' && nextPoint.status === 'ok') {
segmentColor = '#1aad3a'
} else if (point.status === 'down' || nextPoint.status === 'down') {
segmentColor = '#ea4e43'
} else if (point.status === 'ok' || nextPoint.status === 'ok') {
segmentColor = '#1aad3a'
}
// Задержка для появления сегментов
const segmentDelay = index * 50 + 100 // Линии появляются после точек
return (
<path
key={index}
className="timeline-segment"
d={`M ${point.x} ${point.y} L ${nextPoint.x} ${nextPoint.y}`}
fill="none"
stroke={segmentColor}
strokeWidth="2"
strokeLinejoin="round"
strokeLinecap="round"
style={{ animationDelay: `${segmentDelay}ms` }}
/>
)
})
return (
<svg className="timeline-graph" viewBox="0 0 100 100" preserveAspectRatio="none">
{segments}
</svg>
)
})()}
{(() => {
// Находим индекс самой правой точки с данными
const dataPoints = site.daily.slice().reverse()
let latestDataIndex = -1
for (let i = dataPoints.length - 1; i >= 0; i--) {
if (!(dataPoints[i].uptime <= 0 && dataPoints[i].down.times === 0)) {
latestDataIndex = i
break
}
}
return dataPoints.map((data, index) => {
let statusClass = ''
let text = data.date.format('DD.MM.YYYY ')
let topPosition = '50%' // По умолчанию по центру
if (data.uptime >= 100) {
statusClass = 'ok'
topPosition = '35%' // Выше центра для OK
text += `${t('availability')} ${formatNumber(data.uptime)}%`
} else if (data.uptime <= 0 && data.down.times === 0) {
statusClass = 'none'
topPosition = '50%' // По центру для нет данных
text += t('noData')
} else {
statusClass = 'down'
topPosition = '65%' // Ниже центра для DOWN
text += `Сбоев ${data.down.times}, суммарно ${formatDuration(data.down.duration)}, ${t(
'availability'
).toLowerCase()} ${formatNumber(data.uptime)}%`
}
return <div key={index} className={`timeline-item ${statusClass}`} data-tip={text} title={text} />
})}
// Добавляем свечение для самой правой точки с данными
if (index === latestDataIndex && statusClass !== 'none') {
statusClass += ` latest-${statusClass}`
}
const dayPosition = (index / (site.daily.length - 1)) * 100
// Случайная задержка от 50мс до 200мс для каждой точки
const randomDelay = 50 + Math.random() * 150
const animationDelay = index * 30 + randomDelay // Базовая задержка + случайная
return (
<div
key={index}
className={`timeline-point ${statusClass} timeline-point-animate`}
style={{
left: `${dayPosition}%`,
top: topPosition,
animationDelay: `${animationDelay}ms`,
}}
data-tip={text}
title={text}
/>
)
})
})()}
</div>
<div className="timeline-labels d-flex justify-content-between mt-2">
<small className="my-text-content">{t('daysAgo', { days: CountDays })}</small>
@ -137,7 +232,7 @@ const UptimeRobot = ({ apikey }) => {
</div>
</div>
<ReactTooltip className="tooltip" place="top" type="dark" effect="solid" />
<ReactTooltip className="tooltip" place="top" type="dark" effect="solid" multiline={true} delayShow={200} delayHide={100} />
</div>
)
})