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 <patrick@perlbach24.de>
This commit is contained in:
@@ -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"}
|
||||
|
||||
+13
-34
@@ -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}")
|
||||
|
||||
@@ -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 (
|
||||
<div className="min-h-screen bg-background">
|
||||
<Header />
|
||||
@@ -272,7 +298,24 @@ export default function SharesPage() {
|
||||
{activeTab === "config" && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Samba Global Configuration</CardTitle>
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle>Samba Global Configuration</CardTitle>
|
||||
{!editMode && sambaConfig.length > 0 && (
|
||||
<Button size="sm" onClick={handleEditMode} variant="outline">
|
||||
Edit Config
|
||||
</Button>
|
||||
)}
|
||||
{editMode && (
|
||||
<div className="flex gap-2">
|
||||
<Button size="sm" onClick={handleSaveConfig} disabled={saving}>
|
||||
{saving ? "Saving..." : "Save Changes"}
|
||||
</Button>
|
||||
<Button size="sm" onClick={() => setEditMode(false)} variant="outline" disabled={saving}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{sambaConfig.length === 0 ? (
|
||||
@@ -292,7 +335,18 @@ export default function SharesPage() {
|
||||
{sambaConfig.map((param, idx) => (
|
||||
<tr key={idx} className="border-b border-border/50 hover:bg-muted/30">
|
||||
<td className="py-3 px-4 font-mono text-xs font-medium text-blue-600">{param.key}</td>
|
||||
<td className="py-3 px-4 text-xs font-mono break-all">{param.value}</td>
|
||||
<td className="py-3 px-4 text-xs">
|
||||
{editMode ? (
|
||||
<input
|
||||
type="text"
|
||||
value={editedConfig[param.key] || ""}
|
||||
onChange={(e) => setEditedConfig({ ...editedConfig, [param.key]: e.target.value })}
|
||||
className="w-full px-2 py-1 rounded border border-border bg-background text-xs font-mono"
|
||||
/>
|
||||
) : (
|
||||
<span className="font-mono break-all">{param.value}</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
||||
@@ -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<NfsShare[]> {
|
||||
const response = await this.client.get("/api/shares/nfs")
|
||||
|
||||
Reference in New Issue
Block a user