feat: Ordner erstellen/löschen im File Explorer
Backend (WorkspaceEndpoint.cs): - POST /api/workspace/mkdir – Ordner im Session-Workspace erstellen - DELETE /api/workspace/dir – Leeren Ordner im Session-Workspace löschen - POST /api/workspace/agent/mkdir – Ordner im Agent-Memory erstellen - DELETE /api/workspace/agent/dir – Leeren Ordner im Agent-Memory löschen - POST /api/workspace/browse/mkdir – Ordner im Folder-Browser erstellen - Sicherheit: Path-Traversal-Schutz, Root nicht löschbar, nicht-leer → 409 Frontend (file-explorer.js): - 📁+ Button im Header für neuen Ordner (File Explorer + Folder Browser) - 🗑️ Button bei jedem Ordner (erscheint on hover, nur File Explorer) - Validierung: ungültige Zeichen werden abgelehnt CSS: - base.css: Icon-Button mit zwei Icons (folder + plus) - file-explorer.css: Ordner-Zeile als Flex (Label + Delete-Button)
This commit is contained in:
1 parent
d1355dcb0d
commit
8e7bda50f9
4 files changed
+335
-2
No files matched your search
@@ -41,10 +41,15 @@ public static class WorkspaceEndpoint
|
|||||||
app.MapGet("/api/workspace/file", HandleFile);
|
app.MapGet("/api/workspace/file", HandleFile);
|
||||||
app.MapPut("/api/workspace/file", HandleFileSave);
|
app.MapPut("/api/workspace/file", HandleFileSave);
|
||||||
app.MapGet("/api/workspace/browse", HandleBrowse);
|
app.MapGet("/api/workspace/browse", HandleBrowse);
|
||||||
|
app.MapPost("/api/workspace/browse/mkdir", HandleBrowseMkdir);
|
||||||
app.MapGet("/api/workspace/agent/tree", HandleAgentTree);
|
app.MapGet("/api/workspace/agent/tree", HandleAgentTree);
|
||||||
app.MapGet("/api/workspace/agent/file", HandleAgentFile);
|
app.MapGet("/api/workspace/agent/file", HandleAgentFile);
|
||||||
app.MapPut("/api/workspace/agent/file", HandleAgentFileSave);
|
app.MapPut("/api/workspace/agent/file", HandleAgentFileSave);
|
||||||
app.MapGet("/api/workspace/raw", HandleRawFile);
|
app.MapGet("/api/workspace/raw", HandleRawFile);
|
||||||
|
app.MapPost("/api/workspace/mkdir", HandleMkdir);
|
||||||
|
app.MapDelete("/api/workspace/dir", HandleRmdir);
|
||||||
|
app.MapPost("/api/workspace/agent/mkdir", HandleAgentMkdir);
|
||||||
|
app.MapDelete("/api/workspace/agent/dir", HandleAgentRmdir);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task HandleTree(HttpContext ctx)
|
private static async Task HandleTree(HttpContext ctx)
|
||||||
@@ -251,6 +256,38 @@ public static class WorkspaceEndpoint
|
|||||||
await ctx.Response.WriteAsJsonAsync(new { current = targetPath.Replace('\\', '/'), entries });
|
await ctx.Response.WriteAsJsonAsync(new { current = targetPath.Replace('\\', '/'), entries });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task HandleBrowseMkdir(HttpContext ctx)
|
||||||
|
{
|
||||||
|
var path = ctx.Request.Query["path"].FirstOrDefault();
|
||||||
|
if (string.IsNullOrEmpty(path))
|
||||||
|
{
|
||||||
|
ctx.Response.StatusCode = 400;
|
||||||
|
await ctx.Response.WriteAsJsonAsync(new { error = "Pfad fehlt" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetPath = Path.GetFullPath(path);
|
||||||
|
|
||||||
|
if (Directory.Exists(targetPath))
|
||||||
|
{
|
||||||
|
ctx.Response.StatusCode = 409;
|
||||||
|
await ctx.Response.WriteAsJsonAsync(new { error = "Ordner existiert bereits" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parent muss existieren
|
||||||
|
var parent = Path.GetDirectoryName(targetPath);
|
||||||
|
if (string.IsNullOrEmpty(parent) || !Directory.Exists(parent))
|
||||||
|
{
|
||||||
|
ctx.Response.StatusCode = 400;
|
||||||
|
await ctx.Response.WriteAsJsonAsync(new { error = "Elternverzeichnis nicht gefunden" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Directory.CreateDirectory(targetPath);
|
||||||
|
await ctx.Response.WriteAsJsonAsync(new { path = targetPath.Replace('\\', '/'), created = true });
|
||||||
|
}
|
||||||
|
|
||||||
private static bool IsLikelyTextFile(string name)
|
private static bool IsLikelyTextFile(string name)
|
||||||
{
|
{
|
||||||
var lower = name.ToLowerInvariant();
|
var lower = name.ToLowerInvariant();
|
||||||
@@ -424,6 +461,174 @@ public static class WorkspaceEndpoint
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task HandleMkdir(HttpContext ctx)
|
||||||
|
{
|
||||||
|
var sessionId = ctx.Request.Query["sessionId"].FirstOrDefault();
|
||||||
|
var dirPath = ctx.Request.Query["path"].FirstOrDefault();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(dirPath))
|
||||||
|
{
|
||||||
|
ctx.Response.StatusCode = 400;
|
||||||
|
await ctx.Response.WriteAsJsonAsync(new { error = "Pfad fehlt" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var db = ctx.RequestServices.GetRequiredService<Gott.Services.DatabaseService>();
|
||||||
|
var session = await db.GetSession(sessionId ?? "");
|
||||||
|
if (session == null || session.UserId != ctx.GetUserId() || string.IsNullOrEmpty(session.Workspace))
|
||||||
|
{
|
||||||
|
ctx.Response.StatusCode = 400;
|
||||||
|
await ctx.Response.WriteAsJsonAsync(new { error = "Kein Workspace konfiguriert" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var basePath = Path.GetFullPath(session.Workspace);
|
||||||
|
var targetPath = Path.GetFullPath(Path.Combine(basePath, dirPath));
|
||||||
|
|
||||||
|
if (!targetPath.StartsWith(basePath))
|
||||||
|
{
|
||||||
|
ctx.Response.StatusCode = 403;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Directory.Exists(targetPath))
|
||||||
|
{
|
||||||
|
ctx.Response.StatusCode = 409;
|
||||||
|
await ctx.Response.WriteAsJsonAsync(new { error = "Ordner existiert bereits" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Directory.CreateDirectory(targetPath);
|
||||||
|
await ctx.Response.WriteAsJsonAsync(new { path = dirPath, created = true });
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task HandleRmdir(HttpContext ctx)
|
||||||
|
{
|
||||||
|
var sessionId = ctx.Request.Query["sessionId"].FirstOrDefault();
|
||||||
|
var dirPath = ctx.Request.Query["path"].FirstOrDefault();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(dirPath))
|
||||||
|
{
|
||||||
|
ctx.Response.StatusCode = 400;
|
||||||
|
await ctx.Response.WriteAsJsonAsync(new { error = "Pfad fehlt" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var db = ctx.RequestServices.GetRequiredService<Gott.Services.DatabaseService>();
|
||||||
|
var session = await db.GetSession(sessionId ?? "");
|
||||||
|
if (session == null || session.UserId != ctx.GetUserId() || string.IsNullOrEmpty(session.Workspace))
|
||||||
|
{
|
||||||
|
ctx.Response.StatusCode = 400;
|
||||||
|
await ctx.Response.WriteAsJsonAsync(new { error = "Kein Workspace konfiguriert" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var basePath = Path.GetFullPath(session.Workspace);
|
||||||
|
var targetPath = Path.GetFullPath(Path.Combine(basePath, dirPath));
|
||||||
|
|
||||||
|
if (!targetPath.StartsWith(basePath) || targetPath == basePath)
|
||||||
|
{
|
||||||
|
ctx.Response.StatusCode = 403;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Directory.Exists(targetPath))
|
||||||
|
{
|
||||||
|
ctx.Response.StatusCode = 404;
|
||||||
|
await ctx.Response.WriteAsJsonAsync(new { error = "Ordner nicht gefunden" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Directory.GetFileSystemEntries(targetPath).Length > 0)
|
||||||
|
{
|
||||||
|
ctx.Response.StatusCode = 409;
|
||||||
|
await ctx.Response.WriteAsJsonAsync(new { error = "Ordner ist nicht leer" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Directory.Delete(targetPath);
|
||||||
|
await ctx.Response.WriteAsJsonAsync(new { path = dirPath, deleted = true });
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task HandleAgentMkdir(HttpContext ctx)
|
||||||
|
{
|
||||||
|
var basePath = GetAgentBasePath(ctx, out var error);
|
||||||
|
if (basePath == null)
|
||||||
|
{
|
||||||
|
ctx.Response.StatusCode = 400;
|
||||||
|
await ctx.Response.WriteAsJsonAsync(new { error });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dirPath = ctx.Request.Query["path"].FirstOrDefault();
|
||||||
|
if (string.IsNullOrEmpty(dirPath))
|
||||||
|
{
|
||||||
|
ctx.Response.StatusCode = 400;
|
||||||
|
await ctx.Response.WriteAsJsonAsync(new { error = "Pfad fehlt" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetPath = Path.GetFullPath(Path.Combine(basePath, dirPath));
|
||||||
|
if (!targetPath.StartsWith(basePath))
|
||||||
|
{
|
||||||
|
ctx.Response.StatusCode = 403;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Directory.Exists(targetPath))
|
||||||
|
{
|
||||||
|
ctx.Response.StatusCode = 409;
|
||||||
|
await ctx.Response.WriteAsJsonAsync(new { error = "Ordner existiert bereits" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Directory.CreateDirectory(targetPath);
|
||||||
|
await ctx.Response.WriteAsJsonAsync(new { path = dirPath, created = true });
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task HandleAgentRmdir(HttpContext ctx)
|
||||||
|
{
|
||||||
|
var basePath = GetAgentBasePath(ctx, out var error);
|
||||||
|
if (basePath == null)
|
||||||
|
{
|
||||||
|
ctx.Response.StatusCode = 400;
|
||||||
|
await ctx.Response.WriteAsJsonAsync(new { error });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dirPath = ctx.Request.Query["path"].FirstOrDefault();
|
||||||
|
if (string.IsNullOrEmpty(dirPath))
|
||||||
|
{
|
||||||
|
ctx.Response.StatusCode = 400;
|
||||||
|
await ctx.Response.WriteAsJsonAsync(new { error = "Pfad fehlt" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetPath = Path.GetFullPath(Path.Combine(basePath, dirPath));
|
||||||
|
if (!targetPath.StartsWith(basePath) || targetPath == basePath)
|
||||||
|
{
|
||||||
|
ctx.Response.StatusCode = 403;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Directory.Exists(targetPath))
|
||||||
|
{
|
||||||
|
ctx.Response.StatusCode = 404;
|
||||||
|
await ctx.Response.WriteAsJsonAsync(new { error = "Ordner nicht gefunden" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Directory.GetFileSystemEntries(targetPath).Length > 0)
|
||||||
|
{
|
||||||
|
ctx.Response.StatusCode = 409;
|
||||||
|
await ctx.Response.WriteAsJsonAsync(new { error = "Ordner ist nicht leer" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Directory.Delete(targetPath);
|
||||||
|
await ctx.Response.WriteAsJsonAsync(new { path = dirPath, deleted = true });
|
||||||
|
}
|
||||||
|
|
||||||
private static readonly Dictionary<string, string> MimeTypes = new(StringComparer.OrdinalIgnoreCase)
|
private static readonly Dictionary<string, string> MimeTypes = new(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
{ ".png", "image/png" }, { ".jpg", "image/jpeg" }, { ".jpeg", "image/jpeg" },
|
{ ".png", "image/png" }, { ".jpg", "image/jpeg" }, { ".jpeg", "image/jpeg" },
|
||||||
|
|||||||
@@ -81,6 +81,13 @@ input {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-btn:has(i + i) {
|
||||||
|
width: 3em;
|
||||||
|
height: 2em;
|
||||||
|
aspect-ratio: unset;
|
||||||
|
margin-right: var(--badge-spacing);
|
||||||
|
}
|
||||||
|
|
||||||
button.icon-btn,
|
button.icon-btn,
|
||||||
button.icon-btn:hover {
|
button.icon-btn:hover {
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
|
|||||||
@@ -92,6 +92,33 @@
|
|||||||
|
|
||||||
.fe-dir {
|
.fe-dir {
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fe-dir-label {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fe-dir-del {
|
||||||
|
opacity: 0;
|
||||||
|
color: var(--text) !important;
|
||||||
|
padding: 2px 6px !important;
|
||||||
|
font-size: .8em;
|
||||||
|
transition: opacity .15s;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fe-dir:hover .fe-dir-del {
|
||||||
|
opacity: var(--semi-transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fe-dir-del:hover {
|
||||||
|
opacity: 1 !important;
|
||||||
|
color: var(--error) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fe-file {
|
.fe-file {
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ function openFileExplorer(opts = {}) {
|
|||||||
<button class="fe-btn icon-btn" id="feBack" title="Zurück" disabled><i class="fa fa-arrow-left"></i></button>
|
<button class="fe-btn icon-btn" id="feBack" title="Zurück" disabled><i class="fa fa-arrow-left"></i></button>
|
||||||
<span class="fe-path" id="fePath">/</span>
|
<span class="fe-path" id="fePath">/</span>
|
||||||
</div>
|
</div>
|
||||||
|
<button class="fe-btn icon-btn" id="feNewDir" title="Neuer Ordner"><i class="fa fa-folder-open"></i> <i class="fa fa-plus"></i></button>
|
||||||
<button class="fe-btn icon-btn" id="feClose"><i class="fa fa-times"></i></button>
|
<button class="fe-btn icon-btn" id="feClose"><i class="fa fa-times"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="fe-body">
|
<div class="fe-body">
|
||||||
@@ -60,6 +61,7 @@ function openFileExplorer(opts = {}) {
|
|||||||
document.body.appendChild(overlay);
|
document.body.appendChild(overlay);
|
||||||
|
|
||||||
document.getElementById('feBack').addEventListener('click', feGoBack);
|
document.getElementById('feBack').addEventListener('click', feGoBack);
|
||||||
|
document.getElementById('feNewDir').addEventListener('click', feCreateDir);
|
||||||
document.getElementById('feClose').addEventListener('click', closeFileExplorer);
|
document.getElementById('feClose').addEventListener('click', closeFileExplorer);
|
||||||
document.getElementById('feSelectBtn').addEventListener('click', feDoSelect);
|
document.getElementById('feSelectBtn').addEventListener('click', feDoSelect);
|
||||||
document.getElementById('feSaveBtn').addEventListener('click', feDoSave);
|
document.getElementById('feSaveBtn').addEventListener('click', feDoSave);
|
||||||
@@ -138,11 +140,24 @@ async function feLoadDir(path) {
|
|||||||
|
|
||||||
if (entry.type === 'dir') {
|
if (entry.type === 'dir') {
|
||||||
item.classList.add('fe-dir');
|
item.classList.add('fe-dir');
|
||||||
item.innerHTML = '<i class="fa fa-folder"></i> ' + esc(entry.name);
|
const label = document.createElement('span');
|
||||||
item.addEventListener('click', () => {
|
label.className = 'fe-dir-label';
|
||||||
|
label.innerHTML = '<i class="fa fa-folder"></i> ' + esc(entry.name);
|
||||||
|
label.addEventListener('click', () => {
|
||||||
_feState.history.push(path);
|
_feState.history.push(path);
|
||||||
feLoadDir(entry.path);
|
feLoadDir(entry.path);
|
||||||
});
|
});
|
||||||
|
item.appendChild(label);
|
||||||
|
|
||||||
|
const delBtn = document.createElement('button');
|
||||||
|
delBtn.className = 'fe-btn fe-dir-del';
|
||||||
|
delBtn.title = 'Ordner löschen (nur wenn leer)';
|
||||||
|
delBtn.innerHTML = '<i class="fa fa-trash-o"></i>';
|
||||||
|
delBtn.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
feDeleteDir(entry.path, entry.name);
|
||||||
|
});
|
||||||
|
item.appendChild(delBtn);
|
||||||
} else {
|
} else {
|
||||||
item.classList.add('fe-file');
|
item.classList.add('fe-file');
|
||||||
const icon = entry.isText ? '<i class="fa fa-file-text-o"></i>' : getFileIcon(entry.ext);
|
const icon = entry.isText ? '<i class="fa fa-file-text-o"></i>' : getFileIcon(entry.ext);
|
||||||
@@ -505,6 +520,7 @@ function openFolderBrowser(opts = {}) {
|
|||||||
<button class="fe-btn icon-btn" id="feBack" title="Zurück" disabled><i class="fa fa-arrow-left"></i></button>
|
<button class="fe-btn icon-btn" id="feBack" title="Zurück" disabled><i class="fa fa-arrow-left"></i></button>
|
||||||
<span class="fe-path" id="fePath">/</span>
|
<span class="fe-path" id="fePath">/</span>
|
||||||
</div>
|
</div>
|
||||||
|
<button class="fe-btn icon-btn" id="feNewDir" title="Neuer Ordner"><i class="fa fa-folder-open"></i> <i class="fa fa-plus"></i></button>
|
||||||
<button class="fe-btn icon-btn" id="feClose"><i class="fa fa-times"></i></button>
|
<button class="fe-btn icon-btn" id="feClose"><i class="fa fa-times"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="fe-body">
|
<div class="fe-body">
|
||||||
@@ -522,6 +538,7 @@ function openFolderBrowser(opts = {}) {
|
|||||||
document.body.appendChild(overlay);
|
document.body.appendChild(overlay);
|
||||||
|
|
||||||
document.getElementById('feBack').addEventListener('click', fbGoBack);
|
document.getElementById('feBack').addEventListener('click', fbGoBack);
|
||||||
|
document.getElementById('feNewDir').addEventListener('click', fbCreateDir);
|
||||||
document.getElementById('feClose').addEventListener('click', closeFileExplorer);
|
document.getElementById('feClose').addEventListener('click', closeFileExplorer);
|
||||||
document.getElementById('feSelectBtn').addEventListener('click', () => {
|
document.getElementById('feSelectBtn').addEventListener('click', () => {
|
||||||
if (_feState && _feState.onSelect) _feState.onSelect(_feState.currentAbsPath);
|
if (_feState && _feState.onSelect) _feState.onSelect(_feState.currentAbsPath);
|
||||||
@@ -587,6 +604,83 @@ function fbGoBack() {
|
|||||||
fbLoadDir(prev);
|
fbLoadDir(prev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Folder Browser: Create Directory ──
|
||||||
|
|
||||||
|
async function fbCreateDir() {
|
||||||
|
if (!_feState) return;
|
||||||
|
|
||||||
|
const name = prompt('Ordnername:');
|
||||||
|
if (!name || !name.trim()) return;
|
||||||
|
|
||||||
|
const dirName = name.trim();
|
||||||
|
if (/[\/\\:*?"<>|]/.test(dirName)) {
|
||||||
|
alert('Ungültiger Ordnername');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const absPath = _feState.currentAbsPath.replace(/\/$/, '') + '/' + dirName;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resp = await fetch(`/api/workspace/browse/mkdir?path=${encodeURIComponent(absPath)}`, { method: 'POST' });
|
||||||
|
if (!resp.ok) {
|
||||||
|
const err = await resp.json().catch(() => ({}));
|
||||||
|
alert(err.error || 'Fehler beim Erstellen');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fbLoadDir(_feState.currentAbsPath);
|
||||||
|
} catch (e) {
|
||||||
|
alert('Netzwerk-Fehler');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── File Explorer: Create / Delete Directory ──
|
||||||
|
|
||||||
|
async function feCreateDir() {
|
||||||
|
if (!_feState) return;
|
||||||
|
|
||||||
|
const name = prompt('Ordnername:');
|
||||||
|
if (!name || !name.trim()) return;
|
||||||
|
|
||||||
|
const dirName = name.trim();
|
||||||
|
if (/[\/\\:*?"<>|]/.test(dirName)) {
|
||||||
|
alert('Ungültiger Ordnername');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newPath = _feState.currentPath ? _feState.currentPath + '/' + dirName : dirName;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = _feApiUrl('mkdir', `path=${encodeURIComponent(newPath)}`);
|
||||||
|
const resp = await fetch(url, { method: 'POST' });
|
||||||
|
if (!resp.ok) {
|
||||||
|
const err = await resp.json().catch(() => ({}));
|
||||||
|
alert(err.error || 'Fehler beim Erstellen');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
feLoadDir(_feState.currentPath);
|
||||||
|
} catch (e) {
|
||||||
|
alert('Netzwerk-Fehler');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function feDeleteDir(dirPath, dirName) {
|
||||||
|
if (!_feState) return;
|
||||||
|
if (!confirm(`Ordner "${dirName}" löschen?\n(Nur möglich wenn leer)`)) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = _feApiUrl('dir', `path=${encodeURIComponent(dirPath)}`);
|
||||||
|
const resp = await fetch(url, { method: 'DELETE' });
|
||||||
|
if (!resp.ok) {
|
||||||
|
const err = await resp.json().catch(() => ({}));
|
||||||
|
alert(err.error || 'Fehler beim Löschen');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
feLoadDir(_feState.currentPath);
|
||||||
|
} catch (e) {
|
||||||
|
alert('Netzwerk-Fehler');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Helpers ──
|
// ── Helpers ──
|
||||||
|
|
||||||
function getFileIcon(ext) {
|
function getFileIcon(ext) {
|
||||||
|
|||||||
Reference in new issue
Block a user