feat: add custom IP/CIDR input option to policy source and destination fields with dynamic text input and placeholder examples
Add customTargetValue constant for custom IP/CIDR selection, implement endpointSelectValue helper to detect custom values not in target list, add isCustomEndpoint predicate to show/hide custom input fields, extend PolicyDesigner with "Custom IP/CIDR" dropdown option that reveals text input for manual IP/CIDR entry with trim on change, add placeholder examples "172.16.0.50 or 172.16.0.0/16" to guide user
This commit is contained in:
@@ -216,8 +216,8 @@ export function Policies() {
|
||||
<Field label="Project"><select className={selectClass} value={form.project_id} onChange={(event) => setForm({ ...form, project_id: event.target.value })}><option value="">Global</option>{(projects.data ?? []).map((project) => <option key={project.id} value={project.id}>{project.name}</option>)}</select></Field>
|
||||
<Field label="Name"><input className={inputClass} value={form.name} onChange={(event) => setForm({ ...form, name: event.target.value })} /></Field>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<Field label="Source"><input className={inputClass} value={form.source} onChange={(event) => setForm({ ...form, source: event.target.value })} /></Field>
|
||||
<Field label="Destination"><input className={inputClass} value={form.destination} onChange={(event) => setForm({ ...form, destination: event.target.value })} /></Field>
|
||||
<Field label="Source"><input className={inputClass} value={form.source} onChange={(event) => setForm({ ...form, source: event.target.value })} placeholder="any, workload:<id>, 172.16.0.50 or 172.16.0.0/16" /></Field>
|
||||
<Field label="Destination"><input className={inputClass} value={form.destination} onChange={(event) => setForm({ ...form, destination: event.target.value })} placeholder="any, workload:<id>, 172.16.0.53 or 172.16.10.0/24" /></Field>
|
||||
</div>
|
||||
<Field label="Service"><select className={selectClass} value={form.service_id} onChange={(event) => chooseService(event.target.value)}><option value="">Custom</option>{(services.data ?? []).map((service) => <option key={service.id} value={service.id}>{service.name} {service.ports}</option>)}</select></Field>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
|
||||
@@ -11,6 +11,16 @@ type TargetOption = {
|
||||
value: string;
|
||||
};
|
||||
|
||||
const customTargetValue = "__custom_ip_cidr__";
|
||||
|
||||
function endpointSelectValue(value: string, targets: TargetOption[]) {
|
||||
return targets.some((target) => target.value === value) ? value : customTargetValue;
|
||||
}
|
||||
|
||||
function isCustomEndpoint(value: string, targets: TargetOption[]) {
|
||||
return endpointSelectValue(value, targets) === customTargetValue;
|
||||
}
|
||||
|
||||
export function PolicyDesigner() {
|
||||
const queryClient = useQueryClient();
|
||||
const workloads = useQuery({ queryKey: ["workloads"], queryFn: () => api<Workload[]>("/vms") });
|
||||
@@ -35,6 +45,7 @@ export function PolicyDesigner() {
|
||||
const targets = useMemo<TargetOption[]>(() => {
|
||||
return [
|
||||
{ label: "Any", value: "any" },
|
||||
{ label: "Custom IP/CIDR", value: customTargetValue },
|
||||
...(workloads.data ?? []).map((workload) => ({ label: `VM/LXC: ${workload.name}`, value: `workload:${workload.id}` })),
|
||||
...(securityGroups.data ?? []).map((group) => ({ label: `Security Group: ${group.name}`, value: `sg:${group.name}` })),
|
||||
...(networks.data ?? []).map((network) => ({ label: `Network: ${network.name}`, value: `network:${network.name}` })),
|
||||
@@ -115,14 +126,40 @@ export function PolicyDesigner() {
|
||||
</Field>
|
||||
<div />
|
||||
<Field label="Source">
|
||||
<select className={selectClass} value={form.source} onChange={(event) => setForm({ ...form, source: event.target.value })}>
|
||||
<select
|
||||
className={selectClass}
|
||||
value={endpointSelectValue(form.source, targets)}
|
||||
onChange={(event) => setForm({ ...form, source: event.target.value === customTargetValue ? "" : event.target.value })}
|
||||
>
|
||||
{targets.map((target) => <option key={target.value} value={target.value}>{target.label}</option>)}
|
||||
</select>
|
||||
{isCustomEndpoint(form.source, targets) ? (
|
||||
<input
|
||||
className={`${inputClass} mt-2`}
|
||||
value={form.source}
|
||||
onChange={(event) => setForm({ ...form, source: event.target.value.trim() })}
|
||||
placeholder="172.16.0.50 or 172.16.0.0/16"
|
||||
required
|
||||
/>
|
||||
) : null}
|
||||
</Field>
|
||||
<Field label="Destination">
|
||||
<select className={selectClass} value={form.destination} onChange={(event) => setForm({ ...form, destination: event.target.value })}>
|
||||
<select
|
||||
className={selectClass}
|
||||
value={endpointSelectValue(form.destination, targets)}
|
||||
onChange={(event) => setForm({ ...form, destination: event.target.value === customTargetValue ? "" : event.target.value })}
|
||||
>
|
||||
{targets.map((target) => <option key={target.value} value={target.value}>{target.label}</option>)}
|
||||
</select>
|
||||
{isCustomEndpoint(form.destination, targets) ? (
|
||||
<input
|
||||
className={`${inputClass} mt-2`}
|
||||
value={form.destination}
|
||||
onChange={(event) => setForm({ ...form, destination: event.target.value.trim() })}
|
||||
placeholder="172.16.0.53 or 172.16.10.0/24"
|
||||
required
|
||||
/>
|
||||
) : null}
|
||||
</Field>
|
||||
<Field label="Service">
|
||||
<select className={selectClass} value={form.service_id} onChange={(event) => chooseService(event.target.value)}>
|
||||
|
||||
Reference in New Issue
Block a user