262 lines
13 KiB
HTML
262 lines
13 KiB
HTML
{% extends "base.html" %}
|
||
{% block content %}
|
||
<div class="row">
|
||
<div class="col-md-3">
|
||
<div class="card">
|
||
<div class="card-header">Информация о диске</div>
|
||
<div class="card-body">
|
||
<p>Всего: {{ format_size(disk.total) }}</p>
|
||
<p>Занято: {{ format_size(disk.used) }} ({{ disk.percent }}%)</p>
|
||
<p>Свободно: {{ format_size(disk.free) }}</p>
|
||
<div class="progress"><div class="progress-bar" style="width: {{ disk.percent }}%"></div></div>
|
||
</div>
|
||
</div>
|
||
<div class="card mt-3">
|
||
<div class="card-header">Загрузить</div>
|
||
<div class="card-body">
|
||
<div id="dropzone" style="border:2px dashed #ccc; padding:20px; text-align:center; cursor:pointer;">
|
||
Перетащите файлы сюда<br><small>или кликните для выбора</small>
|
||
<input type="file" id="fileInput" style="display:none;" multiple>
|
||
</div>
|
||
<button id="createFolderBtn" class="btn btn-outline-secondary mt-2 w-100">📁 Создать папку</button>
|
||
</div>
|
||
</div>
|
||
<a href="{{ url_for('view_logs') }}" class="btn btn-secondary mt-3">📋 Логи</a>
|
||
</div>
|
||
<div class="col-md-9">
|
||
<nav aria-label="breadcrumb">
|
||
<ol class="breadcrumb">
|
||
{% for crumb in breadcrumbs %}
|
||
<li class="breadcrumb-item"><a href="{{ url_for('index', path=crumb.path) }}">{{ crumb.name }}</a></li>
|
||
{% endfor %}
|
||
</ol>
|
||
</nav>
|
||
|
||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||
{% for category, message in messages %}
|
||
<div class="alert alert-{{ category }} alert-dismissible fade show">{{ message }}<button type="button" class="btn-close" data-bs-dismiss="alert"></button></div>
|
||
{% endfor %}
|
||
{% endwith %}
|
||
|
||
<div class="row" id="fileGrid">
|
||
{% for item in items %}
|
||
<div class="col-md-3 mb-3" data-path="{{ item.path }}" data-type="{{ item.type }}">
|
||
<div class="card h-100">
|
||
{% if item.type == 'dir' %}
|
||
<div class="card-body text-center" style="cursor:pointer;" onclick="window.location.href='{{ url_for('index', path=item.path) }}'">
|
||
<div style="font-size: 48px;">📁</div>
|
||
<h6 class="card-title">{{ item.name }}</h6>
|
||
<small>{{ item.modified }}</small>
|
||
</div>
|
||
{% else %}
|
||
<div class="card-body text-center">
|
||
{% if item.is_image and item.thumb %}
|
||
<img src="data:image/jpeg;base64,{{ item.thumb }}" class="img-thumbnail" style="max-height:100px;" alt="{{ item.name }}">
|
||
{% else %}
|
||
<div style="font-size: 48px;">📄</div>
|
||
{% endif %}
|
||
<h6 class="card-title text-truncate">{{ item.name }}</h6>
|
||
<small>{{ item.size_formatted }}</small><br>
|
||
<div class="btn-group btn-group-sm mt-2">
|
||
<button class="btn btn-outline-primary preview-btn" data-path="{{ item.path }}" data-name="{{ item.name }}" data-mime="{{ item.mime }}">👁️</button>
|
||
<a href="{{ url_for('download_file', filename=item.path, download=1) }}" class="btn btn-outline-success">⬇️</a>
|
||
<button class="btn btn-outline-danger delete-btn" data-path="{{ item.path }}">🗑️</button>
|
||
<button class="btn btn-outline-secondary move-btn" data-path="{{ item.path }}">📂</button>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
{% else %}
|
||
<div class="col-12"><div class="alert alert-info">Папка пуста</div></div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Модалки -->
|
||
<div class="modal fade" id="previewModal" tabindex="-1" aria-hidden="true">
|
||
<div class="modal-dialog modal-xl modal-dialog-centered">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="previewTitle">Просмотр</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body text-center" id="previewBody">
|
||
<div class="spinner-border"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal fade" id="folderModal" tabindex="-1">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header"><h5>Создать папку</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div>
|
||
<div class="modal-body">
|
||
<input type="text" id="folderNameInput" class="form-control" placeholder="Имя папки">
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
|
||
<button id="confirmFolderBtn" class="btn btn-primary">Создать</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal fade" id="moveModal" tabindex="-1">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header"><h5>Переместить в папку</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div>
|
||
<div class="modal-body">
|
||
<p>Укажите относительный путь к папке назначения (например: <code>документы/проекты</code>)</p>
|
||
<input type="text" id="moveDestInput" class="form-control" placeholder="Путь к папке">
|
||
<input type="hidden" id="moveSourcePath">
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
|
||
<button id="confirmMoveBtn" class="btn btn-primary">Переместить</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{% endblock %}
|
||
|
||
{% block scripts %}
|
||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
const currentPath = "{{ current_path }}";
|
||
const dropzone = document.getElementById('dropzone');
|
||
const fileInput = document.getElementById('fileInput');
|
||
|
||
dropzone.addEventListener('click', () => fileInput.click());
|
||
dropzone.addEventListener('dragover', (e) => { e.preventDefault(); dropzone.style.borderColor = '#0d6efd'; });
|
||
dropzone.addEventListener('dragleave', () => { dropzone.style.borderColor = '#ccc'; });
|
||
dropzone.addEventListener('drop', (e) => {
|
||
e.preventDefault();
|
||
dropzone.style.borderColor = '#ccc';
|
||
if (e.dataTransfer.files.length) uploadFiles(e.dataTransfer.files);
|
||
});
|
||
fileInput.addEventListener('change', () => {
|
||
if (fileInput.files.length) {
|
||
uploadFiles(fileInput.files);
|
||
fileInput.value = '';
|
||
}
|
||
});
|
||
|
||
function uploadFiles(files) {
|
||
for (let file of files) {
|
||
const formData = new FormData();
|
||
formData.append('file', file);
|
||
formData.append('path', currentPath);
|
||
fetch('{{ url_for("upload") }}', { method: 'POST', body: formData })
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) location.reload();
|
||
else alert('Ошибка: ' + data.error);
|
||
})
|
||
.catch(err => alert('Ошибка: ' + err));
|
||
}
|
||
}
|
||
|
||
// Создание папки
|
||
document.getElementById('createFolderBtn').addEventListener('click', () => {
|
||
new bootstrap.Modal(document.getElementById('folderModal')).show();
|
||
});
|
||
document.getElementById('confirmFolderBtn').addEventListener('click', () => {
|
||
const name = document.getElementById('folderNameInput').value.trim();
|
||
if (!name) { alert('Введите имя папки'); return; }
|
||
fetch('{{ url_for("create_folder") }}', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||
body: new URLSearchParams({ path: currentPath, folder_name: name })
|
||
})
|
||
.then(r => r.json())
|
||
.then(data => {
|
||
if (data.success) location.reload();
|
||
else alert('Ошибка: ' + data.error);
|
||
});
|
||
});
|
||
|
||
// Предпросмотр
|
||
document.querySelectorAll('.preview-btn').forEach(btn => {
|
||
btn.addEventListener('click', function() {
|
||
const path = this.dataset.path;
|
||
const name = this.dataset.name;
|
||
const mime = this.dataset.mime;
|
||
const modal = new bootstrap.Modal(document.getElementById('previewModal'));
|
||
document.getElementById('previewTitle').innerText = name;
|
||
const body = document.getElementById('previewBody');
|
||
body.innerHTML = '<div class="spinner-border"></div>';
|
||
|
||
fetch('{{ url_for("download_file", filename="") }}' + encodeURIComponent(path))
|
||
.then(r => {
|
||
if (!r.ok) throw new Error('Не удалось загрузить');
|
||
return r.blob();
|
||
})
|
||
.then(blob => {
|
||
const url = URL.createObjectURL(blob);
|
||
let html = '';
|
||
if (mime.startsWith('image/')) {
|
||
html = `<img src="${url}" class="img-fluid" style="max-height:80vh;">`;
|
||
} else if (mime.startsWith('video/')) {
|
||
html = `<video controls class="w-100" style="max-height:80vh;"><source src="${url}" type="${mime}"></video>`;
|
||
} else if (mime === 'application/pdf') {
|
||
html = `<embed src="${url}" type="application/pdf" class="w-100" style="height:80vh;">`;
|
||
} else if (mime.startsWith('text/') || mime === 'application/json') {
|
||
blob.text().then(text => {
|
||
body.innerHTML = `<pre class="text-start" style="max-height:80vh; overflow:auto;">${text}</pre>`;
|
||
});
|
||
return;
|
||
} else {
|
||
html = `<p>Предпросмотр недоступен.<br><a href="${url}" download="${name}">Скачать</a></p>`;
|
||
}
|
||
body.innerHTML = html;
|
||
})
|
||
.catch(err => {
|
||
body.innerHTML = `<div class="alert alert-danger">${err.message}</div>`;
|
||
});
|
||
modal.show();
|
||
});
|
||
});
|
||
|
||
// Удаление
|
||
document.querySelectorAll('.delete-btn').forEach(btn => {
|
||
btn.addEventListener('click', function() {
|
||
if (!confirm('Удалить?')) return;
|
||
fetch('{{ url_for("delete_file", filename="") }}' + encodeURIComponent(this.dataset.path), { method: 'POST' })
|
||
.then(r => {
|
||
if (r.redirected) window.location.href = r.url;
|
||
else location.reload();
|
||
});
|
||
});
|
||
});
|
||
|
||
// Перемещение
|
||
document.querySelectorAll('.move-btn').forEach(btn => {
|
||
btn.addEventListener('click', function() {
|
||
document.getElementById('moveSourcePath').value = this.dataset.path;
|
||
new bootstrap.Modal(document.getElementById('moveModal')).show();
|
||
});
|
||
});
|
||
document.getElementById('confirmMoveBtn').addEventListener('click', function() {
|
||
const source = document.getElementById('moveSourcePath').value;
|
||
const dest = document.getElementById('moveDestInput').value.trim();
|
||
if (!dest) { alert('Укажите папку назначения'); return; }
|
||
fetch('{{ url_for("move_item") }}', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||
body: new URLSearchParams({ source_path: source, dest_path: dest })
|
||
})
|
||
.then(r => r.json())
|
||
.then(data => {
|
||
if (data.success) location.reload();
|
||
else alert('Ошибка: ' + data.error);
|
||
});
|
||
});
|
||
});
|
||
</script>
|
||
{% endblock %}
|