From 55ae3b79aebe7b73c7b50ef5156d03c5dc5ef957 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 22 Apr 2026 01:31:14 +0200 Subject: [PATCH] Add editable Samba global configuration with net conf setparm Backend: - Use 'net conf setparm' to update individual parameters - Accept dictionary of key-value pairs in API Frontend: - Add Edit/Cancel/Save buttons to Samba Config tab - Inline editing of configuration values - Save changes back to registry Changes are applied immediately via net conf setparm. Co-Authored-By: Patrick --- backend/routers/shares.py | 6 ++-- backend/services/shares.py | 47 ++++++++--------------------- frontend/app/shares/page.tsx | 58 ++++++++++++++++++++++++++++++++++-- frontend/lib/api.ts | 5 ++++ 4 files changed, 77 insertions(+), 39 deletions(-) diff --git a/backend/routers/shares.py b/backend/routers/shares.py index e51b39b..0f21979 100644 --- a/backend/routers/shares.py +++ b/backend/routers/shares.py @@ -38,7 +38,7 @@ class CreateNFSShareRequest(BaseModel): class SambaConfigRequest(BaseModel): - config: str + parameters: dict[str, str] class SambaImportRequest(BaseModel): @@ -108,9 +108,9 @@ async def set_samba_config( request: SambaConfigRequest, current_user: str = Depends(get_current_user) ): - """Update Samba global configuration""" + """Update Samba global configuration parameters""" try: - success = share_manager.set_samba_global_config(request.config) + success = share_manager.set_samba_global_config(request.parameters) if not success: raise HTTPException(status_code=400, detail="Failed to update Samba configuration") return {"status": "updated"} diff --git a/backend/services/shares.py b/backend/services/shares.py index 338ed0b..78ca8c1 100644 --- a/backend/services/shares.py +++ b/backend/services/shares.py @@ -216,42 +216,21 @@ class SharesManager: logger.error(f"Error reading Samba registry config: {e}") return {"parameters": []} - def set_samba_global_config(self, config_text: str) -> bool: - """Write Samba global configuration section""" - if not SAMBA_CONFIG.exists(): - return False - + def set_samba_global_config(self, parameters: Dict[str, str]) -> bool: + """Update Samba global configuration parameters using 'net conf setparm'""" try: - with open(SAMBA_CONFIG, 'r') as f: - lines = f.readlines() + for key, value in parameters.items(): + result = subprocess.run( + ['/usr/bin/net', 'conf', 'setparm', 'global', key, value], + capture_output=True, + text=True, + timeout=10 + ) + if result.returncode != 0: + logger.error(f"Failed to set {key}={value}: {result.stderr}") + return False - # Find global section and shares - output_lines = [] - skip_global = False - for i, line in enumerate(lines): - if line.strip().startswith('[global]'): - skip_global = True - output_lines.append('[global]\n') - # Add config lines - for config_line in config_text.split('\n'): - if config_line.strip(): - output_lines.append(' ' + config_line + '\n') - output_lines.append('\n') - continue - - if skip_global: - if line.strip().startswith('['): - skip_global = False - output_lines.append(line) - continue - - output_lines.append(line) - - with open(SAMBA_CONFIG, 'w') as f: - f.writelines(output_lines) - - subprocess.run(['smbcontrol', 'smbd', 'reload-config'], capture_output=True, timeout=10) - logger.info("Samba global config updated") + logger.info(f"Samba global config updated: {len(parameters)} parameters") return True except Exception as e: logger.error(f"Error writing Samba global config: {e}") diff --git a/frontend/app/shares/page.tsx b/frontend/app/shares/page.tsx index 88661fc..f6400e9 100644 --- a/frontend/app/shares/page.tsx +++ b/frontend/app/shares/page.tsx @@ -23,6 +23,9 @@ export default function SharesPage() { const [showNfsDialog, setShowNfsDialog] = useState(false) const [deleteConfirm, setDeleteConfirm] = useState<{ type: "samba" | "nfs"; name: string } | null>(null) const [deleting, setDeleting] = useState(false) + const [editMode, setEditMode] = useState(false) + const [editedConfig, setEditedConfig] = useState<{ [key: string]: string }>({}) + const [saving, setSaving] = useState(false) useEffect(() => { const token = localStorage.getItem("access_token") @@ -90,6 +93,29 @@ export default function SharesPage() { setShowNfsDialog(false) } + const handleEditMode = () => { + const configMap = sambaConfig.reduce((acc, param) => { + acc[param.key] = param.value + return acc + }, {} as { [key: string]: string }) + setEditedConfig(configMap) + setEditMode(true) + } + + const handleSaveConfig = async () => { + try { + setSaving(true) + await api.setSambaConfig(editedConfig) + setEditMode(false) + await loadShares() + setError(null) + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to save configuration") + } finally { + setSaving(false) + } + } + return (
@@ -272,7 +298,24 @@ export default function SharesPage() { {activeTab === "config" && ( - Samba Global Configuration +
+ Samba Global Configuration + {!editMode && sambaConfig.length > 0 && ( + + )} + {editMode && ( +
+ + +
+ )} +
{sambaConfig.length === 0 ? ( @@ -292,7 +335,18 @@ export default function SharesPage() { {sambaConfig.map((param, idx) => ( {param.key} - {param.value} + + {editMode ? ( + setEditedConfig({ ...editedConfig, [param.key]: e.target.value })} + className="w-full px-2 py-1 rounded border border-border bg-background text-xs font-mono" + /> + ) : ( + {param.value} + )} + ))} diff --git a/frontend/lib/api.ts b/frontend/lib/api.ts index 8cb6411..775cf16 100644 --- a/frontend/lib/api.ts +++ b/frontend/lib/api.ts @@ -287,6 +287,11 @@ export class ZFSManagerAPI { return response.data } + async setSambaConfig(parameters: { [key: string]: string }): Promise<{ status: string }> { + const response = await this.client.put("/api/shares/samba/config", { parameters }) + return response.data + } + // Shares — NFS async getNfsShares(): Promise { const response = await this.client.get("/api/shares/nfs")