Почти полностью рабочая версия

This commit is contained in:
2025-12-20 21:52:40 +03:00
parent 721f224c8e
commit 5396c3d0ff
8 changed files with 892 additions and 18 deletions
+223
View File
@@ -0,0 +1,223 @@
/* Карточки */
.card {
border-radius: 10px;
border: 1px solid #e9ecef;
transition: all 0.3s ease;
overflow: hidden;
}
.card:hover {
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);
}
.card-header {
background-color: rgba(0, 0, 0, 0.02);
border-bottom: 1px solid #e9ecef;
padding: 1rem 1.25rem;
}
.card-header h5 {
font-weight: 600;
}
/* Статус индикаторы */
.status-indicator {
width: 12px;
height: 12px;
border-radius: 50%;
display: inline-block;
}
.status-online {
background-color: #198754;
box-shadow: 0 0 0 2px rgba(25, 135, 84, 0.2);
}
.status-offline {
background-color: #6c757d;
box-shadow: 0 0 0 2px rgba(108, 117, 125, 0.2);
}
/* Быстрые действия */
.btn-outline-info,
.btn-outline-success,
.btn-outline-warning,
.btn-outline-secondary {
border-width: 2px;
transition: all 0.3s ease;
}
.btn-outline-info:hover {
background-color: #0dcaf0;
color: white;
border-color: #0dcaf0;
}
.btn-outline-success:hover {
background-color: #198754;
color: white;
border-color: #198754;
}
.btn-outline-warning:hover {
background-color: #ffc107;
color: black;
border-color: #ffc107;
}
.btn-outline-secondary:hover {
background-color: #6c757d;
color: white;
border-color: #6c757d;
}
/* Логи */
.logs-container {
max-height: 400px;
overflow-y: auto;
background-color: #f8f9fa;
border-radius: 6px;
padding: 1rem;
font-family: 'SF Mono', Monaco, 'Cascadia Code', Consolas, 'Courier New', monospace;
font-size: 12px;
line-height: 1.4;
}
.logs-pre {
margin: 0;
white-space: pre-wrap;
word-break: break-word;
color: #212529;
}
.logs-pre .log-info {
color: #0d6efd;
}
.logs-pre .log-success {
color: #198754;
}
.logs-pre .log-warning {
color: #ffc107;
}
.logs-pre .log-error {
color: #dc3545;
}
/* Адаптивные карточки */
@media (max-width: 768px) {
.card-header h5 {
font-size: 1rem;
}
.fw-bold.fs-5 {
font-size: 1.1rem !important;
}
.logs-container {
font-size: 11px;
}
}
/* Анимации */
.fade-in {
animation: fadeIn 0.5s ease-in;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Уведомления */
.alert-fixed {
position: fixed;
top: 80px;
right: 20px;
z-index: 1050;
min-width: 300px;
max-width: 400px;
}
/* Карточки с фоном */
.card.bg-light {
border: none;
}
/* Информационные блоки */
.alert {
border-radius: 8px;
border: none;
}
/* Кнопки действий */
.btn-sm {
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
}
/* Пустые состояния */
.text-center.py-4 .display-1 {
font-size: 4rem;
color: #dee2e6;
margin-bottom: 1rem;
}
/* Бейджи */
.badge {
font-weight: 500;
padding: 0.35em 0.65em;
font-size: 0.85em;
}
/* Таблицы и сетки */
.row.g-3 {
margin-top: -0.75rem;
}
.row.g-3 > [class^="col-"] {
margin-top: 0.75rem;
}
/* Иконки */
.bi {
vertical-align: middle;
}
/* Отступы для контента */
.card-body {
padding: 1.25rem;
}
/* Ховер эффекты для карточек быстрых действий */
.btn:hover .bi {
transform: scale(1.1);
transition: transform 0.3s ease;
}
/* Скроллбар для логов */
.logs-container::-webkit-scrollbar {
width: 6px;
}
.logs-container::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
.logs-container::-webkit-scrollbar-thumb {
background: #888;
border-radius: 3px;
}
.logs-container::-webkit-scrollbar-thumb:hover {
background: #555;
}
+140
View File
@@ -0,0 +1,140 @@
// Инициализация при загрузке страницы
document.addEventListener('DOMContentLoaded', function () {
// Устанавливаем время последнего обновления
updateLastUpdateTime();
// Добавляем подсветку логов
highlightLogs();
});
// Обновление времени последнего обновления
function updateLastUpdateTime() {
const now = new Date();
const timeString = now.toLocaleTimeString('ru-RU', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
const dateString = now.toLocaleDateString('ru-RU', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
});
document.getElementById('lastUpdateTime').textContent = `${dateString} ${timeString}`;
}
// Подсветка логов
function highlightLogs() {
const logsContainer = document.getElementById('logsContainer');
if (!logsContainer) return;
const pre = logsContainer.querySelector('pre');
if (!pre) return;
let content = pre.innerHTML;
// Обработка строкового символа
content = content.replace(/\x1B\[[0-9;]*m/g, '');
// Подсветка уровней логирования
content = content.replace(/\|\sINFO\s\|/g, '| <span class="log-success">INFO</span> |');
content = content.replace(/\|\sWARNING\s\|/g, '| <span class="log-warning">WARNING</span> |');
content = content.replace(/\|\sERROR\s\|/g, '| <span class="log-error">ERROR</span> |');
// Подсветка дат и времени
content = content.replace(/(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/g, '<span class="text-primary">$1</span>');
// Подсветка IP адресов
content = content.replace(/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/g, '<span class="text-success">$1</span>');
// Подсветка HTTP методов
content = content.replace(/(GET|POST|PUT|DELETE|PATCH) (\S+)/g, '<span class="fw-semibold text-info">$1</span> <span class="text-dark">$2</span>');
// Подсветка кодов состояний
content = content.replace(
/"\s(\d{3})\s-/g,
(match, code) => {
let cls = 'text-secondary';
if (code >= 200 && code < 300) cls = 'text-success';
else if (code >= 400 && code < 500) cls = 'text-warning';
else if (code >= 500) cls = 'text-danger';
return `" <span class="fw-bold ${cls}">${code}</span> -`;
}
);
// Подсветка статических файлов
content = content.replace(
/(\/static\/[^\s"]+)/g,
'<span class="text-muted fw-semibold">$1</span>'
);
pre.innerHTML = content;
}
// Переключение видимости логов
function toggleLogs() {
const logsBody = document.getElementById('logsBody');
const toggleBtn = document.querySelector('.card-header .bi-chevron-down, .card-header .bi-chevron-up');
const logsDescription = document.getElementById('logs_description');
if (logsBody.classList.contains('d-none')) {
logsBody.classList.remove('d-none');
toggleBtn.classList.remove('bi-chevron-down');
toggleBtn.classList.add('bi-chevron-up');
logsDescription.classList.remove('d-none');
refreshLogs();
} else {
logsBody.classList.add('d-none');
logsDescription.classList.add('d-none');
toggleBtn.classList.remove('bi-chevron-up');
toggleBtn.classList.add('bi-chevron-down');
}
}
// Обновление логов
async function refreshLogs() {
try {
const logsContainer = document.getElementById('logsContainer');
if (!logsContainer) return;
const pre = logsContainer.querySelector('pre');
if (!pre) return;
// Показываем индикатор загрузки
pre.innerHTML = `
<div class="text-center py-4">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Загрузка логов...</span>
</div>
<p class="mt-2 text-muted">Загрузка логов...</p>
</div>
`;
// Имитация загрузки логов (в реальном приложении здесь будет fetch запрос)
const response = await fetch('/', {
method: 'POST',
});
const data = await response.json();
pre.innerHTML = data.logs;
highlightLogs();
} catch (error) {
console.error('Ошибка при обновлении логов:', error);
}
}
function getAlertIcon(type) {
const icons = {
'success': 'bi-check-circle-fill',
'warning': 'bi-exclamation-triangle-fill',
'danger': 'bi-x-circle-fill',
'info': 'bi-info-circle-fill'
};
return icons[type] || 'bi-info-circle-fill';
}