new file: file_manager.py modified: requirements.txt new file: sessions_db.json modified: templates/base.html new file: templates/file_manager.html new file: templates/file_manager_new.html new file: templates/login.html new file: templates/profile.html new file: templates/users_management.html new file: user_management.py new file: users_db.json
566 lines
19 KiB
HTML
566 lines
19 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}File Manager - {{ super() }}{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid">
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h4 class="mb-0">
|
|
<i class="fas fa-folder-open me-2"></i>
|
|
File Manager
|
|
</h4>
|
|
<p class="mb-0 mt-1 text-muted">Verwalte deine Projektdateien</p>
|
|
</div>
|
|
|
|
<div class="card-body">
|
|
<!-- Navigation -->
|
|
<div class="mb-3 p-2 bg-light rounded">
|
|
<nav aria-label="breadcrumb">
|
|
<ol class="breadcrumb mb-0" id="breadcrumb">
|
|
<li class="breadcrumb-item">
|
|
<a href="#" onclick="navigateTo('')">
|
|
<i class="fas fa-home"></i> Root
|
|
</a>
|
|
</li>
|
|
</ol>
|
|
</nav>
|
|
</div>
|
|
|
|
<!-- Toolbar -->
|
|
<div class="mb-3">
|
|
<div class="btn-toolbar" role="toolbar">
|
|
<div class="btn-group me-2" role="group">
|
|
<button type="button" class="btn btn-outline-primary btn-sm" onclick="createFolder()">
|
|
<i class="fas fa-folder-plus"></i> Ordner
|
|
</button>
|
|
<button type="button" class="btn btn-outline-success btn-sm" onclick="createFile()">
|
|
<i class="fas fa-file-plus"></i> Datei
|
|
</button>
|
|
</div>
|
|
|
|
<div class="btn-group me-2" role="group">
|
|
<input type="file" id="fileInput" multiple style="display: none;">
|
|
<button type="button" class="btn btn-outline-info btn-sm" onclick="uploadFiles()">
|
|
<i class="fas fa-upload"></i> Hochladen
|
|
</button>
|
|
</div>
|
|
|
|
<div class="btn-group" role="group">
|
|
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="refreshFiles()">
|
|
<i class="fas fa-sync-alt"></i> Aktualisieren
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- File List -->
|
|
<div class="table-responsive">
|
|
<table class="table table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th>Name</th>
|
|
<th>Typ</th>
|
|
<th>Größe</th>
|
|
<th>Geändert</th>
|
|
<th>Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="fileList">
|
|
<tr>
|
|
<td colspan="5" class="text-center py-4">
|
|
<div class="spinner-border text-primary" role="status">
|
|
<span class="visually-hidden">Lade...</span>
|
|
</div>
|
|
<p class="mt-2 mb-0">Lade Dateien...</p>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- File Editor Modal -->
|
|
<div class="modal fade" id="editorModal" tabindex="-1">
|
|
<div class="modal-dialog modal-xl">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">
|
|
<i class="fas fa-edit me-2"></i>
|
|
<span id="editorFileName">Datei bearbeiten</span>
|
|
</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<textarea id="fileEditor" class="form-control" rows="20" style="font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;"></textarea>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Schließen</button>
|
|
<button type="button" class="btn btn-primary" onclick="saveFile()">
|
|
<i class="fas fa-save"></i> Speichern
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Create Folder Modal -->
|
|
<div class="modal fade" id="createFolderModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Neuer Ordner</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="mb-3">
|
|
<label for="folderName" class="form-label">Ordnername</label>
|
|
<input type="text" class="form-control" id="folderName" placeholder="Ordnername eingeben...">
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
|
<button type="button" class="btn btn-primary" onclick="createFolderConfirm()">Erstellen</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Create File Modal -->
|
|
<div class="modal fade" id="createFileModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Neue Datei</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="mb-3">
|
|
<label for="fileName" class="form-label">Dateiname</label>
|
|
<input type="text" class="form-control" id="fileName" placeholder="Dateiname.txt">
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
|
<button type="button" class="btn btn-primary" onclick="createFileConfirm()">Erstellen</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Rename Modal -->
|
|
<div class="modal fade" id="renameModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Umbenennen</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="mb-3">
|
|
<label for="newName" class="form-label">Neuer Name</label>
|
|
<input type="text" class="form-control" id="newName" placeholder="Neuer Name...">
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
|
<button type="button" class="btn btn-primary" onclick="renameFileConfirm()">Umbenennen</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let currentPath = '';
|
|
let currentEditFile = '';
|
|
|
|
// Initialize
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
loadFiles();
|
|
setupFileInput();
|
|
});
|
|
|
|
function setupFileInput() {
|
|
document.getElementById('fileInput').addEventListener('change', function(e) {
|
|
handleFileUpload(e.target.files);
|
|
});
|
|
}
|
|
|
|
function loadFiles() {
|
|
fetch(`/api/files/list?path=${encodeURIComponent(currentPath)}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.error) {
|
|
showError('Fehler beim Laden: ' + data.error);
|
|
return;
|
|
}
|
|
renderFiles(data.items || []);
|
|
updateBreadcrumb(data.breadcrumbs || []);
|
|
})
|
|
.catch(error => {
|
|
showError('Netzwerkfehler: ' + error.message);
|
|
});
|
|
}
|
|
|
|
function renderFiles(files) {
|
|
const tbody = document.getElementById('fileList');
|
|
|
|
if (files.length === 0) {
|
|
tbody.innerHTML = `
|
|
<tr>
|
|
<td colspan="5" class="text-center py-4 text-muted">
|
|
<i class="fas fa-folder-open fa-3x mb-3"></i>
|
|
<p>Dieser Ordner ist leer</p>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
let html = '';
|
|
|
|
files.forEach(file => {
|
|
const icon = getFileIcon(file);
|
|
const size = file.type === 'directory' ? '-' : (file.size || '0 B');
|
|
const modified = file.modified || '-';
|
|
|
|
html += `
|
|
<tr>
|
|
<td>
|
|
<i class="${icon}"></i>
|
|
<span class="ms-2">${file.name}</span>
|
|
${file.is_parent ? '<small class="text-muted">(Zurück)</small>' : ''}
|
|
</td>
|
|
<td>
|
|
<span class="badge ${file.type === 'directory' ? 'bg-warning' : 'bg-info'}">
|
|
${file.type === 'directory' ? 'Ordner' : 'Datei'}
|
|
</span>
|
|
</td>
|
|
<td>${size}</td>
|
|
<td>${modified}</td>
|
|
<td>
|
|
<div class="btn-group btn-group-sm" role="group">
|
|
<button class="btn btn-outline-primary" onclick="openFile('${file.path}', '${file.type}')">
|
|
<i class="fas fa-eye"></i>
|
|
</button>
|
|
${file.type !== 'directory' ? `
|
|
<button class="btn btn-outline-success" onclick="editFile('${file.path}')">
|
|
<i class="fas fa-edit"></i>
|
|
</button>
|
|
<button class="btn btn-outline-info" onclick="downloadFile('${file.path}')">
|
|
<i class="fas fa-download"></i>
|
|
</button>
|
|
` : ''}
|
|
<button class="btn btn-outline-warning" onclick="renameFile('${file.path}', '${file.name}')">
|
|
<i class="fas fa-i-cursor"></i>
|
|
</button>
|
|
<button class="btn btn-outline-danger" onclick="deleteFile('${file.path}', '${file.name}')">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
});
|
|
|
|
tbody.innerHTML = html;
|
|
}
|
|
|
|
function getFileIcon(file) {
|
|
if (file.is_parent) {
|
|
return 'fas fa-arrow-left text-secondary';
|
|
}
|
|
|
|
if (file.type === 'directory') {
|
|
return 'fas fa-folder text-warning';
|
|
}
|
|
|
|
const extension = file.name.split('.').pop().toLowerCase();
|
|
const iconMap = {
|
|
'txt': 'fas fa-file-alt text-info',
|
|
'py': 'fab fa-python text-primary',
|
|
'js': 'fab fa-js-square text-warning',
|
|
'html': 'fab fa-html5 text-danger',
|
|
'css': 'fab fa-css3-alt text-primary',
|
|
'json': 'fas fa-file-code text-warning',
|
|
'yml': 'fas fa-file-code text-info',
|
|
'yaml': 'fas fa-file-code text-info',
|
|
'md': 'fas fa-file-alt text-info',
|
|
'log': 'fas fa-file-alt text-muted',
|
|
'zip': 'fas fa-file-archive text-secondary',
|
|
'jpg': 'fas fa-file-image text-success',
|
|
'png': 'fas fa-file-image text-success',
|
|
'pdf': 'fas fa-file-pdf text-danger'
|
|
};
|
|
|
|
return iconMap[extension] || 'fas fa-file text-muted';
|
|
}
|
|
|
|
function updateBreadcrumb(breadcrumbs) {
|
|
const nav = document.getElementById('breadcrumb');
|
|
|
|
let html = `
|
|
<li class="breadcrumb-item">
|
|
<a href="#" onclick="navigateTo('')">
|
|
<i class="fas fa-home"></i> Root
|
|
</a>
|
|
</li>
|
|
`;
|
|
|
|
breadcrumbs.forEach((crumb, index) => {
|
|
if (crumb.name !== 'Root') {
|
|
if (index === breadcrumbs.length - 1) {
|
|
html += `<li class="breadcrumb-item active">${crumb.name}</li>`;
|
|
} else {
|
|
html += `<li class="breadcrumb-item"><a href="#" onclick="navigateTo('${crumb.path}')">${crumb.name}</a></li>`;
|
|
}
|
|
}
|
|
});
|
|
|
|
nav.innerHTML = html;
|
|
}
|
|
|
|
function navigateTo(path) {
|
|
currentPath = path;
|
|
loadFiles();
|
|
}
|
|
|
|
function openFile(path, type) {
|
|
if (type === 'directory') {
|
|
navigateTo(path);
|
|
} else {
|
|
editFile(path);
|
|
}
|
|
}
|
|
|
|
function editFile(path) {
|
|
fetch(`/api/files/read?path=${encodeURIComponent(path)}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.error) {
|
|
showError('Fehler beim Laden: ' + data.error);
|
|
return;
|
|
}
|
|
|
|
if (data.is_binary) {
|
|
showError('Binäre Dateien können nicht bearbeitet werden.');
|
|
return;
|
|
}
|
|
|
|
document.getElementById('editorFileName').textContent = path.split('/').pop();
|
|
document.getElementById('fileEditor').value = data.content;
|
|
currentEditFile = path;
|
|
|
|
new bootstrap.Modal(document.getElementById('editorModal')).show();
|
|
})
|
|
.catch(error => {
|
|
showError('Netzwerkfehler: ' + error.message);
|
|
});
|
|
}
|
|
|
|
function saveFile() {
|
|
if (!currentEditFile) return;
|
|
|
|
const content = document.getElementById('fileEditor').value;
|
|
|
|
fetch('/api/files/write', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ path: currentEditFile, content: content })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.error) {
|
|
showError('Fehler beim Speichern: ' + data.error);
|
|
return;
|
|
}
|
|
|
|
showSuccess('Datei gespeichert!');
|
|
bootstrap.Modal.getInstance(document.getElementById('editorModal')).hide();
|
|
})
|
|
.catch(error => {
|
|
showError('Netzwerkfehler: ' + error.message);
|
|
});
|
|
}
|
|
|
|
function downloadFile(path) {
|
|
window.open(`/api/files/download?path=${encodeURIComponent(path)}`, '_blank');
|
|
}
|
|
|
|
function deleteFile(path, name) {
|
|
if (!confirm(`Möchten Sie "${name}" wirklich löschen?`)) return;
|
|
|
|
fetch('/api/files/delete', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ path: path })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.error) {
|
|
showError('Fehler beim Löschen: ' + data.error);
|
|
return;
|
|
}
|
|
|
|
showSuccess('Element gelöscht!');
|
|
loadFiles();
|
|
})
|
|
.catch(error => {
|
|
showError('Netzwerkfehler: ' + error.message);
|
|
});
|
|
}
|
|
|
|
function createFolder() {
|
|
document.getElementById('folderName').value = '';
|
|
new bootstrap.Modal(document.getElementById('createFolderModal')).show();
|
|
}
|
|
|
|
function createFolderConfirm() {
|
|
const name = document.getElementById('folderName').value.trim();
|
|
if (!name) return;
|
|
|
|
const path = currentPath ? `${currentPath}/${name}` : name;
|
|
|
|
fetch('/api/files/create_directory', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ path: path })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.error) {
|
|
showError('Fehler beim Erstellen: ' + data.error);
|
|
return;
|
|
}
|
|
|
|
showSuccess('Ordner erstellt!');
|
|
loadFiles();
|
|
bootstrap.Modal.getInstance(document.getElementById('createFolderModal')).hide();
|
|
})
|
|
.catch(error => {
|
|
showError('Netzwerkfehler: ' + error.message);
|
|
});
|
|
}
|
|
|
|
function createFile() {
|
|
document.getElementById('fileName').value = '';
|
|
new bootstrap.Modal(document.getElementById('createFileModal')).show();
|
|
}
|
|
|
|
function createFileConfirm() {
|
|
const name = document.getElementById('fileName').value.trim();
|
|
if (!name) return;
|
|
|
|
const path = currentPath ? `${currentPath}/${name}` : name;
|
|
|
|
fetch('/api/files/write', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ path: path, content: '' })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.error) {
|
|
showError('Fehler beim Erstellen: ' + data.error);
|
|
return;
|
|
}
|
|
|
|
showSuccess('Datei erstellt!');
|
|
loadFiles();
|
|
bootstrap.Modal.getInstance(document.getElementById('createFileModal')).hide();
|
|
|
|
// Datei direkt bearbeiten
|
|
setTimeout(() => editFile(path), 500);
|
|
})
|
|
.catch(error => {
|
|
showError('Netzwerkfehler: ' + error.message);
|
|
});
|
|
}
|
|
|
|
function renameFile(path, currentName) {
|
|
document.getElementById('newName').value = currentName;
|
|
document.getElementById('renameModal').dataset.path = path;
|
|
new bootstrap.Modal(document.getElementById('renameModal')).show();
|
|
}
|
|
|
|
function renameFileConfirm() {
|
|
const newName = document.getElementById('newName').value.trim();
|
|
const path = document.getElementById('renameModal').dataset.path;
|
|
|
|
if (!newName || !path) return;
|
|
|
|
fetch('/api/files/rename', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ path: path, new_name: newName })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.error) {
|
|
showError('Fehler beim Umbenennen: ' + data.error);
|
|
return;
|
|
}
|
|
|
|
showSuccess('Element umbenannt!');
|
|
loadFiles();
|
|
bootstrap.Modal.getInstance(document.getElementById('renameModal')).hide();
|
|
})
|
|
.catch(error => {
|
|
showError('Netzwerkfehler: ' + error.message);
|
|
});
|
|
}
|
|
|
|
function uploadFiles() {
|
|
document.getElementById('fileInput').click();
|
|
}
|
|
|
|
function handleFileUpload(files) {
|
|
if (!files.length) return;
|
|
|
|
const formData = new FormData();
|
|
for (let file of files) {
|
|
formData.append('file', file);
|
|
}
|
|
formData.append('path', currentPath);
|
|
|
|
fetch('/api/files/upload', {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.error) {
|
|
showError('Fehler beim Hochladen: ' + data.error);
|
|
return;
|
|
}
|
|
|
|
showSuccess('Datei(en) hochgeladen!');
|
|
loadFiles();
|
|
})
|
|
.catch(error => {
|
|
showError('Netzwerkfehler: ' + error.message);
|
|
});
|
|
}
|
|
|
|
function refreshFiles() {
|
|
loadFiles();
|
|
}
|
|
|
|
function showError(message) {
|
|
console.error(message);
|
|
alert('Fehler: ' + message);
|
|
}
|
|
|
|
function showSuccess(message) {
|
|
console.log(message);
|
|
// Could be replaced with toast notifications
|
|
}
|
|
</script>
|
|
{% endblock %}
|