Files
app-installer/templates/file_manager_new.html
SimolZimol 1eac702d7d modified: app.py
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
2025-07-10 00:00:59 +02:00

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 %}