Improve Samba Global Configuration display in WebUI
Backend: - Parse global config into structured key-value pairs - Return parameters array instead of raw text - Better handling of comments and empty lines Frontend: - Add 'Samba Config' tab to shares page - Display config parameters in readable table format - Color-code parameter names for clarity - Add getSambaConfig() method to API client Co-Authored-By: Patrick <patrick@perlbach24.de>
This commit is contained in:
@@ -180,16 +180,15 @@ class SharesManager:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def get_samba_global_config(self) -> Dict[str, Any]:
|
def get_samba_global_config(self) -> Dict[str, Any]:
|
||||||
"""Read Samba global configuration section"""
|
"""Read Samba global configuration section as structured key-value pairs"""
|
||||||
if not SAMBA_CONFIG.exists():
|
if not SAMBA_CONFIG.exists():
|
||||||
return {"raw": ""}
|
return {"parameters": []}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(SAMBA_CONFIG, 'r') as f:
|
with open(SAMBA_CONFIG, 'r') as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
|
|
||||||
# Extract global section
|
parameters = []
|
||||||
global_section = ""
|
|
||||||
lines = content.split('\n')
|
lines = content.split('\n')
|
||||||
in_global = False
|
in_global = False
|
||||||
for line in lines:
|
for line in lines:
|
||||||
@@ -199,12 +198,20 @@ class SharesManager:
|
|||||||
if in_global:
|
if in_global:
|
||||||
if line.strip().startswith('['):
|
if line.strip().startswith('['):
|
||||||
break
|
break
|
||||||
global_section += line + '\n'
|
line = line.strip()
|
||||||
|
if not line or line.startswith(';') or line.startswith('#'):
|
||||||
|
continue
|
||||||
|
if '=' in line:
|
||||||
|
key, value = line.split('=', 1)
|
||||||
|
parameters.append({
|
||||||
|
"key": key.strip(),
|
||||||
|
"value": value.strip()
|
||||||
|
})
|
||||||
|
|
||||||
return {"raw": global_section.strip()}
|
return {"parameters": parameters}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error reading Samba global config: {e}")
|
logger.error(f"Error reading Samba global config: {e}")
|
||||||
return {"raw": ""}
|
return {"parameters": []}
|
||||||
|
|
||||||
def set_samba_global_config(self, config_text: str) -> bool:
|
def set_samba_global_config(self, config_text: str) -> bool:
|
||||||
"""Write Samba global configuration section"""
|
"""Write Samba global configuration section"""
|
||||||
|
|||||||
@@ -13,9 +13,10 @@ import DeleteConfirmDialog from "@/components/shares/DeleteConfirmDialog"
|
|||||||
|
|
||||||
export default function SharesPage() {
|
export default function SharesPage() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [activeTab, setActiveTab] = useState<"samba" | "nfs">("samba")
|
const [activeTab, setActiveTab] = useState<"samba" | "nfs" | "config">("samba")
|
||||||
const [sambaShares, setSambaShares] = useState<any[]>([])
|
const [sambaShares, setSambaShares] = useState<any[]>([])
|
||||||
const [nfsShares, setNfsShares] = useState<any[]>([])
|
const [nfsShares, setNfsShares] = useState<any[]>([])
|
||||||
|
const [sambaConfig, setSambaConfig] = useState<any[]>([])
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
const [showSambaDialog, setShowSambaDialog] = useState(false)
|
const [showSambaDialog, setShowSambaDialog] = useState(false)
|
||||||
@@ -37,13 +38,15 @@ export default function SharesPage() {
|
|||||||
setLoading(true)
|
setLoading(true)
|
||||||
setError(null)
|
setError(null)
|
||||||
|
|
||||||
const [samba, nfs] = await Promise.all([
|
const [samba, nfs, config] = await Promise.all([
|
||||||
api.getSambaShares().catch(() => []),
|
api.getSambaShares().catch(() => []),
|
||||||
api.getNfsShares().catch(() => []),
|
api.getNfsShares().catch(() => []),
|
||||||
|
api.getSambaConfig().catch(() => ({ parameters: [] })),
|
||||||
])
|
])
|
||||||
|
|
||||||
setSambaShares(samba)
|
setSambaShares(samba)
|
||||||
setNfsShares(nfs)
|
setNfsShares(nfs)
|
||||||
|
setSambaConfig(config.parameters || [])
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : "Failed to load shares")
|
setError(err instanceof Error ? err.message : "Failed to load shares")
|
||||||
} finally {
|
} finally {
|
||||||
@@ -138,6 +141,16 @@ export default function SharesPage() {
|
|||||||
>
|
>
|
||||||
NFS
|
NFS
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab("config")}
|
||||||
|
className={`px-4 py-2 font-medium transition-colors ${
|
||||||
|
activeTab === "config"
|
||||||
|
? "border-b-2 border-primary text-primary"
|
||||||
|
: "text-muted-foreground hover:text-foreground"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Samba Config
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -254,6 +267,41 @@ export default function SharesPage() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* SAMBA CONFIG TAB */}
|
||||||
|
{activeTab === "config" && (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Samba Global Configuration</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
{sambaConfig.length === 0 ? (
|
||||||
|
<p className="text-muted-foreground text-center py-12">
|
||||||
|
No global configuration parameters found.
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<table className="w-full text-sm">
|
||||||
|
<thead className="border-b border-border bg-muted/30">
|
||||||
|
<tr>
|
||||||
|
<th className="text-left py-3 px-4 font-medium w-40">Parameter</th>
|
||||||
|
<th className="text-left py-3 px-4 font-medium">Value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{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>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
{/* Dialogs */}
|
{/* Dialogs */}
|
||||||
|
|||||||
@@ -282,6 +282,11 @@ export class ZFSManagerAPI {
|
|||||||
return response.data
|
return response.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getSambaConfig(): Promise<{ parameters: Array<{ key: string; value: string }> }> {
|
||||||
|
const response = await this.client.get("/api/shares/samba/config")
|
||||||
|
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