Files
file_storage/templates/index.html
T

262 lines
13 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% 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 %}