Feature: MacOS-Toggle + Raw-Editor im Samba Config-Tab
- Toggle 'Global MacOS Shares' setzt/entfernt fruit-Parameter automatisch (fruit:encoding=native, fruit:metadata=stream, fruit:zero_file_id=yes, fruit:nfs_aces=no) via net conf setparm - Raw-Textarea zeigt alle globalen Parameter direkt editierbar (key = value) - Tabellen-Ansicht durch Raw-Editor ersetzt Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+111
-69
@@ -24,8 +24,9 @@ export default function SharesPage() {
|
|||||||
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 [editMode, setEditMode] = useState(false)
|
||||||
const [editedConfig, setEditedConfig] = useState<{ [key: string]: string }>({})
|
const [rawConfigText, setRawConfigText] = useState("")
|
||||||
const [saving, setSaving] = useState(false)
|
const [saving, setSaving] = useState(false)
|
||||||
|
const [macosEnabled, setMacosEnabled] = useState(false)
|
||||||
const [editingShare, setEditingShare] = useState<any | null>(null)
|
const [editingShare, setEditingShare] = useState<any | null>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -50,7 +51,14 @@ export default function SharesPage() {
|
|||||||
|
|
||||||
setSambaShares(samba)
|
setSambaShares(samba)
|
||||||
setNfsShares(nfs)
|
setNfsShares(nfs)
|
||||||
setSambaConfig(config.parameters || [])
|
const params: { key: string; value: string }[] = config.parameters || []
|
||||||
|
setSambaConfig(params)
|
||||||
|
const raw = params.map((p) => `${p.key} = ${p.value}`).join("\n")
|
||||||
|
setRawConfigText(raw)
|
||||||
|
const hasMacOS = ["fruit:encoding", "fruit:metadata", "fruit:zero_file_id", "fruit:nfs_aces"].every(
|
||||||
|
(k) => params.some((p) => p.key === k)
|
||||||
|
)
|
||||||
|
setMacosEnabled(hasMacOS)
|
||||||
} 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 {
|
||||||
@@ -117,21 +125,29 @@ export default function SharesPage() {
|
|||||||
setShowNfsDialog(false)
|
setShowNfsDialog(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleEditMode = () => {
|
const MACOS_PARAMS: { [key: string]: string } = {
|
||||||
const configMap = sambaConfig.reduce((acc, param) => {
|
"fruit:encoding": "native",
|
||||||
acc[param.key] = param.value
|
"fruit:metadata": "stream",
|
||||||
return acc
|
"fruit:zero_file_id": "yes",
|
||||||
}, {} as { [key: string]: string })
|
"fruit:nfs_aces": "no",
|
||||||
setEditedConfig(configMap)
|
|
||||||
setEditMode(true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSaveConfig = async () => {
|
const MACOS_KEYS = Object.keys(MACOS_PARAMS)
|
||||||
|
|
||||||
|
const handleSaveRaw = async () => {
|
||||||
try {
|
try {
|
||||||
setSaving(true)
|
setSaving(true)
|
||||||
await api.setSambaConfig(editedConfig)
|
const parsed: { [key: string]: string } = {}
|
||||||
setEditMode(false)
|
rawConfigText.split("\n").forEach((line) => {
|
||||||
|
const eq = line.indexOf("=")
|
||||||
|
if (eq === -1) return
|
||||||
|
const key = line.slice(0, eq).trim()
|
||||||
|
const value = line.slice(eq + 1).trim()
|
||||||
|
if (key) parsed[key] = value
|
||||||
|
})
|
||||||
|
await api.setSambaConfig(parsed)
|
||||||
await loadShares()
|
await loadShares()
|
||||||
|
setEditMode(false)
|
||||||
setError(null)
|
setError(null)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : "Failed to save configuration")
|
setError(err instanceof Error ? err.message : "Failed to save configuration")
|
||||||
@@ -140,6 +156,27 @@ export default function SharesPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleToggleMacOS = async (enable: boolean) => {
|
||||||
|
try {
|
||||||
|
setSaving(true)
|
||||||
|
const current = sambaConfig.reduce((acc, p) => { acc[p.key] = p.value; return acc }, {} as { [key: string]: string })
|
||||||
|
if (enable) {
|
||||||
|
await api.setSambaConfig({ ...current, ...MACOS_PARAMS } as { [key: string]: string })
|
||||||
|
} else {
|
||||||
|
const filtered: { [key: string]: string } = {}
|
||||||
|
Object.entries(current).forEach(([k, v]) => { if (!MACOS_KEYS.includes(k)) filtered[k] = v as string })
|
||||||
|
await api.setSambaConfig(filtered)
|
||||||
|
}
|
||||||
|
setMacosEnabled(enable)
|
||||||
|
await loadShares()
|
||||||
|
setError(null)
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err.message : "Failed to update macOS settings")
|
||||||
|
} finally {
|
||||||
|
setSaving(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background">
|
<div className="min-h-screen bg-background">
|
||||||
<Header />
|
<Header />
|
||||||
@@ -390,65 +427,70 @@ export default function SharesPage() {
|
|||||||
|
|
||||||
{/* SAMBA CONFIG TAB */}
|
{/* SAMBA CONFIG TAB */}
|
||||||
{activeTab === "config" && (
|
{activeTab === "config" && (
|
||||||
<Card>
|
<div className="space-y-4">
|
||||||
<CardHeader>
|
{/* MacOS Toggle */}
|
||||||
<div className="flex items-center justify-between">
|
<Card>
|
||||||
<CardTitle>Samba Global Configuration</CardTitle>
|
<CardContent className="py-5">
|
||||||
{!editMode && sambaConfig.length > 0 && (
|
<div className="flex items-center justify-between">
|
||||||
<Button size="sm" onClick={handleEditMode} variant="outline">
|
<div>
|
||||||
Edit Config
|
<p className="font-medium">Global MacOS Shares</p>
|
||||||
</Button>
|
<p className="text-sm text-muted-foreground mt-0.5">Optimize all shares for MacOS</p>
|
||||||
)}
|
{macosEnabled && (
|
||||||
{editMode && (
|
<div className="mt-2 flex flex-wrap gap-2">
|
||||||
<div className="flex gap-2">
|
{Object.entries(MACOS_PARAMS).map(([k, v]) => (
|
||||||
<Button size="sm" onClick={handleSaveConfig} disabled={saving}>
|
<span key={k} className="text-xs font-mono bg-muted px-2 py-0.5 rounded">
|
||||||
{saving ? "Saving..." : "Save Changes"}
|
{k} = {v}
|
||||||
</Button>
|
</span>
|
||||||
<Button size="sm" onClick={() => setEditMode(false)} variant="outline" disabled={saving}>
|
))}
|
||||||
Cancel
|
</div>
|
||||||
</Button>
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
<button
|
||||||
</div>
|
role="switch"
|
||||||
</CardHeader>
|
aria-checked={macosEnabled}
|
||||||
<CardContent>
|
disabled={saving}
|
||||||
{sambaConfig.length === 0 ? (
|
onClick={() => handleToggleMacOS(!macosEnabled)}
|
||||||
<p className="text-muted-foreground text-center py-12">
|
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none ${macosEnabled ? "bg-red-500" : "bg-muted-foreground/30"} disabled:opacity-50`}
|
||||||
No global configuration parameters found.
|
>
|
||||||
</p>
|
<span className={`inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform ${macosEnabled ? "translate-x-6" : "translate-x-1"}`} />
|
||||||
) : (
|
</button>
|
||||||
<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">
|
|
||||||
{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>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</CardContent>
|
||||||
</CardContent>
|
</Card>
|
||||||
</Card>
|
|
||||||
|
{/* Advanced raw config */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<CardTitle>Advanced</CardTitle>
|
||||||
|
{!editMode ? (
|
||||||
|
<Button size="sm" onClick={() => setEditMode(true)} variant="outline">
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button size="sm" onClick={handleSaveRaw} disabled={saving}>
|
||||||
|
{saving ? "Saving..." : "Apply"}
|
||||||
|
</Button>
|
||||||
|
<Button size="sm" onClick={() => setEditMode(false)} variant="outline" disabled={saving}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<textarea
|
||||||
|
readOnly={!editMode}
|
||||||
|
value={rawConfigText}
|
||||||
|
onChange={(e) => setRawConfigText(e.target.value)}
|
||||||
|
rows={20}
|
||||||
|
spellCheck={false}
|
||||||
|
className={`w-full font-mono text-xs rounded border px-3 py-2 bg-background resize-y focus:outline-none focus:ring-2 focus:ring-ring ${editMode ? "border-border" : "border-transparent text-muted-foreground"}`}
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user