feat: DefaultPath als Base-Path für User-Workspaces

- WorkspaceHelper.cs: Zentrale Workspace-Auflösung (relativ → absolut)
- Session-Workspace wird relativ zum User-DefaultPath gespeichert
- System-Prompt bekommt aufgelösten absoluten Pfad
- Path-Traversal-Schutz: '..' in Workspace-Pfaden blockiert
- Browse-API gibt '..' nicht mehr als Eintrag zurück
- Folder-Browser: basePath-Limit, Back-Button navigiert zum Parent
- DefaultPath nur durch Admin bearbeitbar (Profil: readonly)
- Legacy: Absolute Pfade in session.Workspace weiterhin kompatibel
This commit is contained in:
Meleki committed 2026-03-05 22:49:09 +01:00
1 parent 8e7bda50f9
commit 4e1ed81f37
12 files changed
+240 -106

No files matched your search

+1 -1
View File
@@ -94,7 +94,7 @@
color: var(--text);
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--badge-spacing);
}
.fe-dir-label {
+1 -5
View File
@@ -398,11 +398,7 @@
<label>Anzeigename</label>
<input type="text" id="profileDisplayName" placeholder="Anzeigename">
<label>Standard-Pfad</label>
<span class="setting-hint">Wird als Basis-Pfad beim Auswählen eines Workspace in Sessions verwendet.</span>
<div class="input-with-btn">
<input type="text" id="profileDefaultPath" placeholder="z.B. /home/user/projects" autocomplete="off">
<button type="button" id="profileDefaultPathBrowse" class="icon-btn"><i class="fa fa-folder"></i></button>
</div>
<input type="text" id="profileDefaultPath" disabled placeholder="Wird vom Administrator festgelegt">
<label>Persona</label>
<span class="setting-hint">Wird im System-Prompt verwendet, wenn der Agent "Kennt sein Gegenüber" aktiviert hat.</span>
<textarea id="profilePersona" rows="8" placeholder="Beschreibe dich selbst – Name, Rolle, Präferenzen, Kommunikationsstil..."></textarea>
+26 -4
View File
@@ -504,6 +504,7 @@ function openFolderBrowser(opts = {}) {
mode: 'folder',
onSelect: opts.onSelect || null,
currentAbsPath: opts.startPath || '/',
basePath: (opts.basePath || '').replace(/\/+$/, '') || null,
history: []
};
@@ -592,16 +593,37 @@ async function fbLoadDir(absPath) {
sidebar.innerHTML = '<div class="fe-empty">Leer</div>';
}
document.getElementById('feBack').disabled = _feState.history.length === 0;
const currentNorm = data.current.replace(/\/+$/, '');
const atBase = _feState.basePath && currentNorm === _feState.basePath;
const atRoot = currentNorm === '' || currentNorm === '/';
document.getElementById('feBack').disabled = atBase || (atRoot && _feState.history.length === 0);
} catch (e) {
sidebar.innerHTML = '<div class="fe-error">Netzwerk-Fehler</div>';
}
}
function fbGoBack() {
if (!_feState || _feState.history.length === 0) return;
const prev = _feState.history.pop();
fbLoadDir(prev);
if (!_feState) return;
// Nicht über den basePath hinaus navigieren
const currentNorm = _feState.currentAbsPath.replace(/\/+$/, '');
if (_feState.basePath && currentNorm === _feState.basePath) return;
if (_feState.history.length > 0) {
const prev = _feState.history.pop();
// Prüfe ob der vorherige Pfad über dem basePath liegt
if (_feState.basePath && !prev.replace(/\/+$/, '').startsWith(_feState.basePath)) {
_feState.history.length = 0;
fbLoadDir(_feState.basePath);
return;
}
fbLoadDir(prev);
} else {
// Kein History-Eintrag → zum Parent navigieren
const parent = currentNorm.includes('/') ? currentNorm.substring(0, currentNorm.lastIndexOf('/')) || '/' : '/';
if (parent !== currentNorm) {
fbLoadDir(parent);
}
}
}
// ── Folder Browser: Create Directory ──
-14
View File
@@ -10,7 +10,6 @@ registerPage('profile', {
var usernameInput = document.getElementById('profileUsername');
var displayNameInput = document.getElementById('profileDisplayName');
var defaultPathInput = document.getElementById('profileDefaultPath');
var defaultPathBrowse = document.getElementById('profileDefaultPathBrowse');
var personaInput = document.getElementById('profilePersona');
var saveBtn = document.getElementById('profileSaveBtn');
var messageDiv = document.getElementById('profileMessage');
@@ -43,18 +42,6 @@ registerPage('profile', {
displayNameInput.value = profile.displayName || '';
defaultPathInput.value = profile.defaultPath || '';
// Default Path Browse
if (defaultPathBrowse) {
defaultPathBrowse.addEventListener('click', function() {
openFolderBrowser({
startPath: defaultPathInput.value.trim() || '/',
onSelect: function(path) {
defaultPathInput.value = path;
}
});
});
}
// Profil speichern
saveBtn.addEventListener('click', async function() {
saveBtn.disabled = true;
@@ -64,7 +51,6 @@ registerPage('profile', {
try {
var updated = await apiUpdateProfile({
displayName: displayNameInput.value.trim(),
defaultPath: defaultPathInput.value.trim() || null,
persona: _profileMde.value().trim()
});
+22 -3
View File
@@ -129,10 +129,29 @@ registerPage('session', {
const workspaceBrowse = document.getElementById('sessionWorkspaceBrowse');
if (workspaceBrowse) {
workspaceBrowse.addEventListener('click', () => {
const defaultPath = (currentUser && currentUser.defaultPath) || '';
const currentVal = workspaceEl.value.trim();
// Für den Folder-Browser den absoluten Startpfad zusammenbauen
let startPath = '/';
if (defaultPath && currentVal) {
startPath = defaultPath.replace(/\/+$/, '') + '/' + currentVal;
} else if (defaultPath) {
startPath = defaultPath;
} else if (currentVal) {
startPath = currentVal;
}
openFolderBrowser({
startPath: workspaceEl.value.trim() || (currentUser && currentUser.defaultPath) || '/',
onSelect(path) {
workspaceEl.value = path;
startPath,
basePath: defaultPath || null,
onSelect(absolutePath) {
// Relativen Pfad zum DefaultPath berechnen
if (defaultPath && absolutePath.startsWith(defaultPath)) {
let rel = absolutePath.slice(defaultPath.length).replace(/^\/+/, '');
workspaceEl.value = rel || '';
} else {
// Kein DefaultPath oder Pfad außerhalb → absoluten Pfad beibehalten (Legacy)
workspaceEl.value = absolutePath;
}
}
});
});