diff --git a/Dockerfile b/Dockerfile index 129aef3..4436d9a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,12 +7,15 @@ WORKDIR /app # Копируем package.json и package-lock.json COPY package*.json ./ -# Устанавливаем зависимости -RUN npm ci --only=production +# Устанавливаем зависимости (включая dev для скриптов) +RUN npm ci # Копируем исходный код COPY . . +# Обновляем версию для кэш-бастинга +RUN node scripts/update-version.js + # Устанавливаем публичный путь для React ENV PUBLIC_URL=/uptime diff --git a/nginx-uptime-path.conf b/nginx-uptime-path.conf index 75dc0e1..4035db7 100644 --- a/nginx-uptime-path.conf +++ b/nginx-uptime-path.conf @@ -28,17 +28,30 @@ server { return 301 $scheme://$host/uptime/; } + # config.js - не кэшируем, может изменяться + location = /uptime/config.js { + rewrite ^/uptime(/.*)$ $1 break; + proxy_pass http://127.0.0.1:34481; + proxy_set_header Host $host; + expires -1; + add_header Cache-Control "no-cache, no-store, must-revalidate"; + add_header Pragma "no-cache"; + } + + # Версионированные файлы - долгое кэширование + location ~* ^/uptime/.*\?v=[\d\.]+$ { + rewrite ^/uptime(/.*)$ $1 break; + proxy_pass http://127.0.0.1:34481; + proxy_set_header Host $host; + expires 1y; + add_header Cache-Control "public, immutable"; + } + # Статические файлы (обрабатываем в первую очередь) location ~* ^/uptime/static/.*\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf)$ { rewrite ^/uptime(/.*)$ $1 break; proxy_pass http://127.0.0.1:34481; proxy_set_header Host $host; - # Временно отключаем кэш для JS файлов во время разработки - location ~* \.js$ { - expires -1; - add_header Cache-Control "no-cache, no-store, must-revalidate"; - add_header Pragma "no-cache"; - } expires 1y; add_header Cache-Control "public, immutable"; } diff --git a/public/config.js b/public/config.js index eb1dbfe..d5c9865 100644 --- a/public/config.js +++ b/public/config.js @@ -1,6 +1,6 @@ window.Config = { // Название сайта - SiteName: 'Nerjel', + SiteName: 'Nerjel Status', // UptimeRobot Api Keys // Поддерживает Monitor-Specific и Read-Only @@ -14,14 +14,14 @@ window.Config = { // URL для проверки пинга (по порядку соответствуют API ключам) PingUrls: [ - 'http://itachi.nj0.ru', // Для первого API ключа - 'http://hidan.nj0.ru', // Для второго API ключа - 'http://yugito.nj0.ru', // Для третьего API ключа - 'http://lando.nj0.ru', // Для четвертого API ключа + 'http://itachi.nj0.ru:60231', // Для первого API ключа + 'http://hidan.nj0.ru:60231', // Для второго API ключа + 'http://yugito.nj0.ru:60231', // Для третьего API ключа + 'http://lando.nj0.ru:60231', // Для четвертого API ключа ], // Количество дней в логах - CountDays: 20, + CountDays: 16, // Показывать ли ссылки на проверяемые сайты ShowLink: false, diff --git a/public/index.html b/public/index.html index 8b66038..d496e17 100644 --- a/public/index.html +++ b/public/index.html @@ -33,7 +33,7 @@ - + diff --git a/public/sw.js b/public/sw.js index 38e56f9..e033a5e 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,4 +1,4 @@ -const CACHE_NAME = 'uptime-status-v2.0.1.1753557287951' +const CACHE_NAME = 'uptime-status-v2.0.1.1753641153734' const CONFIG_FILE = '/config.js' // Устанавливаем Service Worker diff --git a/rebuild_new.sh b/rebuild_new.sh new file mode 100644 index 0000000..ae75a90 --- /dev/null +++ b/rebuild_new.sh @@ -0,0 +1,82 @@ +#!/bin/bash + +# Скрипт для полной пересборки с версионированием +# Использование: ./rebuild_new.sh + +# Цвета для вывода +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo -e "${BLUE}=== 🚀 Полная пересборка Uptime Status ===${NC}" + +# Проверка необходимых файлов +if [ ! -f "scripts/update-version.js" ] || [ ! -f "package.json" ]; then + echo -e "${RED}❌ Не найдены файлы для обновления версии${NC}" + exit 1 +fi + +# 1. Остановка и удаление контейнера +echo -e "${YELLOW}🛑 Остановка и удаление контейнера...${NC}" +docker-compose down +docker rm -f uptime-status 2>/dev/null + +# 2. Полная очистка образов +echo -e "${YELLOW}🗑️ Полная очистка старых образов...${NC}" +# Удаляем основной образ +docker rmi uptime-status 2>/dev/null || true +# Удаляем все образы с тегом uptime-status +docker rmi $(docker images | grep "uptime-status" | awk '{print $3}') 2>/dev/null || true +# Удаляем dangling образы +docker rmi $(docker images -f "dangling=true" -q) 2>/dev/null || true + +# 3. Обновление версии +echo -e "${YELLOW}📝 Обновление версии и кэша...${NC}" +node scripts/update-version.js +if [ $? -ne 0 ]; then + echo -e "${RED}❌ Ошибка при обновлении версии${NC}" + exit 1 +fi + +# 4. Сборка нового образа +echo -e "${YELLOW}🔨 Сборка нового образа (полная пересборка)...${NC}" +docker build -t uptime-status . --no-cache --pull +if [ $? -ne 0 ]; then + echo -e "${RED}❌ Ошибка при сборке образа${NC}" + exit 1 +fi + +# 5. Запуск контейнера +echo -e "${YELLOW}🚀 Запуск нового контейнера...${NC}" +docker-compose up -d +if [ $? -ne 0 ]; then + echo -e "${RED}❌ Ошибка при запуске контейнера${NC}" + exit 1 +fi + +# 6. Ожидание запуска +echo -e "${YELLOW}⏳ Ожидание запуска (5 сек)...${NC}" +sleep 5 + +# 7. Проверка статуса +echo -e "${GREEN}✅ Пересборка завершена!${NC}" +echo -e "${GREEN}📱 Приложение запущено на порту 34481${NC}" + +# Показать статус +echo -e "${BLUE}📊 Статус контейнера:${NC}" +docker ps | head -1 +docker ps | grep uptime-status + +# Показать логи последних 10 строк +echo -e "${BLUE}📄 Последние логи:${NC}" +docker logs uptime-status --tail 10 + +# Показать информацию о версии +if [ -f "public/index.html" ]; then + VERSION=$(grep -o 'config\.js?v=[^"]*' public/index.html | cut -d'=' -f2) + echo -e "${GREEN}🔖 Версия: ${VERSION}${NC}" +fi + +echo -e "${GREEN}🎉 Готово! Приложение доступно по адресу: http://localhost:34481/uptime/${NC}" \ No newline at end of file diff --git a/scripts/update-version.js b/scripts/update-version.js index b0a626f..8cf73c6 100644 --- a/scripts/update-version.js +++ b/scripts/update-version.js @@ -13,8 +13,8 @@ const versionString = `${version}.${timestamp}` const indexPath = path.join(__dirname, '../public/index.html') let indexContent = fs.readFileSync(indexPath, 'utf8') -// Заменяем версию в config.js -indexContent = indexContent.replace(/src="\.\/config\.js\?v=[^"]*"/, `src="./config.js?v=${versionString}"`) +// Заменяем версию в config.js (поддерживаем разные пути) +indexContent = indexContent.replace(/src="[^"]*config\.js(\?v=[^"]*)?"/g, `src="/uptime/config.js?v=${versionString}"`) fs.writeFileSync(indexPath, indexContent) diff --git a/src/common/i18n.js b/src/common/i18n.js index 05b90d7..82cc07a 100644 --- a/src/common/i18n.js +++ b/src/common/i18n.js @@ -39,14 +39,11 @@ export const MESSAGES = { // Футер footerText: 'Based on UptimeRobot API, check frequency 5 minutes', - // Обновления - updateAvailable: 'Update Available', - updateDescription: 'A new version of the application is available. Update now to get the latest improvements.', - updateNow: 'Update Now', - later: 'Later', - // Пинг pingStatus: 'Current delay', + pingAvg: 'AVG', + pingMinMax: 'Min/Max', + pingMeasuring: 'Measuring ping...', }, ru: { // Общие @@ -87,14 +84,11 @@ export const MESSAGES = { // Футер footerText: 'Сделано на основе API UptimeRobot, частота проверки 5 минут', - // Обновления - updateAvailable: 'Доступно обновление', - updateDescription: 'Доступна новая версия приложения. Обновите сейчас, чтобы получить последние улучшения.', - updateNow: 'Обновить сейчас', - later: 'Позже', - // Пинг pingStatus: 'Текущая задержка', + pingAvg: 'СР', + pingMinMax: 'Мин/Макс', + pingMeasuring: 'Измерение пинга...', }, } diff --git a/src/common/ping.js b/src/common/ping.js index b7c6b51..c9d2718 100644 --- a/src/common/ping.js +++ b/src/common/ping.js @@ -1,7 +1,7 @@ // Измерение пинга серверов const PING_TIMEOUT = 3000 -// Пинг с замером времени +// Быстрый пинг через fetch с фолбэком на Image export const measurePing = async (url, attempts = 3) => { const times = [] @@ -11,14 +11,48 @@ export const measurePing = async (url, attempts = 3) => { const start = performance.now() try { - await fetch(url, { - method: 'HEAD', - mode: 'no-cors', - cache: 'no-store', - signal: controller.signal, - }) - const time = performance.now() - start + // Сначала пробуем быстрый fetch + try { + await fetch(url, { + method: 'HEAD', + mode: 'no-cors', + cache: 'no-store', + signal: controller.signal, + }) + } catch (fetchError) { + // Если fetch не работает, фолбэк на Image (медленнее, но работает) + await new Promise((resolve, reject) => { + const img = new Image() + const cleanup = () => { + img.onload = null + img.onerror = null + img.src = '' // Останавливаем загрузку + } + + const timeoutId = setTimeout(() => { + cleanup() + reject(new Error('Image timeout')) + }, 2000) // Короткий таймаут для Image + + img.onload = img.onerror = () => { + clearTimeout(timeoutId) + cleanup() + resolve() + } + + controller.signal.addEventListener('abort', () => { + clearTimeout(timeoutId) + cleanup() + reject(new Error('Aborted')) + }) + + // Минимальный URL для быстрой проверки + img.src = url + (url.includes('?') ? '&' : '?') + 't=' + Date.now() + }) + } + + const time = performance.now() - start if (i > 0) times.push(Math.round(time)) // пропускаем первый } catch { // Игнорируем ошибки diff --git a/src/components/app.js b/src/components/app.js index c5d50fc..0bcf5b0 100644 --- a/src/components/app.js +++ b/src/components/app.js @@ -2,7 +2,6 @@ import { useMemo } from 'react' import { LanguageProvider, useLanguage } from '../contexts/LanguageContext' import Header from './header' import Link from './link' -import UpdateNotifier from './update-notifier' import UptimeRobot from './uptimerobot' const AppContent = () => { @@ -40,7 +39,6 @@ const AppContent = () => {

- ) } diff --git a/src/components/update-notifier.js b/src/components/update-notifier.js deleted file mode 100644 index 9db4729..0000000 --- a/src/components/update-notifier.js +++ /dev/null @@ -1,83 +0,0 @@ -import { useEffect, useState } from 'react' -import { useLanguage } from '../contexts/LanguageContext' - -const UpdateNotifier = () => { - const { t } = useLanguage() - const [updateAvailable, setUpdateAvailable] = useState(false) - const [registration, setRegistration] = useState(null) - - useEffect(() => { - if ('serviceWorker' in navigator) { - navigator.serviceWorker.getRegistration().then((reg) => { - if (reg) { - setRegistration(reg) - - // Проверяем обновления - reg.addEventListener('updatefound', () => { - const newWorker = reg.installing - if (newWorker) { - newWorker.addEventListener('statechange', () => { - if (newWorker.state === 'installed' && navigator.serviceWorker.controller) { - console.log('Обновление доступно!') - setUpdateAvailable(true) - } - }) - } - }) - } - }) - - // Слушаем сообщения от Service Worker - navigator.serviceWorker.addEventListener('message', (event) => { - if (event.data.type === 'UPDATE_AVAILABLE') { - setUpdateAvailable(true) - } - }) - } - }, []) - - const handleUpdate = () => { - if (registration) { - // Отправляем сообщение SW для активации обновления - if (registration.waiting) { - registration.waiting.postMessage({ type: 'SKIP_WAITING' }) - } - - // Перезагружаем страницу - window.location.reload() - } - } - - const handleDismiss = () => { - setUpdateAvailable(false) - } - - if (!updateAvailable) { - return null - } - - return ( -
-
-
-
- {t('updateAvailable')} - -
-
-

{t('updateDescription')}

-
- - -
-
-
-
- ) -} - -export default UpdateNotifier diff --git a/src/components/uptimerobot.js b/src/components/uptimerobot.js index 0c0051d..6de6c05 100644 --- a/src/components/uptimerobot.js +++ b/src/components/uptimerobot.js @@ -275,7 +275,10 @@ const UptimeRobot = ({ apikey, pingUrl }) => { {t('pingStatus')} {(() => { const formatted = formatPing(pingResult) - const tooltipText = pingResult ? formatted.details : 'Измерение пинга...' + // Формируем tooltip без строки Details с переводами + const tooltipText = pingResult + ? `${t('pingAvg')}: ${pingResult.avg}ms\n${t('pingMinMax')}: ${pingResult.min}/${pingResult.max}ms` + : t('pingMeasuring') return (