Почти полностью рабочая версия
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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';
|
||||
}
|
||||
Reference in New Issue
Block a user