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):
|
class SambaConfigRequest(BaseModel):
|
||||||
config: str
|
parameters: dict[str, str]
|
||||||
|
|
||||||
|
|
||||||
class SambaImportRequest(BaseModel):
|
class SambaImportRequest(BaseModel):
|
||||||
@@ -108,9 +108,9 @@ async def set_samba_config(
|
|||||||
request: SambaConfigRequest,
|
request: SambaConfigRequest,
|
||||||
current_user: str = Depends(get_current_user)
|
current_user: str = Depends(get_current_user)
|
||||||
):
|
):
|
||||||
"""Update Samba global configuration"""
|
"""Update Samba global configuration parameters"""
|
||||||
try:
|
try:
|
||||||
success = share_manager.set_samba_global_config(request.config)
|
success = share_manager.set_samba_global_config(request.parameters)
|
||||||
if not success:
|
if not success:
|
||||||
raise HTTPException(status_code=400, detail="Failed to update Samba configuration")
|
raise HTTPException(status_code=400, detail="Failed to update Samba configuration")
|
||||||
return {"status": "updated"}
|
return {"status": "updated"}
|
||||||
|
|||||||
+13
-34
@@ -216,42 +216,21 @@ class SharesManager:
|
|||||||
logger.error(f"Error reading Samba registry config: {e}")
|
logger.error(f"Error reading Samba registry config: {e}")
|
||||||
return {"parameters": []}
|
return {"parameters": []}
|
||||||
|
|
||||||
def set_samba_global_config(self, config_text: str) -> bool:
|
def set_samba_global_config(self, parameters: Dict[str, str]) -> bool:
|
||||||
"""Write Samba global configuration section"""
|
"""Update Samba global configuration parameters using 'net conf setparm'"""
|
||||||
if not SAMBA_CONFIG.exists():
|
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(SAMBA_CONFIG, 'r') as f:
|
for key, value in parameters.items():
|
||||||
lines = f.readlines()
|
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
|
logger.info(f"Samba global config updated: {len(parameters)} parameters")
|
||||||
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")
|
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error writing Samba global config: {e}")
|
logger.error(f"Error writing Samba global config: {e}")
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ export default function SharesPage() {
|
|||||||
const [showNfsDialog, setShowNfsDialog] = useState(false)
|
const [showNfsDialog, setShowNfsDialog] = useState(false)
|
||||||
const [deleteConfirm, setDeleteConfirm] = useState<{ type: "samba" | "nfs"; name: string } | null>(null)
|
const [deleteConfirm, setDeleteConfirm] = useState<{ type: "samba" | "nfs"; name: string } | null>(null)
|
||||||
const [deleting, setDeleting] = useState(false)
|
const [deleting, setDeleting] = useState(false)
|
||||||
|
const [editMode, setEditMode] = useState(false)
|
||||||
|
const [editedConfig, setEditedConfig] = useState<{ [key: string]: string }>({})
|
||||||
|
const [saving, setSaving] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const token = localStorage.getItem("access_token")
|
const token = localStorage.getItem("access_token")
|
||||||
@@ -90,6 +93,29 @@ export default function SharesPage() {
|
|||||||
setShowNfsDialog(false)
|
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 (
|
return (
|
||||||
<div className="min-h-screen bg-background">
|
<div className="min-h-screen bg-background">
|
||||||
<Header />
|
<Header />
|
||||||
@@ -272,7 +298,24 @@ export default function SharesPage() {
|
|||||||
{activeTab === "config" && (
|
{activeTab === "config" && (
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<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>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{sambaConfig.length === 0 ? (
|
{sambaConfig.length === 0 ? (
|
||||||
@@ -292,7 +335,18 @@ export default function SharesPage() {
|
|||||||
{sambaConfig.map((param, idx) => (
|
{sambaConfig.map((param, idx) => (
|
||||||
<tr key={idx} className="border-b border-border/50 hover:bg-muted/30">
|
<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 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>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -287,6 +287,11 @@ export class ZFSManagerAPI {
|
|||||||
return response.data
|
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
|
// Shares — NFS
|
||||||
async getNfsShares(): Promise<NfsShare[]> {
|
async getNfsShares(): Promise<NfsShare[]> {
|
||||||
const response = await this.client.get("/api/shares/nfs")
|
const response = await this.client.get("/api/shares/nfs")
|
||||||
|
|||||||
Reference in New Issue
Block a user