AdminLTE3

This commit is contained in:
2025-10-26 22:14:47 +09:00
parent 291fc63a4c
commit 9974811a3e
226 changed files with 88284 additions and 3406 deletions

View File

@@ -1,241 +1,709 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Медиа Галерея - SmartSolTech Admin</title>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- Custom CSS -->
<link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="/css/fixes.css">
</head>
<body class="bg-gray-100">
<!-- Main Content -->
<main class="flex-1 p-8">
<div class="space-y-6">
<!-- Header -->
<div class="bg-white shadow rounded-lg p-6">
<div class="flex justify-between items-center">
<div>
<h1 class="text-2xl font-bold text-gray-900 flex items-center">
<i class="fas fa-images mr-3 text-blue-600"></i>
Медиа Галерея
</h1>
<p class="mt-2 text-gray-600">Управление изображениями и файлами сайта</p>
</div>
<div class="flex space-x-3">
<button id="refresh-btn" class="bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-lg transition-colors">
<i class="fas fa-sync-alt mr-2"></i>
Обновить
</button>
<button id="upload-btn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition-colors">
<i class="fas fa-upload mr-2"></i>
Загрузить файлы
</button>
</div>
</div>
</div>
<!-- Upload Zone -->
<div id="upload-zone" class="bg-white shadow rounded-lg p-8 border-2 border-dashed border-gray-300 text-center" style="display: none;">
<div class="mb-4">
<i class="fas fa-cloud-upload-alt text-6xl text-gray-400 mb-4"></i>
<p class="text-xl text-gray-600 mb-2">Перетащите файлы сюда или нажмите для выбора</p>
<p class="text-gray-500">Поддерживаются: JPG, PNG, GIF, SVG (максимум 10MB каждый)</p>
</div>
<input type="file" id="file-input" multiple accept="image/*" class="hidden">
<button type="button" onclick="document.getElementById('file-input').click()" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-lg">
Выбрать файлы
<!-- Content Header (Page header) -->
<section class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<h1><i class="fas fa-images mr-2"></i>Медиа Галерея</h1>
</div>
<div class="col-sm-6">
<div class="float-sm-right">
<button id="refresh-btn" class="btn btn-secondary">
<i class="fas fa-sync-alt mr-1"></i>Обновить
</button>
<button id="cancel-upload" class="bg-gray-500 hover:bg-gray-600 text-white px-6 py-3 rounded-lg ml-3">
Отмена
<button id="upload-btn" class="btn btn-primary">
<i class="fas fa-upload mr-1"></i>Загрузить файлы
</button>
</div>
<!-- Upload Progress -->
<div id="upload-progress" class="bg-white shadow rounded-lg p-6" style="display: none;">
<h3 class="text-lg font-semibold text-gray-900 mb-4">Загрузка файлов</h3>
<div class="space-y-3" id="progress-list">
<!-- Progress items will be added here -->
</div>
</div>
<!-- Filter and Search -->
<div class="bg-white shadow rounded-lg p-6">
<div class="flex flex-col md:flex-row md:items-center md:justify-between space-y-4 md:space-y-0">
<div class="flex space-x-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Тип файла</label>
<select id="file-type-filter" class="border border-gray-300 rounded-lg px-3 py-2">
<option value="">Все типы</option>
<option value="image/jpeg">JPEG</option>
<option value="image/png">PNG</option>
<option value="image/gif">GIF</option>
<option value="image/svg+xml">SVG</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Размер</label>
<select id="size-filter" class="border border-gray-300 rounded-lg px-3 py-2">
<option value="">Любой размер</option>
<option value="small">Маленький (&lt; 1MB)</option>
<option value="medium">Средний (1-5MB)</option>
<option value="large">Большой (&gt; 5MB)</option>
</select>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Поиск</label>
<div class="relative">
<input type="text" id="search-input" placeholder="Поиск по имени файла..." class="border border-gray-300 rounded-lg px-3 py-2 pr-10 w-64">
<i class="fas fa-search absolute right-3 top-3 text-gray-400"></i>
</div>
</div>
</div>
</div>
<!-- Media Grid -->
<div class="bg-white shadow rounded-lg p-6">
<div class="flex justify-between items-center mb-6">
<h3 class="text-lg font-semibold text-gray-900">Файлы</h3>
<div class="flex items-center space-x-4">
<span id="file-count" class="text-sm text-gray-600">Загрузка...</span>
<div class="flex space-x-2">
<button id="grid-view" class="p-2 text-gray-600 hover:text-gray-900 border border-gray-300 rounded">
<i class="fas fa-th-large"></i>
</button>
<button id="list-view" class="p-2 text-gray-600 hover:text-gray-900 border border-gray-300 rounded">
<i class="fas fa-list"></i>
</button>
</div>
</div>
</div>
<!-- Loading State -->
<div id="loading" class="text-center py-12">
<div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
<p class="mt-2 text-gray-600">Загрузка медиа файлов...</p>
</div>
<!-- Empty State -->
<div id="empty-state" class="text-center py-12" style="display: none;">
<i class="fas fa-images text-6xl text-gray-400 mb-4"></i>
<h3 class="text-xl font-semibold text-gray-900 mb-2">Нет загруженных файлов</h3>
<p class="text-gray-600 mb-6">Начните с загрузки ваших первых изображений</p>
<button onclick="document.getElementById('upload-btn').click()" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-lg">
<i class="fas fa-upload mr-2"></i>
Загрузить файлы
</button>
</div>
<!-- Media Grid -->
<div id="media-grid" class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6 gap-4">
<!-- Media items will be loaded here -->
</div>
<!-- Media List -->
<div id="media-list" class="space-y-4" style="display: none;">
<!-- List items will be loaded here -->
</div>
<!-- Pagination -->
<div id="pagination" class="mt-8 flex justify-center" style="display: none;">
<nav class="flex space-x-2">
<button id="prev-page" class="px-3 py-2 bg-gray-200 text-gray-600 rounded hover:bg-gray-300 disabled:opacity-50">
<i class="fas fa-chevron-left"></i>
</button>
<div id="page-numbers" class="flex space-x-2">
<!-- Page numbers will be added here -->
</div>
<button id="next-page" class="px-3 py-2 bg-gray-200 text-gray-600 rounded hover:bg-gray-300 disabled:opacity-50">
<i class="fas fa-chevron-right"></i>
</button>
</nav>
</div>
</div>
</div>
</main>
</div>
</div>
</section>
<!-- Media Preview Modal -->
<div id="preview-modal" class="fixed inset-0 bg-black bg-opacity-75 z-50 flex items-center justify-center" style="display: none;">
<div class="bg-white rounded-lg shadow-lg max-w-4xl max-h-[90vh] w-full mx-4 overflow-hidden">
<div class="p-4 border-b flex justify-between items-center">
<h3 id="modal-title" class="text-lg font-semibold text-gray-900">Предпросмотр файла</h3>
<button id="close-modal" class="text-gray-400 hover:text-gray-600">
<i class="fas fa-times text-xl"></i>
<!-- Main content -->
<section class="content">
<div class="container-fluid">
<!-- Upload Zone -->
<div id="upload-zone" class="card" style="display: none;">
<div class="card-body text-center">
<div class="mb-4">
<i class="fas fa-cloud-upload-alt fa-6x text-muted mb-4"></i>
<p class="h5 text-muted mb-2">Перетащите файлы сюда или нажмите для выбора</p>
<p class="text-muted">Поддерживаются: JPG, PNG, GIF, SVG (максимум 10MB каждый)</p>
</div>
<input type="file" id="file-input" multiple accept="image/*" class="d-none">
<button type="button" onclick="document.getElementById('file-input').click()" class="btn btn-primary">
Выбрать файлы
</button>
<button id="cancel-upload" class="btn btn-secondary ml-3">
Отмена
</button>
</div>
<div class="p-6">
<div class="flex flex-col lg:flex-row space-y-6 lg:space-y-0 lg:space-x-6">
<div class="flex-1">
<img id="modal-image" src="" alt="" class="w-full h-auto rounded-lg shadow">
</div>
<!-- Upload Progress -->
<div id="upload-progress" class="card" style="display: none;">
<div class="card-header">
<h3 class="card-title">Загрузка файлов</h3>
</div>
<div class="card-body">
<div id="progress-list">
<!-- Progress items will be added here -->
</div>
</div>
</div>
<!-- Filter and Search -->
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-md-3">
<div class="form-group">
<label>Тип файла</label>
<select id="file-type-filter" class="form-control">
<option value="">Все типы</option>
<option value="image/jpeg">JPEG</option>
<option value="image/png">PNG</option>
<option value="image/gif">GIF</option>
<option value="image/svg+xml">SVG</option>
</select>
</div>
</div>
<div class="w-full lg:w-80">
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Имя файла</label>
<input id="modal-filename" type="text" class="w-full border border-gray-300 rounded-lg px-3 py-2" readonly>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">URL</label>
<div class="flex">
<input id="modal-url" type="text" class="flex-1 border border-gray-300 rounded-l-lg px-3 py-2" readonly>
<button onclick="copyToClipboard()" class="bg-gray-100 border border-l-0 border-gray-300 rounded-r-lg px-3 py-2 hover:bg-gray-200">
<i class="fas fa-copy"></i>
</button>
<div class="col-md-3">
<div class="form-group">
<label>Размер</label>
<select id="size-filter" class="form-control">
<option value="">Любой размер</option>
<option value="small">Маленький (&lt; 1MB)</option>
<option value="medium">Средний (1-5MB)</option>
<option value="large">Большой (&gt; 5MB)</option>
</select>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>Поиск</label>
<div class="input-group">
<input type="text" id="search-input" placeholder="Поиск по имени файла..." class="form-control">
<div class="input-group-append">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Размер</label>
<p id="modal-size" class="text-sm text-gray-600">-</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Тип</label>
<p id="modal-type" class="text-sm text-gray-600">-</p>
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Ширина</label>
<p id="modal-width" class="text-sm text-gray-600">-</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Высота</label>
<p id="modal-height" class="text-sm text-gray-600">-</p>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Загружено</label>
<p id="modal-date" class="text-sm text-gray-600">-</p>
</div>
<div class="border-t pt-4 space-y-3">
<button onclick="downloadFile()" class="w-full bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg">
<i class="fas fa-download mr-2"></i>
Скачать
</button>
<button onclick="deleteFile()" class="w-full bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg">
<i class="fas fa-trash mr-2"></i>
Удалить
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- JavaScript -->
<script src="/js/main.js"></script>
<!-- Media Grid -->
<div class="card">
<div class="card-header">
<h3 class="card-title">Файлы</h3>
<div class="card-tools">
<span id="file-count" class="badge badge-secondary">Загрузка...</span>
<div class="btn-group ml-2">
<button id="grid-view" class="btn btn-sm btn-default">
<i class="fas fa-th-large"></i>
</button>
<button id="list-view" class="btn btn-sm btn-default">
<i class="fas fa-list"></i>
</button>
</div>
</div>
</div>
<div class="card-body">
<!-- Loading State -->
<div id="loading" class="text-center py-5">
<div class="spinner-border text-primary" role="status">
<span class="sr-only">Загрузка...</span>
</div>
<p class="mt-2 text-muted">Загрузка медиа файлов...</p>
</div>
<!-- Empty State -->
<div id="empty-state" class="text-center py-5" style="display: none;">
<i class="fas fa-images fa-6x text-muted mb-4"></i>
<h4 class="text-muted mb-2">Нет загруженных файлов</h4>
<p class="text-muted mb-4">Начните с загрузки ваших первых изображений</p>
<button onclick="document.getElementById('upload-btn').click()" class="btn btn-primary">
<i class="fas fa-upload mr-2"></i>Загрузить файлы
</button>
</div>
<!-- Media Grid -->
<div id="media-grid" class="row">
<!-- Media items will be loaded here -->
</div>
<!-- Media List -->
<div id="media-list" style="display: none;">
<!-- List items will be loaded here -->
</div>
</div>
<!-- Pagination -->
<div class="card-footer" id="pagination" style="display: none;">
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center m-0">
<li class="page-item">
<button id="prev-page" class="page-link">
<i class="fas fa-chevron-left"></i>
</button>
</li>
<div id="page-numbers" class="d-flex">
<!-- Page numbers will be added here -->
</div>
<li class="page-item">
<button id="next-page" class="page-link">
<i class="fas fa-chevron-right"></i>
</button>
</li>
</ul>
</nav>
</div>
</div>
</div>
</section>
<!-- Media Preview Modal -->
<div class="modal fade" id="preview-modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-xl" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 id="modal-title" class="modal-title">Предпросмотр файла</h4>
<button type="button" class="close" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-8">
<img id="modal-image" src="" alt="" class="img-fluid rounded">
</div>
<div class="col-md-4">
<div class="form-group">
<label>Имя файла</label>
<input id="modal-filename" type="text" class="form-control" readonly>
</div>
<div class="form-group">
<label>URL</label>
<div class="input-group">
<input id="modal-url" type="text" class="form-control" readonly>
<div class="input-group-append">
<button onclick="copyToClipboard()" class="btn btn-outline-secondary" type="button">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
</div>
<div class="row">
<div class="col-6">
<div class="form-group">
<label>Размер</label>
<p id="modal-size" class="form-control-plaintext">-</p>
</div>
</div>
<div class="col-6">
<div class="form-group">
<label>Тип</label>
<p id="modal-type" class="form-control-plaintext">-</p>
</div>
</div>
</div>
<div class="row">
<div class="col-6">
<div class="form-group">
<label>Ширина</label>
<p id="modal-width" class="form-control-plaintext">-</p>
</div>
</div>
<div class="col-6">
<div class="form-group">
<label>Высота</label>
<p id="modal-height" class="form-control-plaintext">-</p>
</div>
</div>
</div>
<div class="form-group">
<label>Загружено</label>
<p id="modal-date" class="form-control-plaintext">-</p>
</div>
<div class="btn-group d-flex">
<button onclick="downloadFile()" class="btn btn-primary">
<i class="fas fa-download mr-1"></i>Скачать
</button>
<button onclick="deleteFile()" class="btn btn-danger">
<i class="fas fa-trash mr-1"></i>Удалить
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Scripts -->
<script>
let currentFiles = [];
let filteredFiles = [];
let currentPage = 1;
let filesPerPage = 24;
let isGridView = true;
let currentFile = null;
document.addEventListener('DOMContentLoaded', function() {
loadFiles();
setupEventListeners();
});
function setupEventListeners() {
// Upload button
document.getElementById('upload-btn').addEventListener('click', function() {
document.getElementById('upload-zone').style.display = 'block';
});
// Cancel upload
document.getElementById('cancel-upload').addEventListener('click', function() {
document.getElementById('upload-zone').style.display = 'none';
});
// File input
document.getElementById('file-input').addEventListener('change', handleFileSelect);
// Refresh
document.getElementById('refresh-btn').addEventListener('click', loadFiles);
// View toggle
document.getElementById('grid-view').addEventListener('click', function() {
isGridView = true;
updateViewButtons();
renderFiles();
});
document.getElementById('list-view').addEventListener('click', function() {
isGridView = false;
updateViewButtons();
renderFiles();
});
// Filters
document.getElementById('file-type-filter').addEventListener('change', applyFilters);
document.getElementById('size-filter').addEventListener('change', applyFilters);
document.getElementById('search-input').addEventListener('input', applyFilters);
// Drag and drop
const uploadZone = document.getElementById('upload-zone');
uploadZone.addEventListener('dragover', function(e) {
e.preventDefault();
uploadZone.classList.add('border-blue-500');
});
uploadZone.addEventListener('dragleave', function(e) {
e.preventDefault();
uploadZone.classList.remove('border-blue-500');
});
uploadZone.addEventListener('drop', function(e) {
e.preventDefault();
uploadZone.classList.remove('border-blue-500');
handleFileSelect({ target: { files: e.dataTransfer.files } });
});
}
function updateViewButtons() {
const gridBtn = document.getElementById('grid-view');
const listBtn = document.getElementById('list-view');
if (isGridView) {
gridBtn.classList.add('bg-blue-500', 'text-white');
gridBtn.classList.remove('btn-default');
listBtn.classList.remove('bg-blue-500', 'text-white');
listBtn.classList.add('btn-default');
} else {
listBtn.classList.add('bg-blue-500', 'text-white');
listBtn.classList.remove('btn-default');
gridBtn.classList.remove('bg-blue-500', 'text-white');
gridBtn.classList.add('btn-default');
}
}
async function loadFiles() {
try {
document.getElementById('loading').style.display = 'block';
document.getElementById('empty-state').style.display = 'none';
document.getElementById('media-grid').style.display = 'none';
document.getElementById('media-list').style.display = 'none';
const response = await fetch('/api/admin/media');
const data = await response.json();
if (data.success) {
currentFiles = data.files;
filteredFiles = [...currentFiles];
updateFileCount();
renderFiles();
if (currentFiles.length === 0) {
document.getElementById('empty-state').style.display = 'block';
}
} else {
throw new Error(data.message);
}
} catch (error) {
console.error('Error loading files:', error);
alert('Ошибка загрузки файлов: ' + error.message);
} finally {
document.getElementById('loading').style.display = 'none';
}
}
function applyFilters() {
const typeFilter = document.getElementById('file-type-filter').value;
const sizeFilter = document.getElementById('size-filter').value;
const searchQuery = document.getElementById('search-input').value.toLowerCase();
filteredFiles = currentFiles.filter(file => {
let matches = true;
// Type filter
if (typeFilter && file.mimetype !== typeFilter) {
matches = false;
}
// Size filter
if (sizeFilter) {
const sizeMB = file.size / (1024 * 1024);
if (sizeFilter === 'small' && sizeMB >= 1) matches = false;
if (sizeFilter === 'medium' && (sizeMB < 1 || sizeMB > 5)) matches = false;
if (sizeFilter === 'large' && sizeMB <= 5) matches = false;
}
// Search filter
if (searchQuery && !file.filename.toLowerCase().includes(searchQuery)) {
matches = false;
}
return matches;
});
currentPage = 1;
updateFileCount();
renderFiles();
}
function updateFileCount() {
const countElement = document.getElementById('file-count');
countElement.textContent = `${filteredFiles.length} файлов`;
}
function renderFiles() {
const gridContainer = document.getElementById('media-grid');
const listContainer = document.getElementById('media-list');
if (isGridView) {
gridContainer.style.display = 'block';
listContainer.style.display = 'none';
renderGridView();
} else {
gridContainer.style.display = 'none';
listContainer.style.display = 'block';
renderListView();
}
renderPagination();
}
function renderGridView() {
const container = document.getElementById('media-grid');
const startIndex = (currentPage - 1) * filesPerPage;
const endIndex = startIndex + filesPerPage;
const pageFiles = filteredFiles.slice(startIndex, endIndex);
container.innerHTML = pageFiles.map(file => `
<div class="col-md-2 col-sm-3 col-6 mb-3">
<div class="card h-100">
<div class="card-img-top" style="height: 150px; background-image: url('${file.url}'); background-size: cover; background-position: center; cursor: pointer;" onclick="openPreview('${file._id}')"></div>
<div class="card-body p-2">
<p class="card-text small text-truncate" title="${file.filename}">${file.filename}</p>
<small class="text-muted">${formatFileSize(file.size)}</small>
</div>
</div>
</div>
`).join('');
}
function renderListView() {
const container = document.getElementById('media-list');
const startIndex = (currentPage - 1) * filesPerPage;
const endIndex = startIndex + filesPerPage;
const pageFiles = filteredFiles.slice(startIndex, endIndex);
container.innerHTML = pageFiles.map(file => `
<div class="card">
<div class="card-body">
<div class="row align-items-center">
<div class="col-md-1">
<img src="${file.url}" alt="${file.filename}" class="img-thumbnail" style="width: 60px; height: 60px; object-fit: cover;">
</div>
<div class="col-md-4">
<h6 class="mb-1">${file.filename}</h6>
<small class="text-muted">${file.mimetype}</small>
</div>
<div class="col-md-2">
<span class="badge badge-secondary">${formatFileSize(file.size)}</span>
</div>
<div class="col-md-3">
<small class="text-muted">${new Date(file.uploadedAt).toLocaleDateString('ru-RU')}</small>
</div>
<div class="col-md-2 text-right">
<div class="btn-group">
<button onclick="openPreview('${file._id}')" class="btn btn-sm btn-outline-primary">
<i class="fas fa-eye"></i>
</button>
<button onclick="downloadFile('${file._id}')" class="btn btn-sm btn-outline-secondary">
<i class="fas fa-download"></i>
</button>
<button onclick="deleteFile('${file._id}')" class="btn btn-sm btn-outline-danger">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
</div>
</div>
</div>
`).join('');
}
function renderPagination() {
const totalPages = Math.ceil(filteredFiles.length / filesPerPage);
const paginationContainer = document.getElementById('pagination');
if (totalPages <= 1) {
paginationContainer.style.display = 'none';
return;
}
paginationContainer.style.display = 'block';
// Update prev/next buttons
const prevBtn = document.getElementById('prev-page');
const nextBtn = document.getElementById('next-page');
prevBtn.disabled = currentPage === 1;
nextBtn.disabled = currentPage === totalPages;
prevBtn.onclick = () => {
if (currentPage > 1) {
currentPage--;
renderFiles();
}
};
nextBtn.onclick = () => {
if (currentPage < totalPages) {
currentPage++;
renderFiles();
}
};
// Update page numbers
const pageNumbersContainer = document.getElementById('page-numbers');
let pageNumbers = '';
for (let i = 1; i <= totalPages; i++) {
if (i === currentPage) {
pageNumbers += `<li class="page-item active"><span class="page-link">${i}</span></li>`;
} else {
pageNumbers += `<li class="page-item"><button class="page-link" onclick="goToPage(${i})">${i}</button></li>`;
}
}
pageNumbersContainer.innerHTML = pageNumbers;
}
function goToPage(page) {
currentPage = page;
renderFiles();
}
function formatFileSize(bytes) {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
function openPreview(fileId) {
const file = currentFiles.find(f => f._id === fileId);
if (!file) return;
currentFile = file;
document.getElementById('modal-title').textContent = file.filename;
document.getElementById('modal-image').src = file.url;
document.getElementById('modal-filename').value = file.filename;
document.getElementById('modal-url').value = file.url;
document.getElementById('modal-size').textContent = formatFileSize(file.size);
document.getElementById('modal-type').textContent = file.mimetype;
document.getElementById('modal-width').textContent = file.width || '-';
document.getElementById('modal-height').textContent = file.height || '-';
document.getElementById('modal-date').textContent = new Date(file.uploadedAt).toLocaleDateString('ru-RU');
$('#preview-modal').modal('show');
}
function copyToClipboard() {
const urlInput = document.getElementById('modal-url');
urlInput.select();
document.execCommand('copy');
// Show feedback
const btn = event.target.closest('button');
const originalHtml = btn.innerHTML;
btn.innerHTML = '<i class="fas fa-check"></i>';
setTimeout(() => {
btn.innerHTML = originalHtml;
}, 1000);
}
function downloadFile() {
if (currentFile) {
const link = document.createElement('a');
link.href = currentFile.url;
link.download = currentFile.filename;
link.click();
}
}
async function deleteFile() {
if (!currentFile) return;
if (!confirm(`Вы уверены, что хотите удалить файл "${currentFile.filename}"?`)) {
return;
}
try {
const response = await fetch(`/api/admin/media/${currentFile._id}`, {
method: 'DELETE'
});
const data = await response.json();
if (data.success) {
$('#preview-modal').modal('hide');
loadFiles();
alert('Файл успешно удален');
} else {
throw new Error(data.message);
}
} catch (error) {
console.error('Error deleting file:', error);
alert('Ошибка удаления файла: ' + error.message);
}
}
async function handleFileSelect(event) {
const files = Array.from(event.target.files);
if (files.length === 0) return;
document.getElementById('upload-zone').style.display = 'none';
document.getElementById('upload-progress').style.display = 'block';
const progressList = document.getElementById('progress-list');
progressList.innerHTML = '';
for (let i = 0; i < files.length; i++) {
const file = files[i];
const progressItem = createProgressItem(file.name, i);
progressList.appendChild(progressItem);
try {
await uploadFile(file, i);
} catch (error) {
updateProgress(i, 100, 'error', error.message);
}
}
setTimeout(() => {
document.getElementById('upload-progress').style.display = 'none';
loadFiles();
}, 2000);
}
function createProgressItem(filename, index) {
const div = document.createElement('div');
div.className = 'progress-item';
div.innerHTML = `
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="filename">${filename}</span>
<span class="status" id="status-${index}">Загрузка...</span>
</div>
<div class="progress">
<div class="progress-bar" id="progress-${index}" role="progressbar" style="width: 0%"></div>
</div>
`;
return div;
}
function updateProgress(index, percent, status, message) {
const progressBar = document.getElementById(`progress-${index}`);
const statusSpan = document.getElementById(`status-${index}`);
if (progressBar) {
progressBar.style.width = percent + '%';
if (status === 'success') {
progressBar.classList.add('bg-success');
statusSpan.textContent = 'Готово';
statusSpan.classList.add('text-success');
} else if (status === 'error') {
progressBar.classList.add('bg-danger');
statusSpan.textContent = 'Ошибка: ' + message;
statusSpan.classList.add('text-danger');
} else {
statusSpan.textContent = Math.round(percent) + '%';
}
}
}
async function uploadFile(file, index) {
const formData = new FormData();
formData.append('file', file);
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', function(e) {
if (e.lengthComputable) {
const percent = (e.loaded / e.total) * 100;
updateProgress(index, percent, 'uploading');
}
});
xhr.addEventListener('load', function() {
if (xhr.status === 200) {
const response = JSON.parse(xhr.responseText);
if (response.success) {
updateProgress(index, 100, 'success');
resolve(response);
} else {
reject(new Error(response.message));
}
} else {
reject(new Error('HTTP ' + xhr.status));
}
});
xhr.addEventListener('error', function() {
reject(new Error('Network error'));
});
xhr.open('POST', '/api/admin/media/upload');
xhr.send(formData);
});
}
</script>
<script>
class MediaGallery {