json api improv
All checks were successful
Build and Deploy MITM Webserver / build (push) Successful in 9s
All checks were successful
Build and Deploy MITM Webserver / build (push) Successful in 9s
This commit is contained in:
@@ -57,17 +57,13 @@ function buildExprFromValues(values: any): Expr[] {
|
||||
},
|
||||
});
|
||||
} else if (preset === 'tcp') {
|
||||
// meta l4proto tcp can be expressed as a 'match' fallback, but backend handles tcp dicts for ports
|
||||
// include a simple token so renderer can show "tcp"
|
||||
expr.push({ tcp: {} });
|
||||
} else if (preset === 'udp') {
|
||||
expr.push({ udp: {} });
|
||||
}
|
||||
} else {
|
||||
// custom protocol — the UI accepts free text; attempt to produce a match if the user entered "icmp" etc.
|
||||
const custom = (values.protocolCustom || '').trim();
|
||||
if (custom) {
|
||||
// simple heuristics
|
||||
if (/^icmpv6$/i.test(custom)) {
|
||||
expr.push({
|
||||
match: {
|
||||
@@ -89,13 +85,12 @@ function buildExprFromValues(values: any): Expr[] {
|
||||
} else if (/udp/i.test(custom)) {
|
||||
expr.push({ udp: {} });
|
||||
} else {
|
||||
// fallback: include as generic token (string) -- backend may not accept this
|
||||
expr.push(custom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// source/destination addresses (encoded as payload matches)
|
||||
// source/destination addresses
|
||||
if (values.saddr) {
|
||||
expr.push({
|
||||
match: {
|
||||
@@ -115,9 +110,8 @@ function buildExprFromValues(values: any): Expr[] {
|
||||
});
|
||||
}
|
||||
|
||||
// ports for tcp/udp - encode with tcp/udp dicts if provided
|
||||
// ports for tcp/udp
|
||||
if (values.sport) {
|
||||
// heuristics: if protocol preset is udp or custom mentions udp -> use udp
|
||||
const useUdp =
|
||||
values.protocolPreset === 'udp' ||
|
||||
(values.protocolChoice === 'custom' && /(udp)/i.test(values.protocolCustom || ''));
|
||||
@@ -136,32 +130,28 @@ function buildExprFromValues(values: any): Expr[] {
|
||||
expr.push(obj);
|
||||
}
|
||||
|
||||
// advanced free-text: we include as a string token so the backend can either render or reject
|
||||
// advanced free-text (try JSON, otherwise string token)
|
||||
if (values.advanced) {
|
||||
// try to include as raw JSON if looks like JSON, else include as string token
|
||||
const adv = values.advanced.trim();
|
||||
try {
|
||||
const parsed = JSON.parse(adv);
|
||||
// if parsed is an object or array, append it directly
|
||||
expr.push(parsed);
|
||||
} catch {
|
||||
// push as raw string token (backend may fail to render — user can use Raw)
|
||||
expr.push(adv);
|
||||
}
|
||||
}
|
||||
|
||||
// action: drop/accept/reject (we encode as dicts)
|
||||
// action
|
||||
const action = values.action || 'drop';
|
||||
if (action === 'drop') expr.push({ drop: null });
|
||||
else if (action === 'accept') expr.push({ accept: null });
|
||||
else if (action === 'reject') expr.push({ reject: null }); // nft supports 'reject' textual; JSON might differ, backend may reject
|
||||
else if (action === 'reject') expr.push({ reject: null });
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deterministic short textual serializer for expr (for preview)
|
||||
* Mirrors backend's serializer heuristics so preview matches server-side text generation.
|
||||
* Deterministic short textual serializer for expr (preview)
|
||||
*/
|
||||
function textFromExpr(expr: Expr): string {
|
||||
if (expr == null) return '';
|
||||
@@ -312,7 +302,7 @@ export const RuleBuilder: React.FC<RuleBuilderProps> = ({ onCreated }) => {
|
||||
}
|
||||
|
||||
const expr = buildExprFromValues(values);
|
||||
// POST JSON
|
||||
|
||||
Modal.confirm({
|
||||
title: 'Create rule (JSON)',
|
||||
content: (
|
||||
@@ -342,10 +332,34 @@ export const RuleBuilder: React.FC<RuleBuilderProps> = ({ onCreated }) => {
|
||||
if (onCreated) await onCreated();
|
||||
form.resetFields(['advanced']);
|
||||
} else {
|
||||
// server returned 2xx but rc != 0
|
||||
message.error(`Create failed: ${res?.stderr ?? 'unknown error'}`);
|
||||
}
|
||||
} catch (err: any) {
|
||||
message.error(`Create failed: ${err?.message ?? String(err)}`);
|
||||
const resp = err?.response;
|
||||
if (resp && resp.data) {
|
||||
const data = resp.data;
|
||||
if (typeof data === 'object' && (typeof data.rc === 'number' || 'stderr' in data)) {
|
||||
const rc = Number(data.rc ?? -1);
|
||||
const stderr = data.stderr ?? data;
|
||||
if (rc === 0) {
|
||||
message.warn(
|
||||
'Rule appears to have been created, but server returned an error status. Check output for details.',
|
||||
);
|
||||
if (onCreated) await onCreated();
|
||||
form.resetFields(['advanced']);
|
||||
} else {
|
||||
const errMsg = typeof stderr === 'string' ? stderr : JSON.stringify(stderr);
|
||||
message.error(`Create failed: ${errMsg}`);
|
||||
}
|
||||
} else if (resp.data.detail) {
|
||||
message.error(`Create failed: ${resp.data.detail}`);
|
||||
} else {
|
||||
message.error(`Create failed: ${JSON.stringify(resp.data)}`);
|
||||
}
|
||||
} else {
|
||||
message.error(`Create failed: ${err?.message ?? String(err)}`);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -355,6 +369,27 @@ export const RuleBuilder: React.FC<RuleBuilderProps> = ({ onCreated }) => {
|
||||
[form, preview, onCreated],
|
||||
);
|
||||
|
||||
// prepare chain options for currently selected table
|
||||
const chainOptions = useMemo(() => {
|
||||
const ts = form.getFieldValue('tableSelect');
|
||||
if (ts && ts !== '__manual__') {
|
||||
const [f, n] = String(ts).split(':');
|
||||
const tbl = tables.find((t) => t.family === f && t.name === n);
|
||||
if (tbl && tbl.chains.length > 0) {
|
||||
return tbl.chains.map((c) => (
|
||||
<Option key={c.name} value={c.name}>
|
||||
{c.name}
|
||||
</Option>
|
||||
));
|
||||
}
|
||||
}
|
||||
return [
|
||||
<Option key="__manual_chain__" value="__manual_chain__">
|
||||
Manual chain...
|
||||
</Option>,
|
||||
];
|
||||
}, [form, tables]);
|
||||
|
||||
return (
|
||||
<Card style={{ maxWidth: 980 }}>
|
||||
<Title level={4}>Add Firewall Rule (JSON)</Title>
|
||||
@@ -414,27 +449,7 @@ export const RuleBuilder: React.FC<RuleBuilderProps> = ({ onCreated }) => {
|
||||
<Row gutter={16}>
|
||||
<Col xs={24} sm={12}>
|
||||
<Form.Item name="chainSelect" label="Chain (select)">
|
||||
<Select>
|
||||
{(() => {
|
||||
const ts = form.getFieldValue('tableSelect');
|
||||
if (ts && ts !== '__manual__') {
|
||||
const [f, n] = String(ts).split(':');
|
||||
const tbl = tables.find((t) => t.family === f && t.name === n);
|
||||
if (tbl && tbl.chains.length > 0) {
|
||||
return tbl.chains.map((c) => (
|
||||
<Option key={c.name} value={c.name}>
|
||||
{c.name}
|
||||
</Option>
|
||||
));
|
||||
}
|
||||
}
|
||||
return [
|
||||
<Option key="__manual_chain__" value="__manual_chain__">
|
||||
Manual chain...
|
||||
</Option>,
|
||||
];
|
||||
})()}
|
||||
</Select>
|
||||
<Select>{chainOptions}</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user