Files
app-installer/templates/file_manager.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

2146 lines
62 KiB
HTML

{% extends "base.html" %}
{% block title %}File Manager - {{ super() }}{% endblock %}
{% block head %}
<style>
/* File Manager Specific Styles */
.file-manager-container {
background: #fff;
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
overflow: hidden;
margin-top: 20px;
}
.file-manager-header {
background: linear-gradient(45deg, #667eea, #764ba2);
color: white;
padding: 1rem;
border-bottom: 1px solid #e9ecef;
}
.breadcrumb-nav {
background: #f8f9fa;
padding: 0.75rem 1rem;
border-bottom: 1px solid #e9ecef;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.breadcrumb-item {
cursor: pointer;
padding: 0.25rem 0.5rem;
border-radius: 4px;
transition: background-color 0.2s;
}
.breadcrumb-item:hover {
background: #e9ecef;
}
.breadcrumb-separator {
color: #6c757d;
}
.file-manager-toolbar {
background: #f8f9fa;
padding: 0.75rem 1rem;
border-bottom: 1px solid #e9ecef;
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.file-list {
max-height: 60vh;
overflow-y: auto;
}
.file-item {
padding: 0.75rem 1rem;
border-bottom: 1px solid #f8f9fa;
transition: all 0.2s ease;
cursor: pointer;
display: flex;
align-items: center;
gap: 1rem;
}
.file-item:hover {
background: #f8f9fa;
}
.file-item.selected {
background: #e3f2fd;
border-left: 4px solid #2196f3;
}
.file-icon {
width: 24px;
text-align: center;
}
.file-icon.directory {
color: #ffc107;
}
.file-icon.text {
color: #17a2b8;
}
.file-icon.image {
color: #28a745;
}
.file-icon.archive {
color: #6f42c1;
}
.file-icon.executable {
color: #dc3545;
}
.file-details {
flex: 1;
display: flex;
justify-content: space-between;
align-items: center;
}
.file-info {
display: flex;
gap: 1rem;
color: #6c757d;
font-size: 0.875rem;
}
.loading {
text-align: center;
padding: 2rem;
color: #6c757d;
}
.empty-folder {
text-align: center;
padding: 3rem;
color: #6c757d;
}
.editor-container {
display: none;
border-top: 1px solid #e9ecef;
}
.editor-header {
background: #f8f9fa;
padding: 0.75rem 1rem;
border-bottom: 1px solid #e9ecef;
display: flex;
justify-content: space-between;
align-items: center;
}
.editor-content textarea {
width: 100%;
height: 50vh;
border: none;
resize: vertical;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 0.875rem;
padding: 1rem;
}
.context-menu {
position: absolute;
background: white;
border: 1px solid #e9ecef;
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 1000;
display: none;
min-width: 180px;
}
.context-menu-item {
padding: 0.5rem 1rem;
cursor: pointer;
transition: background-color 0.2s;
border-bottom: 1px solid #f8f9fa;
}
.context-menu-item:last-child {
border-bottom: none;
}
.context-menu-item:hover {
background: #f8f9fa;
}
.context-menu-item.danger:hover {
background: #f8d7da;
color: #721c24;
}
.upload-zone {
border: 2px dashed #dee2e6;
border-radius: 8px;
padding: 2rem;
text-align: center;
margin: 1rem;
transition: all 0.3s ease;
}
.upload-zone.dragover {
border-color: #007bff;
background: #f8f9fa;
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="file-manager-container">
<!-- Header -->
<div class="file-manager-header">
<h4 class="mb-0">
<i class="fas fa-folder-open me-2"></i>
File Manager
</h4>
<p class="mb-0 mt-1 opacity-75">Verwalte deine Projektdateien</p>
</div>
<!-- Breadcrumb Navigation -->
<div class="breadcrumb-nav" id="breadcrumbNav">
<i class="fas fa-home"></i>
<span class="breadcrumb-item" data-path="">Root</span>
</div>
<!-- Toolbar -->
<div class="file-manager-toolbar">
<button class="btn btn-sm btn-outline-primary" onclick="createFolder()">
<i class="fas fa-folder-plus me-1"></i> Neuer Ordner
</button>
<button class="btn btn-sm btn-outline-success" onclick="createFile()">
<i class="fas fa-file-plus me-1"></i> Neue Datei
</button>
<button class="btn btn-sm btn-outline-info" onclick="uploadFile()">
<i class="fas fa-upload me-1"></i> Hochladen
</button>
<div class="ms-auto">
<button class="btn btn-sm btn-outline-secondary" onclick="refreshFileList()">
<i class="fas fa-sync-alt me-1"></i> Aktualisieren
</button>
</div>
</div>
<!-- File List -->
<div class="file-list" id="fileList">
<div class="loading">
<i class="fas fa-spinner fa-spin me-2"></i>
Lade Dateien...
</div>
</div>
<!-- File Editor -->
<div class="editor-container" id="editorContainer">
<div class="editor-header">
<span id="editorFileName">Datei.txt</span>
<div>
<button class="btn btn-sm btn-success me-2" onclick="saveFile()">
<i class="fas fa-save me-1"></i> Speichern
</button>
<button class="btn btn-sm btn-secondary" onclick="closeEditor()">
<i class="fas fa-times me-1"></i> Schließen
</button>
</div>
</div>
<div class="editor-content">
<textarea id="fileEditor" placeholder="Dateiinhalt..."></textarea>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Upload Zone (Hidden) -->
<div class="upload-zone" id="uploadZone" style="display: none;">
<i class="fas fa-cloud-upload-alt fa-3x mb-3 text-muted"></i>
<p>Dateien hier ablegen zum Hochladen</p>
<input type="file" id="fileInput" multiple style="display: none;">
</div>
<!-- Context Menu -->
<div class="context-menu" id="contextMenu">
<div class="context-menu-item" onclick="openFile()">
<i class="fas fa-eye me-2"></i> Öffnen
</div>
<div class="context-menu-item" onclick="editFile()">
<i class="fas fa-edit me-2"></i> Bearbeiten
</div>
<div class="context-menu-item" onclick="downloadFile()">
<i class="fas fa-download me-2"></i> Herunterladen
</div>
<div class="context-menu-item" onclick="renameItem()">
<i class="fas fa-i-cursor me-2"></i> Umbenennen
</div>
<div class="context-menu-item" onclick="copyItem()">
<i class="fas fa-copy me-2"></i> Kopieren
</div>
<div class="context-menu-item danger" onclick="deleteItem()">
<i class="fas fa-trash me-2"></i> Löschen
</div>
</div>
<!-- Modals -->
<div class="modal fade" id="createFolderModal" tabindex="-1">
<div class="modal-dialog modal-sm">
<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">
<input type="text" class="form-control" id="folderName" placeholder="Ordnername">
</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>
<div class="modal fade" id="createFileModal" tabindex="-1">
<div class="modal-dialog modal-sm">
<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">
<input type="text" class="form-control" id="fileName" placeholder="Dateiname.txt">
</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>
<div class="modal fade" id="renameModal" tabindex="-1">
<div class="modal-dialog modal-sm">
<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">
<input type="text" class="form-control" id="newName" placeholder="Neuer Name">
</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="renameConfirm()">Umbenennen</button>
</div>
</div>
</div>
</div>
<script>
// Global Variables
let currentPath = '';
let selectedItem = null;
let currentEditFile = null;
// Initialize
document.addEventListener('DOMContentLoaded', function() {
loadFileList();
setupEventListeners();
});
function setupEventListeners() {
// Context Menu
document.addEventListener('click', hideContextMenu);
document.addEventListener('contextmenu', function(e) {
if (!e.target.closest('.file-item')) {
hideContextMenu();
}
});
// File Upload
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', handleFileUpload);
}
// API Functions
function loadFileList() {
showLoading();
fetch(`/api/files/list?path=${encodeURIComponent(currentPath)}`)
.then(response => response.json())
.then(data => {
if (data.error) {
showError('Fehler beim Laden: ' + data.error);
return;
}
renderFileList(data.items);
updateBreadcrumb(data.breadcrumbs);
})
.catch(error => {
showError('Netzwerkfehler: ' + error.message);
});
}
function renderFileList(items) {
const fileList = document.getElementById('fileList');
if (items.length === 0) {
fileList.innerHTML = `
<div class="empty-folder">
<i class="fas fa-folder-open fa-3x mb-3"></i>
<p>Dieser Ordner ist leer</p>
</div>
`;
return;
}
let html = '';
items.forEach(item => {
const icon = getFileIcon(item);
const itemClass = item.type === 'directory' ? 'directory' : item.file_type || 'unknown';
html += `
<div class="file-item"
data-path="${item.path}"
data-type="${item.type}"
data-name="${item.name}"
onclick="selectItem(this)"
ondblclick="openItem('${item.path}', '${item.type}')"
oncontextmenu="showContextMenu(event, this)">
<div class="file-icon ${itemClass}">
${icon}
</div>
<div class="file-details">
<div>
<strong>${item.name}</strong>
${item.is_parent ? '<small class="text-muted">(Zurück)</small>' : ''}
</div>
<div class="file-info">
${item.size ? `<span>${item.size}</span>` : ''}
${item.modified ? `<span>${item.modified}</span>` : ''}
<span class="text-muted">${item.permissions || ''}</span>
</div>
</div>
</div>
`;
});
fileList.innerHTML = html;
}
function getFileIcon(item) {
if (item.is_parent) {
return '<i class="fas fa-arrow-left"></i>';
}
if (item.type === 'directory') {
return '<i class="fas fa-folder"></i>';
}
switch (item.file_type) {
case 'text':
return '<i class="fas fa-file-alt"></i>';
case 'image':
return '<i class="fas fa-file-image"></i>';
case 'archive':
return '<i class="fas fa-file-archive"></i>';
case 'executable':
return '<i class="fas fa-cog"></i>';
default:
return '<i class="fas fa-file"></i>';
}
}
function updateBreadcrumb(breadcrumbs) {
const nav = document.getElementById('breadcrumbNav');
let html = '<i class="fas fa-home"></i>';
breadcrumbs.forEach((crumb, index) => {
if (index > 0) {
html += '<span class="breadcrumb-separator">/</span>';
}
html += `<span class="breadcrumb-item" data-path="${crumb.path}" onclick="navigateTo('${crumb.path}')">${crumb.name}</span>`;
});
nav.innerHTML = html;
}
// Navigation Functions
function navigateTo(path) {
currentPath = path;
loadFileList();
}
function openItem(path, type) {
if (type === 'directory') {
navigateTo(path);
} else {
editFile(path);
}
}
function selectItem(element) {
// Remove previous selection
document.querySelectorAll('.file-item.selected').forEach(item => {
item.classList.remove('selected');
});
// Select current item
element.classList.add('selected');
selectedItem = {
path: element.dataset.path,
type: element.dataset.type,
name: element.dataset.name
};
}
// Context Menu Functions
function showContextMenu(event, element) {
event.preventDefault();
selectItem(element);
const contextMenu = document.getElementById('contextMenu');
contextMenu.style.display = 'block';
contextMenu.style.left = event.pageX + 'px';
contextMenu.style.top = event.pageY + 'px';
}
function hideContextMenu() {
document.getElementById('contextMenu').style.display = 'none';
}
// File Operations
function openFile() {
if (!selectedItem) return;
if (selectedItem.type === 'directory') {
navigateTo(selectedItem.path);
} else {
window.open(`/api/files/download?path=${encodeURIComponent(selectedItem.path)}`, '_blank');
}
hideContextMenu();
}
function editFile(path = null) {
const filePath = path || (selectedItem ? selectedItem.path : null);
if (!filePath || (selectedItem && selectedItem.type === 'directory')) {
return;
}
fetch(`/api/files/read?path=${encodeURIComponent(filePath)}`)
.then(response => response.json())
.then(data => {
if (data.error) {
showError('Fehler beim Laden der Datei: ' + data.error);
return;
}
if (data.is_binary) {
showError('Binäre Dateien können nicht bearbeitet werden.');
return;
}
document.getElementById('editorFileName').textContent = filePath.split('/').pop();
document.getElementById('fileEditor').value = data.content;
document.getElementById('editorContainer').style.display = 'block';
currentEditFile = filePath;
})
.catch(error => {
showError('Netzwerkfehler: ' + error.message);
});
hideContextMenu();
}
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!');
})
.catch(error => {
showError('Netzwerkfehler: ' + error.message);
});
}
function closeEditor() {
document.getElementById('editorContainer').style.display = 'none';
currentEditFile = null;
}
function downloadFile() {
if (!selectedItem || selectedItem.type === 'directory') return;
window.open(`/api/files/download?path=${encodeURIComponent(selectedItem.path)}`, '_blank');
hideContextMenu();
}
function deleteItem() {
if (!selectedItem) return;
if (confirm(`Möchten Sie "${selectedItem.name}" wirklich löschen?`)) {
fetch('/api/files/delete', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
path: selectedItem.path
})
})
.then(response => response.json())
.then(data => {
if (data.error) {
showError('Fehler beim Löschen: ' + data.error);
return;
}
showSuccess('Element gelöscht!');
loadFileList();
})
.catch(error => {
showError('Netzwerkfehler: ' + error.message);
});
}
hideContextMenu();
}
// Create Operations
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!');
loadFileList();
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!');
loadFileList();
bootstrap.Modal.getInstance(document.getElementById('createFileModal')).hide();
// Öffne die neue Datei zum Bearbeiten
setTimeout(() => editFile(path), 500);
})
.catch(error => {
showError('Netzwerkfehler: ' + error.message);
});
}
// Rename Operation
function renameItem() {
if (!selectedItem) return;
document.getElementById('newName').value = selectedItem.name;
new bootstrap.Modal(document.getElementById('renameModal')).show();
hideContextMenu();
}
function renameConfirm() {
const newName = document.getElementById('newName').value.trim();
if (!newName || !selectedItem) return;
fetch('/api/files/rename', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
path: selectedItem.path,
new_name: newName
})
})
.then(response => response.json())
.then(data => {
if (data.error) {
showError('Fehler beim Umbenennen: ' + data.error);
return;
}
showSuccess('Element umbenannt!');
loadFileList();
bootstrap.Modal.getInstance(document.getElementById('renameModal')).hide();
})
.catch(error => {
showError('Netzwerkfehler: ' + error.message);
});
}
// Upload Operations
function uploadFile() {
document.getElementById('fileInput').click();
}
function handleFileUpload(event) {
const files = event.target.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!');
loadFileList();
})
.catch(error => {
showError('Netzwerkfehler: ' + error.message);
});
}
// Utility Functions
function refreshFileList() {
loadFileList();
}
function showLoading() {
document.getElementById('fileList').innerHTML = `
<div class="loading">
<i class="fas fa-spinner fa-spin me-2"></i>
Lade Dateien...
</div>
`;
}
function showError(message) {
// Use existing flash message system
console.error(message);
alert('Fehler: ' + message);
}
function showSuccess(message) {
// Use existing flash message system
console.log(message);
// You could also show a toast notification here
}
function copyItem() {
// Placeholder for copy functionality
showError('Kopieren ist noch nicht implementiert.');
hideContextMenu();
}
</script>
{% endblock %}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="file-manager-container">
<!-- Header -->
<div class="file-manager-header">
<h4 class="mb-0">
<i class="fas fa-folder-open me-2"></i>
File Manager
</h4>
<p class="mb-0 mt-1 opacity-75">Verwalte deine Projektdateien</p>
</div>
<!-- Breadcrumb Navigation -->
<div class="breadcrumb-nav" id="breadcrumbNav">
<i class="fas fa-home"></i>
<span class="breadcrumb-item" data-path="">Root</span>
</div>
<!-- Toolbar -->
<div class="file-manager-toolbar">
<button class="btn btn-sm btn-outline-primary" onclick="createFolder()">
<i class="fas fa-folder-plus me-1"></i> Neuer Ordner
</button>
<button class="btn btn-sm btn-outline-success" onclick="createFile()">
<i class="fas fa-file-plus me-1"></i> Neue Datei
</button>
<button class="btn btn-sm btn-outline-info" onclick="uploadFile()">
<i class="fas fa-upload me-1"></i> Hochladen
</button>
<div class="ms-auto">
<button class="btn btn-sm btn-outline-secondary" onclick="refreshFileList()">
<i class="fas fa-sync-alt me-1"></i> Aktualisieren
</button>
</div>
</div>
<!-- File List -->
<div class="file-list" id="fileList">
<div class="loading">
<i class="fas fa-spinner fa-spin me-2"></i>
Lade Dateien...
</div>
</div>
<!-- File Editor -->
<div class="editor-container" id="editorContainer">
<div class="editor-header">
<span id="editorFileName">Datei.txt</span>
<div>
<button class="btn btn-sm btn-success me-2" onclick="saveFile()">
<i class="fas fa-save me-1"></i> Speichern
</button>
<button class="btn btn-sm btn-secondary" onclick="closeEditor()">
<i class="fas fa-times me-1"></i> Schließen
</button>
</div>
</div>
<div class="editor-content">
<textarea id="fileEditor" placeholder="Dateiinhalt..."></textarea>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Upload Zone (Hidden) -->
<div class="upload-zone" id="uploadZone" style="display: none;">
<i class="fas fa-cloud-upload-alt fa-3x mb-3 text-muted"></i>
<p>Dateien hier ablegen zum Hochladen</p>
<input type="file" id="fileInput" multiple style="display: none;">
</div>
<!-- Context Menu -->
<div class="context-menu" id="contextMenu">
<div class="context-menu-item" onclick="openFile()">
<i class="fas fa-eye me-2"></i> Öffnen
</div>
<div class="context-menu-item" onclick="editFile()">
<i class="fas fa-edit me-2"></i> Bearbeiten
</div>
<div class="context-menu-item" onclick="downloadFile()">
<i class="fas fa-download me-2"></i> Herunterladen
</div>
<div class="context-menu-item" onclick="renameItem()">
<i class="fas fa-i-cursor me-2"></i> Umbenennen
</div>
<div class="context-menu-item" onclick="copyItem()">
<i class="fas fa-copy me-2"></i> Kopieren
</div>
<div class="context-menu-item danger" onclick="deleteItem()">
<i class="fas fa-trash me-2"></i> Löschen
</div>
</div>
<!-- Modals -->
<div class="modal fade" id="createFolderModal" tabindex="-1">
<div class="modal-dialog modal-sm">
<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">
<input type="text" class="form-control" id="folderName" placeholder="Ordnername">
</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>
<div class="modal fade" id="createFileModal" tabindex="-1">
<div class="modal-dialog modal-sm">
<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">
<input type="text" class="form-control" id="fileName" placeholder="Dateiname.txt">
</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>
<div class="modal fade" id="renameModal" tabindex="-1">
<div class="modal-dialog modal-sm">
<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">
<input type="text" class="form-control" id="newName" placeholder="Neuer Name">
</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="renameConfirm()">Umbenennen</button>
</div>
</div>
</div>
</div>
<script>
// Global Variables
let currentPath = '';
let selectedItem = null;
let currentEditFile = null;
// Initialize
document.addEventListener('DOMContentLoaded', function() {
loadFileList();
setupEventListeners();
});
function setupEventListeners() {
// Context Menu
document.addEventListener('click', hideContextMenu);
document.addEventListener('contextmenu', function(e) {
if (!e.target.closest('.file-item')) {
hideContextMenu();
}
});
// File Upload
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', handleFileUpload);
}
// API Functions
function loadFileList() {
showLoading();
fetch(`/api/files/list?path=${encodeURIComponent(currentPath)}`)
.then(response => response.json())
.then(data => {
if (data.error) {
showError('Fehler beim Laden: ' + data.error);
return;
}
renderFileList(data.items);
updateBreadcrumb(data.breadcrumbs);
})
.catch(error => {
showError('Netzwerkfehler: ' + error.message);
});
}
function renderFileList(items) {
const fileList = document.getElementById('fileList');
if (items.length === 0) {
fileList.innerHTML = `
<div class="empty-folder">
<i class="fas fa-folder-open fa-3x mb-3"></i>
<p>Dieser Ordner ist leer</p>
</div>
`;
return;
}
let html = '';
items.forEach(item => {
const icon = getFileIcon(item);
const itemClass = item.type === 'directory' ? 'directory' : item.file_type || 'unknown';
html += `
<div class="file-item"
data-path="${item.path}"
data-type="${item.type}"
data-name="${item.name}"
onclick="selectItem(this)"
ondblclick="openItem('${item.path}', '${item.type}')"
oncontextmenu="showContextMenu(event, this)">
<div class="file-icon ${itemClass}">
${icon}
</div>
<div class="file-details">
<div>
<strong>${item.name}</strong>
${item.is_parent ? '<small class="text-muted">(Zurück)</small>' : ''}
</div>
<div class="file-info">
${item.size ? `<span>${item.size}</span>` : ''}
${item.modified ? `<span>${item.modified}</span>` : ''}
<span class="text-muted">${item.permissions || ''}</span>
</div>
</div>
</div>
`;
});
fileList.innerHTML = html;
}
function getFileIcon(item) {
if (item.is_parent) {
return '<i class="fas fa-arrow-left"></i>';
}
if (item.type === 'directory') {
return '<i class="fas fa-folder"></i>';
}
switch (item.file_type) {
case 'text':
return '<i class="fas fa-file-alt"></i>';
case 'image':
return '<i class="fas fa-file-image"></i>';
case 'archive':
return '<i class="fas fa-file-archive"></i>';
case 'executable':
return '<i class="fas fa-cog"></i>';
default:
return '<i class="fas fa-file"></i>';
}
}
function updateBreadcrumb(breadcrumbs) {
const nav = document.getElementById('breadcrumbNav');
let html = '<i class="fas fa-home"></i>';
breadcrumbs.forEach((crumb, index) => {
if (index > 0) {
html += '<span class="breadcrumb-separator">/</span>';
}
html += `<span class="breadcrumb-item" data-path="${crumb.path}" onclick="navigateTo('${crumb.path}')">${crumb.name}</span>`;
});
nav.innerHTML = html;
}
// Navigation Functions
function navigateTo(path) {
currentPath = path;
loadFileList();
}
function openItem(path, type) {
if (type === 'directory') {
navigateTo(path);
} else {
editFile(path);
}
}
function selectItem(element) {
// Remove previous selection
document.querySelectorAll('.file-item.selected').forEach(item => {
item.classList.remove('selected');
});
// Select current item
element.classList.add('selected');
selectedItem = {
path: element.dataset.path,
type: element.dataset.type,
name: element.dataset.name
};
}
// Context Menu Functions
function showContextMenu(event, element) {
event.preventDefault();
selectItem(element);
const contextMenu = document.getElementById('contextMenu');
contextMenu.style.display = 'block';
contextMenu.style.left = event.pageX + 'px';
contextMenu.style.top = event.pageY + 'px';
}
function hideContextMenu() {
document.getElementById('contextMenu').style.display = 'none';
}
// File Operations
function openFile() {
if (!selectedItem) return;
if (selectedItem.type === 'directory') {
navigateTo(selectedItem.path);
} else {
window.open(`/api/files/download?path=${encodeURIComponent(selectedItem.path)}`, '_blank');
}
hideContextMenu();
}
function editFile(path = null) {
const filePath = path || (selectedItem ? selectedItem.path : null);
if (!filePath || (selectedItem && selectedItem.type === 'directory')) {
return;
}
fetch(`/api/files/read?path=${encodeURIComponent(filePath)}`)
.then(response => response.json())
.then(data => {
if (data.error) {
showError('Fehler beim Laden der Datei: ' + data.error);
return;
}
if (data.is_binary) {
showError('Binäre Dateien können nicht bearbeitet werden.');
return;
}
document.getElementById('editorFileName').textContent = filePath.split('/').pop();
document.getElementById('fileEditor').value = data.content;
document.getElementById('editorContainer').style.display = 'block';
currentEditFile = filePath;
})
.catch(error => {
showError('Netzwerkfehler: ' + error.message);
});
hideContextMenu();
}
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!');
})
.catch(error => {
showError('Netzwerkfehler: ' + error.message);
});
}
function closeEditor() {
document.getElementById('editorContainer').style.display = 'none';
currentEditFile = null;
}
function downloadFile() {
if (!selectedItem || selectedItem.type === 'directory') return;
window.open(`/api/files/download?path=${encodeURIComponent(selectedItem.path)}`, '_blank');
hideContextMenu();
}
function deleteItem() {
if (!selectedItem) return;
if (confirm(`Möchten Sie "${selectedItem.name}" wirklich löschen?`)) {
fetch('/api/files/delete', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
path: selectedItem.path
})
})
.then(response => response.json())
.then(data => {
if (data.error) {
showError('Fehler beim Löschen: ' + data.error);
return;
}
showSuccess('Element gelöscht!');
loadFileList();
})
.catch(error => {
showError('Netzwerkfehler: ' + error.message);
});
}
hideContextMenu();
}
// Create Operations
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!');
loadFileList();
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!');
loadFileList();
bootstrap.Modal.getInstance(document.getElementById('createFileModal')).hide();
// Öffne die neue Datei zum Bearbeiten
setTimeout(() => editFile(path), 500);
})
.catch(error => {
showError('Netzwerkfehler: ' + error.message);
});
}
// Rename Operation
function renameItem() {
if (!selectedItem) return;
document.getElementById('newName').value = selectedItem.name;
new bootstrap.Modal(document.getElementById('renameModal')).show();
hideContextMenu();
}
function renameConfirm() {
const newName = document.getElementById('newName').value.trim();
if (!newName || !selectedItem) return;
fetch('/api/files/rename', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
path: selectedItem.path,
new_name: newName
})
})
.then(response => response.json())
.then(data => {
if (data.error) {
showError('Fehler beim Umbenennen: ' + data.error);
return;
}
showSuccess('Element umbenannt!');
loadFileList();
bootstrap.Modal.getInstance(document.getElementById('renameModal')).hide();
})
.catch(error => {
showError('Netzwerkfehler: ' + error.message);
});
}
// Upload Operations
function uploadFile() {
document.getElementById('fileInput').click();
}
function handleFileUpload(event) {
const files = event.target.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!');
loadFileList();
})
.catch(error => {
showError('Netzwerkfehler: ' + error.message);
});
}
// Utility Functions
function refreshFileList() {
loadFileList();
}
function showLoading() {
document.getElementById('fileList').innerHTML = `
<div class="loading">
<i class="fas fa-spinner fa-spin me-2"></i>
Lade Dateien...
</div>
`;
}
function showError(message) {
// Use existing flash message system
console.error(message);
alert('Fehler: ' + message);
}
function showSuccess(message) {
// Use existing flash message system
console.log(message);
// You could also show a toast notification here
}
function copyItem() {
// Placeholder for copy functionality
showError('Kopieren ist noch nicht implementiert.');
hideContextMenu();
}
</script>
{% endblock %}
.file-icon {
width: 20px;
height: 20px;
margin-right: 10px;
display: inline-flex;
align-items: center;
justify-content: center;
}
.file-icon.folder { color: #ffc107; }
.file-icon.code { color: #28a745; }
.file-icon.text { color: #6c757d; }
.file-icon.image { color: #17a2b8; }
.file-icon.archive { color: #fd7e14; }
.file-icon.config { color: #dc3545; }
.file-details {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.file-name {
font-weight: 500;
flex-grow: 1;
}
.file-meta {
font-size: 0.8rem;
color: #6c757d;
text-align: right;
}
.action-toolbar {
background: #f8f9fa;
padding: 0.75rem 1rem;
border-top: 1px solid #e9ecef;
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.editor-container {
display: none;
border-top: 1px solid #e9ecef;
}
.code-editor {
width: 100%;
height: 500px;
border: none;
padding: 1rem;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 14px;
line-height: 1.5;
background: #2d3748;
color: #e2e8f0;
resize: vertical;
}
.upload-zone {
border: 2px dashed #ced4da;
border-radius: 8px;
padding: 2rem;
text-align: center;
margin: 1rem;
transition: all 0.3s ease;
}
.upload-zone:hover, .upload-zone.dragover {
border-color: #007bff;
background: #f8f9ff;
}
.permission-badge {
font-size: 0.7rem;
padding: 0.2rem 0.4rem;
border-radius: 3px;
margin-left: 0.5rem;
}
.permission-r { background: #d4edda; color: #155724; }
.permission-w { background: #fff3cd; color: #856404; }
.permission-x { background: #f8d7da; color: #721c24; }
/* Context Menu */
.context-menu {
position: absolute;
background: white;
border: 1px solid #ced4da;
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 1000;
min-width: 180px;
display: none;
}
.context-menu-item {
padding: 0.5rem 1rem;
cursor: pointer;
border-bottom: 1px solid #f8f9fa;
transition: background 0.2s ease;
}
.context-menu-item:hover {
background: #f8f9fa;
}
.context-menu-item:last-child {
border-bottom: none;
}
.context-menu-item.disabled {
color: #6c757d;
cursor: not-allowed;
}
.context-menu-item.disabled:hover {
background: transparent;
}
/* Progress Bar für Upload */
.upload-progress {
display: none;
margin: 1rem;
}
/* Responsive */
@media (max-width: 768px) {
.file-details {
flex-direction: column;
align-items: flex-start;
}
.file-meta {
text-align: left;
margin-top: 0.25rem;
}
.action-toolbar {
flex-direction: column;
}
.action-toolbar .btn {
margin-bottom: 0.25rem;
}
}
</style>
{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2>
<i class="fas fa-folder-open me-2 text-primary"></i>
File Manager - {{ project_name }}
</h2>
<p class="text-muted mb-0">Verwalten Sie Projektdateien direkt im Browser</p>
</div>
<a href="{{ url_for('project_details', project_name=project_name) }}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left"></i> Zurück zum Projekt
</a>
</div>
<div class="file-manager-container">
<!-- Header mit Pfad-Navigation -->
<div class="file-manager-header">
<div class="d-flex justify-content-between align-items-center">
<div>
<h5 class="mb-1">
<i class="fas fa-server me-2"></i>{{ project_name }}
</h5>
<small class="opacity-75">Aktueller Pfad: {{ current_path or '/' }}</small>
</div>
<div>
<button class="btn btn-light btn-sm" onclick="refreshFileList()">
<i class="fas fa-sync-alt"></i> Aktualisieren
</button>
</div>
</div>
</div>
<!-- Breadcrumb Navigation -->
<div class="breadcrumb-nav">
<nav aria-label="breadcrumb">
<ol class="breadcrumb mb-0">
<li class="breadcrumb-item">
<a href="#" onclick="navigateToPath('/')" class="text-decoration-none">
<i class="fas fa-home"></i> Root
</a>
</li>
{% if current_path and current_path != '/' %}
{% set path_parts = current_path.strip('/').split('/') %}
{% for part in path_parts %}
{% if not loop.last %}
<li class="breadcrumb-item">
<a href="#" onclick="navigateToPath('/{{ path_parts[:loop.index]|join('/') }}')" class="text-decoration-none">
{{ part }}
</a>
</li>
{% else %}
<li class="breadcrumb-item active" aria-current="page">{{ part }}</li>
{% endif %}
{% endfor %}
{% endif %}
</ol>
</nav>
</div>
<!-- File List -->
<div class="file-list" id="fileList">
<!-- Wird dynamisch geladen -->
<div class="text-center p-4">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Lade Dateien...</span>
</div>
<p class="mt-2 text-muted">Lade Dateiliste...</p>
</div>
</div>
<!-- Action Toolbar -->
<div class="action-toolbar">
<button class="btn btn-primary btn-sm" onclick="showUploadModal()">
<i class="fas fa-upload"></i> Datei hochladen
</button>
<button class="btn btn-success btn-sm" onclick="createNewFile()">
<i class="fas fa-file-plus"></i> Neue Datei
</button>
<button class="btn btn-info btn-sm" onclick="createNewFolder()">
<i class="fas fa-folder-plus"></i> Neuer Ordner
</button>
<button class="btn btn-warning btn-sm" onclick="downloadSelected()" id="btnDownload" disabled>
<i class="fas fa-download"></i> Download
</button>
<button class="btn btn-outline-secondary btn-sm" onclick="editSelected()" id="btnEdit" disabled>
<i class="fas fa-edit"></i> Bearbeiten
</button>
<button class="btn btn-danger btn-sm" onclick="deleteSelected()" id="btnDelete" disabled>
<i class="fas fa-trash"></i> Löschen
</button>
</div>
<!-- Inline Editor -->
<div class="editor-container" id="editorContainer">
<div class="p-3 bg-light border-bottom">
<div class="d-flex justify-content-between align-items-center">
<h6 class="mb-0">
<i class="fas fa-edit me-2"></i>
Editor: <span id="editingFileName"></span>
</h6>
<div>
<button class="btn btn-success btn-sm" onclick="saveFile()">
<i class="fas fa-save"></i> Speichern
</button>
<button class="btn btn-secondary btn-sm" onclick="closeEditor()">
<i class="fas fa-times"></i> Schließen
</button>
</div>
</div>
</div>
<textarea class="code-editor" id="codeEditor" placeholder="Dateiinhalt wird geladen..."></textarea>
</div>
</div>
<!-- Upload Modal -->
<div class="modal fade" id="uploadModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-upload me-2"></i>Dateien hochladen
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="upload-zone" id="uploadZone">
<i class="fas fa-cloud-upload-alt fa-3x text-muted mb-3"></i>
<h5>Dateien hier ablegen oder klicken zum Auswählen</h5>
<p class="text-muted">Unterstützte Formate: Alle Dateitypen (Max. 50MB pro Datei)</p>
<input type="file" class="d-none" id="fileInput" multiple>
</div>
<div class="upload-progress">
<div class="progress">
<div class="progress-bar" role="progressbar" style="width: 0%"></div>
</div>
<small class="text-muted mt-1">Upload läuft...</small>
</div>
</div>
</div>
</div>
</div>
<!-- Context Menu -->
<div class="context-menu" id="contextMenu">
<div class="context-menu-item" onclick="openFile()">
<i class="fas fa-eye me-2"></i>Öffnen
</div>
<div class="context-menu-item" onclick="editFile()">
<i class="fas fa-edit me-2"></i>Bearbeiten
</div>
<div class="context-menu-item" onclick="downloadFile()">
<i class="fas fa-download me-2"></i>Download
</div>
<hr class="my-1">
<div class="context-menu-item" onclick="renameFile()">
<i class="fas fa-i-cursor me-2"></i>Umbenennen
</div>
<div class="context-menu-item" onclick="copyFile()">
<i class="fas fa-copy me-2"></i>Kopieren
</div>
<div class="context-menu-item" onclick="moveFile()">
<i class="fas fa-cut me-2"></i>Verschieben
</div>
<hr class="my-1">
<div class="context-menu-item text-danger" onclick="deleteFile()">
<i class="fas fa-trash me-2"></i>Löschen
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
let currentPath = '{{ current_path or "/" }}';
let selectedFiles = [];
let contextMenuTarget = null;
// Globale Variablen
const PROJECT_NAME = '{{ project_name }}';
// Page Load
document.addEventListener('DOMContentLoaded', function() {
loadFileList();
initializeEventListeners();
});
// Event Listeners initialisieren
function initializeEventListeners() {
// Upload Zone Events
const uploadZone = document.getElementById('uploadZone');
const fileInput = document.getElementById('fileInput');
uploadZone.addEventListener('click', () => fileInput.click());
uploadZone.addEventListener('dragover', handleDragOver);
uploadZone.addEventListener('drop', handleDrop);
uploadZone.addEventListener('dragleave', handleDragLeave);
fileInput.addEventListener('change', handleFileSelect);
// Context Menu Events
document.addEventListener('click', hideContextMenu);
document.addEventListener('contextmenu', function(e) {
if (!e.target.closest('.file-item')) {
hideContextMenu();
}
});
}
// Dateiliste laden
function loadFileList() {
fetch(`/api/file_manager/${PROJECT_NAME}/list?path=${encodeURIComponent(currentPath)}`)
.then(response => response.json())
.then(data => {
if (data.success) {
renderFileList(data.files);
updateBreadcrumb(data.current_path);
} else {
showError('Fehler beim Laden der Dateiliste: ' + data.error);
}
})
.catch(error => {
showError('Netzwerkfehler: ' + error.message);
});
}
// Dateiliste rendern
function renderFileList(files) {
const fileList = document.getElementById('fileList');
if (files.length === 0) {
fileList.innerHTML = `
<div class="text-center p-4 text-muted">
<i class="fas fa-folder-open fa-3x mb-3"></i>
<p>Dieser Ordner ist leer</p>
</div>
`;
return;
}
let html = '';
// Zurück-Button wenn nicht im Root
if (currentPath !== '/') {
html += `
<div class="file-item" onclick="navigateUp()">
<div class="file-details">
<div class="d-flex align-items-center">
<div class="file-icon">
<i class="fas fa-arrow-left text-secondary"></i>
</div>
<div class="file-name text-secondary">.. (Zurück)</div>
</div>
</div>
</div>
`;
}
// Ordner zuerst, dann Dateien
const folders = files.filter(f => f.type === 'directory');
const regularFiles = files.filter(f => f.type === 'file');
[...folders, ...regularFiles].forEach(file => {
const icon = getFileIcon(file);
const size = file.type === 'directory' ? '-' : formatFileSize(file.size);
const permissions = getPermissionBadges(file.permissions);
html += `
<div class="file-item"
data-path="${file.path}"
data-type="${file.type}"
onclick="selectFile(this, '${file.path}', '${file.type}')"
ondblclick="handleDoubleClick('${file.path}', '${file.type}')"
oncontextmenu="showContextMenu(event, this, '${file.path}', '${file.type}')">
<div class="file-details">
<div class="d-flex align-items-center">
<div class="file-icon ${icon.class}">
<i class="${icon.icon}"></i>
</div>
<div class="file-name">${file.name}</div>
${permissions}
</div>
<div class="file-meta">
<div>${size}</div>
<div class="text-muted small">${formatDate(file.modified)}</div>
</div>
</div>
</div>
`;
});
fileList.innerHTML = html;
}
// Datei-Icon bestimmen
function getFileIcon(file) {
if (file.type === 'directory') {
return { icon: 'fas fa-folder', class: 'folder' };
}
const ext = file.name.split('.').pop().toLowerCase();
// Code-Dateien
if (['js', 'ts', 'py', 'php', 'java', 'cpp', 'c', 'h', 'css', 'html', 'xml'].includes(ext)) {
return { icon: 'fas fa-file-code', class: 'code' };
}
// Config-Dateien
if (['json', 'yml', 'yaml', 'conf', 'cfg', 'ini', 'env', 'dockerfile'].includes(ext)) {
return { icon: 'fas fa-file-alt', class: 'config' };
}
// Bilder
if (['png', 'jpg', 'jpeg', 'gif', 'svg', 'ico', 'webp'].includes(ext)) {
return { icon: 'fas fa-file-image', class: 'image' };
}
// Archive
if (['zip', 'tar', 'gz', 'rar', '7z'].includes(ext)) {
return { icon: 'fas fa-file-archive', class: 'archive' };
}
// Text-Dateien
if (['txt', 'md', 'log'].includes(ext)) {
return { icon: 'fas fa-file-alt', class: 'text' };
}
return { icon: 'fas fa-file', class: 'text' };
}
// Dateigröße formatieren
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(1)) + ' ' + sizes[i];
}
// Datum formatieren
function formatDate(dateString) {
return new Date(dateString).toLocaleString('de-DE');
}
// Berechtigungen als Badges
function getPermissionBadges(permissions) {
if (!permissions) return '';
let badges = '';
if (permissions.includes('r')) badges += '<span class="permission-badge permission-r">R</span>';
if (permissions.includes('w')) badges += '<span class="permission-badge permission-w">W</span>';
if (permissions.includes('x')) badges += '<span class="permission-badge permission-x">X</span>';
return badges;
}
// Navigation
function navigateToPath(path) {
currentPath = path;
selectedFiles = [];
updateActionButtons();
loadFileList();
}
function navigateUp() {
const pathParts = currentPath.split('/').filter(p => p);
pathParts.pop();
navigateToPath('/' + pathParts.join('/'));
}
// Datei/Ordner-Auswahl
function selectFile(element, path, type) {
// Clear andere Auswahl
document.querySelectorAll('.file-item.selected').forEach(item => {
item.classList.remove('selected');
});
element.classList.add('selected');
selectedFiles = [{ path, type }];
updateActionButtons();
}
function handleDoubleClick(path, type) {
if (type === 'directory') {
navigateToPath(path);
} else {
openFile(path);
}
}
// Action Buttons Update
function updateActionButtons() {
const hasSelection = selectedFiles.length > 0;
const isFile = hasSelection && selectedFiles[0].type === 'file';
document.getElementById('btnDownload').disabled = !hasSelection;
document.getElementById('btnEdit').disabled = !isFile;
document.getElementById('btnDelete').disabled = !hasSelection;
}
// File Operations
function openFile(path = null) {
const filePath = path || selectedFiles[0]?.path;
if (!filePath) return;
// Check if file is editable
const ext = filePath.split('.').pop().toLowerCase();
const editableExtensions = ['txt', 'md', 'json', 'yml', 'yaml', 'js', 'py', 'php', 'html', 'css', 'env', 'conf', 'cfg', 'ini'];
if (editableExtensions.includes(ext)) {
editFile(filePath);
} else {
downloadFile(filePath);
}
}
// Upload Functions
function showUploadModal() {
new bootstrap.Modal(document.getElementById('uploadModal')).show();
}
function handleDragOver(e) {
e.preventDefault();
e.currentTarget.classList.add('dragover');
}
function handleDrop(e) {
e.preventDefault();
e.currentTarget.classList.remove('dragover');
const files = e.dataTransfer.files;
uploadFiles(files);
}
function handleDragLeave(e) {
e.currentTarget.classList.remove('dragover');
}
function handleFileSelect(e) {
uploadFiles(e.target.files);
}
// Context Menu
function showContextMenu(e, element, path, type) {
e.preventDefault();
contextMenuTarget = { element, path, type };
const contextMenu = document.getElementById('contextMenu');
contextMenu.style.display = 'block';
contextMenu.style.left = e.pageX + 'px';
contextMenu.style.top = e.pageY + 'px';
// Select the item
selectFile(element, path, type);
}
function hideContextMenu() {
document.getElementById('contextMenu').style.display = 'none';
contextMenuTarget = null;
}
// Utility Functions
function refreshFileList() {
loadFileList();
}
function updateBreadcrumb(path) {
currentPath = path;
// Breadcrumb wird vom Server-Side-Template gehandhabt
}
function showError(message) {
alert('Fehler: ' + message);
}
function showSuccess(message) {
// Implementierung für Success-Nachrichten
console.log('Success: ' + message);
}
// Placeholder Functions (werden in der nächsten Phase implementiert)
function editFile(path = null) {
alert('Editor wird in der nächsten Phase implementiert');
}
function downloadFile(path = null) {
alert('Download wird in der nächsten Phase implementiert');
}
function uploadFiles(files) {
alert('Upload wird in der nächsten Phase implementiert');
}
function createNewFile() {
alert('Neue Datei wird in der nächsten Phase implementiert');
}
function createNewFolder() {
alert('Neuer Ordner wird in der nächsten Phase implementiert');
}
function deleteSelected() {
alert('Löschen wird in der nächsten Phase implementiert');
}
function editSelected() {
editFile();
}
function downloadSelected() {
downloadFile();
}
function saveFile() {
alert('Speichern wird in der nächsten Phase implementiert');
}
function closeEditor() {
document.getElementById('editorContainer').style.display = 'none';
}
function renameFile() {
alert('Umbenennen wird in der nächsten Phase implementiert');
}
function copyFile() {
alert('Kopieren wird in der nächsten Phase implementiert');
}
function moveFile() {
alert('Verschieben wird in der nächsten Phase implementiert');
}
</script>
{% endblock %}