Cluster UI: Blocklists management, locked config-file items, read-only upstreams; honour allow_config_write
The console now works as the cluster's editor, without the monitoring pages. Web: Blocklists gets Add/Edit/Remove for BlockList objects (and shows ones the resolver has not compiled yet); Clients marks cluster groups and locks config-file groups and the default policy; Upstreams becomes a read-only health view when the config cannot be written; Zones and Domains lose the managed-directory and list copy; the Local zones page and its API calls are removed; nav is Status, Blocklists, Domains, Lookup, Address redirects, Zones, Clients, Upstreams, Settings. Console: a change to a group the resolver's config file defines is refused with the reason instead of being skipped silently, and /api/clients says what the console can and cannot edit. Server: isFileWritable called a read-only existing file (a mounted ConfigMap) writable, and api.allow_config_write was not reflected in the writable flag nor checked by the upstream/ACL edit path. Both fixed; tests pin them. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7d58b80583
commit
7fa48bcdf9
18 files changed
+726
-404
No files matched your search
+20
-38
@@ -2,17 +2,17 @@
|
||||
* Zones — the local zones this resolver answers for itself, and the
|
||||
* delegations that hand a name to another server.
|
||||
*
|
||||
* The console only ever writes the managed zone directory, so a record from an
|
||||
* operator's master file is shown and left alone: it carries no edit controls,
|
||||
* and the API would refuse it anyway. Everything the page does is a
|
||||
* request-then-refetch, because every write recompiles the zone store and the
|
||||
* serial in the answer is the cheapest way to see that the file moved.
|
||||
* In the cluster a zone is a DNSZone object and the console edits it; a record
|
||||
* from a master file in the resolver's own config is shown and left alone: it
|
||||
* carries no edit controls, and the API would refuse it anyway. Everything the
|
||||
* page does is a request-then-refetch: a write changes the object, the resolver
|
||||
* re-renders the zone, and the serial in the answer is the cheapest way to see
|
||||
* that it moved.
|
||||
*/
|
||||
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import type { ReactNode } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { AlertTriangle, FileCode2, List, Pencil, Plus, Search, Trash2, Waypoints } from 'lucide-react'
|
||||
import { AlertTriangle, List, Pencil, Plus, Search, Trash2, Waypoints } from 'lucide-react'
|
||||
|
||||
import {
|
||||
addZoneDelegation,
|
||||
@@ -122,7 +122,6 @@ const NEW_ZONE = '__new__'
|
||||
export default function Zones() {
|
||||
const { toast } = useToast()
|
||||
const { canWrite } = useWhoami()
|
||||
const navigate = useNavigate()
|
||||
const { data, error, loading, refetch } = useApi(getZones, [])
|
||||
|
||||
const [origin, setOrigin] = useState('')
|
||||
@@ -329,7 +328,7 @@ export default function Zones() {
|
||||
const noWriteReason = !canWrite
|
||||
? 'This API key is read-only'
|
||||
: !writable
|
||||
? 'No writable managed zone directory'
|
||||
? 'Zones are edited from the console, not from the resolver itself'
|
||||
: undefined
|
||||
|
||||
const recordColumns = [
|
||||
@@ -383,7 +382,7 @@ export default function Zones() {
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<span className="text-[10px] text-fg-subtle" title="This record is in a master file, not the managed directory">
|
||||
<span className="text-[10px] text-fg-subtle" title="This record is in a master file in the resolver's config, not in a DNSZone object">
|
||||
read-only
|
||||
</span>
|
||||
)}
|
||||
@@ -418,8 +417,8 @@ export default function Zones() {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<PageHeader
|
||||
title="Managed zones"
|
||||
description="Manage records and delegations for console-owned authoritative zones. Use Zone files to edit master-file data."
|
||||
title="Zones"
|
||||
description="Records and delegations for the zones this resolver answers for itself."
|
||||
actions={
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button
|
||||
@@ -442,18 +441,11 @@ export default function Zones() {
|
||||
}
|
||||
/>
|
||||
|
||||
{data !== null && data.managed_dir === '' ? (
|
||||
{data !== null && !data.writable ? (
|
||||
<Notice tone="info" title="Zones are read-only here">
|
||||
Set <code className="font-mono">blocklist.managed_zones</code> to a writable directory and
|
||||
this page can create zones, add records and delegate names. The zones below are
|
||||
compiled from <code className="font-mono">blocklist.local_zones</code> and are not
|
||||
rewritten by the console.
|
||||
</Notice>
|
||||
) : null}
|
||||
{data !== null && data.managed_dir !== '' && !data.writable ? (
|
||||
<Notice tone="warn" title="The managed zone directory is not writable">
|
||||
<code className="font-mono">{data.managed_dir}</code> exists but the daemon cannot
|
||||
write to it, so the console is read-only. The zone files themselves are fine.
|
||||
This is the resolver's own view. Zones from master files named in{' '}
|
||||
<code className="font-mono">blocklist.local_zones</code> are edited on disk; zones kept as
|
||||
DNSZone objects are edited from the console.
|
||||
</Notice>
|
||||
) : null}
|
||||
{data !== null && data.error !== '' ? (
|
||||
@@ -467,9 +459,9 @@ export default function Zones() {
|
||||
<EmptyState
|
||||
title="No local zones"
|
||||
description={
|
||||
data?.managed_dir === ''
|
||||
? 'Add a zone with blocklist.local_zones, or set blocklist.managed_zones and create one from here.'
|
||||
: 'Create one from here: adding a record to a zone that does not exist yet starts it.'
|
||||
writable
|
||||
? 'Create one from here: adding a record to a zone that does not exist yet starts it.'
|
||||
: 'Add a master file to blocklist.local_zones, or create a zone from the console.'
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
@@ -492,23 +484,13 @@ export default function Zones() {
|
||||
</p>
|
||||
<p className="text-fg-subtle">
|
||||
{zone?.managed === true
|
||||
? 'Managed by this console'
|
||||
: `From ${zone?.files.length ?? 0} file${zone?.files.length === 1 ? '' : 's'}`}
|
||||
? 'A DNSZone object'
|
||||
: `From ${zone?.files.length ?? 0} master file${zone?.files.length === 1 ? '' : 's'} (read-only)`}
|
||||
{data !== null ? ` · loaded ${formatRelative(data.built_at)}` : ''}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-end justify-end gap-2">
|
||||
{zone !== null && !zone.managed ? (
|
||||
<Button
|
||||
variant="ghost"
|
||||
icon={FileCode2}
|
||||
onClick={() => navigate('/local-zones')}
|
||||
title="This zone comes from a master file, not the managed directory. Local zones edits that file."
|
||||
>
|
||||
Edit the file
|
||||
</Button>
|
||||
) : null}
|
||||
{zone !== null && zone.managed ? (
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
||||
Reference in new issue
Block a user