feat: Datei-Symbol am Projekt (auto) + VM-Startparameter im Desktop-Panel
- Brain: /projects/status + /list liefern has_files + file_count pro Projekt (Scan /shared/projects/<id>/). Ersetzt das manuelle Code-Flag als primaeren Indikator. VM-Liste liefert boot_cmd (lesbarer aria-vm-Startbefehl). - App: 📄-Symbol (+ Anzahl) an Projekten mit Dateien im ProjectsBrowser. DesktopTile zeigt pro VM den Start-Befehl als Wert dahinter. - Diagnostic: 📄-Symbol (+ Anzahl, Tooltip) an Projekten mit Dateien. Hinweis (kein Code): der VNC-Stream laeuft komplett durch RVS — der Port ist nur der interne QEMU-Display-Port, den die Bridge lokal auf dem Host nutzt; die App oeffnet nie einen Port (firewall-unabhaengig). py/tsc clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -225,6 +225,9 @@ export const ProjectsBrowser: React.FC<Props> = ({ visible = true, onClose, onAc
|
||||
<View style={{ width: 8, height: 8, borderRadius: 4, backgroundColor: dot.color }} />
|
||||
)}
|
||||
<Text style={[s.rowName, isActive && { color: '#34C759' }]}>{item.name}</Text>
|
||||
{item.has_files && (
|
||||
<Text style={{ fontSize: 12 }} accessibilityLabel="hat Dateien">📄{item.file_count ? ` ${item.file_count}` : ''}</Text>
|
||||
)}
|
||||
{hidden && <Text style={s.hiddenBadge}>versteckt</Text>}
|
||||
{item.status === 'ended' && <Text style={s.statusBadge}>beendet</Text>}
|
||||
{isActive && <Text style={s.activeBadge}>✓ FOCUS</Text>}
|
||||
|
||||
@@ -168,6 +168,9 @@ export interface Project {
|
||||
// Optionale absolute noVNC-URL (falls der Desktop direkt erreichbar ist,
|
||||
// sonst laeuft der VNC-Stream als RFB-Bytes durch RVS).
|
||||
desktop_url?: string;
|
||||
// Automatisch: hat das Projekt Dateien in /shared/projects/<id>/? → Datei-Symbol.
|
||||
has_files?: boolean;
|
||||
file_count?: number;
|
||||
}
|
||||
|
||||
export interface ProjectStatus {
|
||||
@@ -185,6 +188,7 @@ export interface ProjectVm {
|
||||
mem: number;
|
||||
running?: boolean;
|
||||
vnc_port?: number;
|
||||
boot_cmd?: string;
|
||||
created_at?: number;
|
||||
}
|
||||
|
||||
|
||||
@@ -101,8 +101,9 @@ const DesktopTile: React.FC<Props> = ({ projectId, focused }) => {
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={styles.vmName}>
|
||||
<Text style={{ color: vm.running ? '#34C759' : '#555570' }}>●</Text> {vm.name}
|
||||
<Text style={styles.vmMeta}> {vm.arch} · {vm.running ? 'läuft' : 'gestoppt'}</Text>
|
||||
</Text>
|
||||
<Text style={styles.vmMeta}>{vm.arch} · Display :{vm.vnc_display} · {vm.running ? 'läuft' : 'gestoppt'}</Text>
|
||||
<Text style={styles.vmCmd} numberOfLines={2}>{vm.boot_cmd || `aria-vm boot ${vm.name} --vnc-display ${vm.vnc_display}`}</Text>
|
||||
</View>
|
||||
<View style={styles.vmBtns}>
|
||||
{isBusy ? (
|
||||
@@ -145,7 +146,8 @@ const styles = StyleSheet.create({
|
||||
err: { color: '#FF6E6E', fontSize: 13, marginTop: 16 },
|
||||
vmRow: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#12122A', borderRadius: 10, padding: 12, marginBottom: 8 },
|
||||
vmName: { color: '#E0E0F0', fontSize: 15, fontWeight: '700' },
|
||||
vmMeta: { color: '#8888AA', fontSize: 12, marginTop: 3 },
|
||||
vmMeta: { color: '#8888AA', fontSize: 12, fontWeight: '400' },
|
||||
vmCmd: { color: '#6A9BD0', fontSize: 11, fontFamily: 'monospace', marginTop: 4 },
|
||||
vmBtns: { flexDirection: 'row', gap: 6, alignItems: 'center' },
|
||||
vmBtn: { borderWidth: 1, borderRadius: 8, paddingHorizontal: 10, paddingVertical: 6 },
|
||||
vmBtnText: { fontSize: 12, fontWeight: '700' },
|
||||
|
||||
+47
-2
@@ -770,15 +770,49 @@ def projects_queue_status():
|
||||
|
||||
# ── Projekte ────────────────────────────────────────────────────────
|
||||
|
||||
def _project_file_count(pid: str) -> int:
|
||||
"""Anzahl Dateien in /shared/projects/<pid>/ (rekursiv, gecappt). 0 = leer."""
|
||||
base = os.path.join("/shared/projects", pid or "")
|
||||
if not os.path.isdir(base):
|
||||
return 0
|
||||
cnt = 0
|
||||
try:
|
||||
for _dp, dns, fns in os.walk(base):
|
||||
dns[:] = [d for d in dns if d not in (".git", "node_modules", "__pycache__", ".venv", "venv")]
|
||||
cnt += len(fns)
|
||||
if cnt > 999:
|
||||
return 999
|
||||
except Exception:
|
||||
return 0
|
||||
return cnt
|
||||
|
||||
|
||||
def _enrich_projects(projects: list) -> list:
|
||||
"""Ergaenzt has_files + file_count pro Projekt (fuer das Datei-Symbol in der
|
||||
Liste). Das ersetzt das manuelle Code-Flag als primaeren Code-Indikator."""
|
||||
for p in projects or []:
|
||||
if not isinstance(p, dict):
|
||||
continue
|
||||
c = _project_file_count(p.get("id") or "")
|
||||
p["file_count"] = c
|
||||
p["has_files"] = c > 0
|
||||
return projects
|
||||
|
||||
|
||||
@app.get("/projects/status")
|
||||
def projects_status():
|
||||
"""Komplett-Status: aktives Projekt + Liste aller (nicht-archivierten)."""
|
||||
return projects_mod.status()
|
||||
st = projects_mod.status()
|
||||
_enrich_projects(st.get("projects", []))
|
||||
if st.get("active"):
|
||||
_enrich_projects([st["active"]])
|
||||
return st
|
||||
|
||||
|
||||
@app.get("/projects/list")
|
||||
def projects_list(include_archived: bool = False):
|
||||
return {"projects": projects_mod.list_projects(include_archived=include_archived)}
|
||||
return {"projects": _enrich_projects(
|
||||
projects_mod.list_projects(include_archived=include_archived))}
|
||||
|
||||
|
||||
class ProjectCreateBody(BaseModel):
|
||||
@@ -933,6 +967,16 @@ class VmAddBody(BaseModel):
|
||||
size: str = "10G"
|
||||
|
||||
|
||||
def _vm_boot_cmd(v: dict) -> str:
|
||||
"""Lesbarer Start-Befehl (aria-vm) als 'Wert' hinter dem VM-Eintrag."""
|
||||
parts = ["aria-vm", "boot", v.get("name", "?"),
|
||||
"--vnc-display", str(v.get("vnc_display", 1)),
|
||||
"--mem", str(v.get("mem", 1024))]
|
||||
if v.get("iso"):
|
||||
parts += ["--iso", v["iso"]]
|
||||
return " ".join(parts)
|
||||
|
||||
|
||||
@app.get("/projects/{project_id}/vms")
|
||||
def project_vms_list(project_id: str):
|
||||
vms = [dict(v) for v in project_vms_mod.list_vms(project_id)]
|
||||
@@ -940,6 +984,7 @@ def project_vms_list(project_id: str):
|
||||
for v in vms:
|
||||
v["running"] = v.get("name") in running
|
||||
v["vnc_port"] = 5900 + int(v.get("vnc_display", 1))
|
||||
v["boot_cmd"] = _vm_boot_cmd(v)
|
||||
return {"projectId": project_id, "vms": vms}
|
||||
|
||||
|
||||
|
||||
@@ -3192,6 +3192,7 @@
|
||||
<div onclick="switchProject('${p.id}')" style="cursor:pointer;flex:1;">
|
||||
<div style="color:${isActive ? '#34C759' : '#E0E0F0'};font-weight:600;">
|
||||
${hidden ? '🙈' : '📁'} ${escapeHtml(p.name)}
|
||||
${p.has_files ? `<span title="${p.file_count || 0} Dateien in /shared/projects/${escapeHtmlAttr(p.id)}/" style="font-size:11px;margin-left:4px;">📄${p.file_count ? ' ' + p.file_count : ''}</span>` : ''}
|
||||
${hidden ? '<span style="color:#B392F0;font-size:10px;font-weight:700;margin-left:6px;background:rgba(179,146,240,0.15);padding:2px 6px;border-radius:3px;">versteckt</span>' : ''}
|
||||
${ended ? '<span style="color:#FFD60A;font-size:10px;font-weight:700;margin-left:6px;background:rgba(255,214,10,0.15);padding:2px 6px;border-radius:3px;">beendet</span>' : ''}
|
||||
${p.kind === 'code' ? '<span style="color:#0096FF;font-size:10px;font-weight:700;margin-left:6px;background:rgba(0,150,255,0.15);padding:2px 6px;border-radius:3px;"></> Code</span>' : ''}
|
||||
|
||||
Reference in New Issue
Block a user