// ── Admin · Rust · Zone presets (PLAN_REDESIGNS §3.1, D195, D210) ────────── // // Where a zone's ZoneManager flags and settings are chosen. Core's step editor // has single-value fields only, so the org lead put the checkboxes here (D210): // an admin saves a named set for one server, several, or every server, and a // zone step's Options field offers the presets. Picking one COPIES its line into // the step, so editing a preset here changes no published event. // // The flags are whatever each server's ZoneManager declared in its last hello // (D211), grouped by `server/model/zones/zoneOptions.js`. A flag that file does // not know is under Other, never hidden. A flag not on every server a preset // covers is refused on save, with the server named. import { useMemo, useState } from 'react' import { Link } from 'react-router-dom' import { ErrorState, Loading, useAsync } from '../../core.js' import api from '../../api.js' const inputStyle = { background: 'var(--panel-flat, transparent)', color: 'var(--text)', border: '1px solid var(--line)', borderRadius: 6, padding: '6px 8px', font: 'inherit', } function blankForm() { return { id: null, name: '', flags: [], settings: {}, allServers: false, servers: [] } } function formFrom(p) { const settings = {} for (const [k, v] of Object.entries(p.settings || {})) settings[k] = String(v) return { id: p.id, name: p.name, flags: [...p.flags], settings, allServers: p.allServers, servers: [...p.servers] } } /** What the page sends: blank settings left out, the rest as typed (the server checks them). */ function bodyFrom(form) { const settings = {} for (const [k, v] of Object.entries(form.settings)) { if (v !== '' && v !== undefined && v !== null) settings[k] = v } return { name: form.name.trim(), flags: form.flags, settings, allServers: form.allServers, servers: form.allServers ? [] : form.servers } } /** One line on what a server said about its zone mods. */ export function zoneModsLine(s) { const zm = s.zoneManager const zd = s.zoneDomes const parts = [zm ? `ZoneManager ${zm.version || ''} · ${zm.flagCount} flags`.replace(' ', ' ') : 'no ZoneManager reported'] if (!zd || !zd.loaded) parts.push('no ZoneDomes, so no domes') else if (!zd.helper || zd.helper.state === 'missing') parts.push('ZoneDomes without RunicGatewayDomes.cs, so no domes') else if (zd.helper.state !== 'patched') parts.push(`the domes helper could not patch ZoneDomes${zd.helper.reason ? ` (${zd.helper.reason})` : ''}`) else parts.push('domes available') return parts.join(' · ') } export default function ZonePresets() { const [reloads, setReloads] = useState(0) const { data, error: loadError } = useAsync(() => api.adminZones.read(), [reloads]) const [form, setForm] = useState(null) const [busy, setBusy] = useState(false) const [error, setError] = useState('') if (loadError) return if (!data) return const servers = data.servers || [] const nameOf = new Map(servers.map((s) => [s.id, s.name || s.id])) const save = async (e) => { e.preventDefault() setBusy(true) setError('') try { if (form.id) await api.adminZones.update(form.id, bodyFrom(form)) else await api.adminZones.create(bodyFrom(form)) setForm(null) setReloads((n) => n + 1) } catch (err) { setError(err.message || 'That did not save.') } finally { setBusy(false) } } const remove = async (preset) => { setBusy(true) setError('') try { await api.adminZones.remove(preset.id) setForm(null) setReloads((n) => n + 1) } catch (err) { setError(err.message || 'That did not delete.') } finally { setBusy(false) } } return (

Named sets of ZoneManager flags and settings. An event’s Open a zone step offers these in its Options field; picking one copies it into the step, so changing a preset later does not change an event already published. Who is exempt from a flag is a zonemanager.ignoreflag.* permission, set on the{' '} permissions page.

Servers

{servers.length === 0 &&

No servers are configured yet.

} {servers.map((s) => (
{s.name || s.id}{' '} {zoneModsLine(s)}
))}

Presets

{(data.presets || []).length === 0 && (

None yet. None ship with the module: what an arena is differs by server.

)} {(data.presets || []).map((p) => (
{p.name} {p.allServers ? 'every server' : p.servers.map((id) => nameOf.get(id) || id).join(', ')}
{p.line || 'no flags or settings'}
))}
{form && ( setForm(null)} onDelete={form.id ? () => remove(form) : null} /> )}
) } function PresetForm({ form, setForm, data, busy, error, onSave, onCancel, onDelete }) { const servers = data.servers || [] // The flags on EVERY covered server that reported a list: a flag missing from // one of them is shown but marked, and the save refuses it with the server named. const covered = form.allServers ? servers : servers.filter((s) => form.servers.includes(s.id)) const missingOn = useMemo(() => { const out = new Map() for (const s of covered) { const flags = s.zoneManager && s.zoneManager.flags if (!flags || flags.length === 0) continue const have = new Set(flags.map((f) => f.toLowerCase())) for (const g of data.groups || []) { for (const f of g.flags) { if (!have.has(f.name.toLowerCase())) out.set(f.name, [...(out.get(f.name) || []), s.name || s.id]) } } } return out }, [covered, data.groups]) const toggleFlag = (name) => setForm((f) => ({ ...f, flags: f.flags.includes(name) ? f.flags.filter((x) => x !== name) : [...f.flags, name] })) const toggleServer = (id) => setForm((f) => ({ ...f, servers: f.servers.includes(id) ? f.servers.filter((x) => x !== id) : [...f.servers, id] })) const setSetting = (key, value) => setForm((f) => ({ ...f, settings: { ...f.settings, [key]: value } })) return (

{form.id ? `Edit ${form.name}` : 'A new preset'}

For {!form.allServers && servers.map((s) => ( ))}
Flags {(data.groups || []).length === 0 && (

No server has reported its ZoneManager flags yet. They arrive when a server with ZoneManager connects.

)} {(data.groups || []).map((g) => (
{g.group}
{g.flags.map((f) => { const missing = missingOn.get(f.name) return ( ) })}
))} {missingOn.size > 0 && (

* not on every server this preset is for. Hover for which.

)}
Settings {(data.settings || []).map((s) => ( ))}
{error &&

{error}

}
{onDelete && ( )}
) }