diff --git a/frontend/src/components/FirewallRuleBuilder.tsx b/frontend/src/components/FirewallRuleBuilder.tsx index f586297..2896a41 100644 --- a/frontend/src/components/FirewallRuleBuilder.tsx +++ b/frontend/src/components/FirewallRuleBuilder.tsx @@ -29,14 +29,11 @@ interface RuleBuilderProps { onCreated?: () => Promise | void; } -/** - * Helper: create an nft JSON 'expr' array from form values. - * The produced expr is intentionally conservative and designed to be renderable by the backend's renderer. - */ +/* ---------- helpers ---------- */ + function buildExprFromValues(values: any): Expr[] { const expr: Expr[] = []; - // protocol const protoChoice = values.protocolChoice ?? 'preset'; if (protoChoice === 'preset') { const preset = values.protocolPreset; @@ -90,7 +87,6 @@ function buildExprFromValues(values: any): Expr[] { } } - // source/destination addresses if (values.saddr) { expr.push({ match: { @@ -110,7 +106,6 @@ function buildExprFromValues(values: any): Expr[] { }); } - // ports for tcp/udp if (values.sport) { const useUdp = values.protocolPreset === 'udp' || @@ -130,7 +125,6 @@ function buildExprFromValues(values: any): Expr[] { expr.push(obj); } - // advanced free-text (try JSON, otherwise string token) if (values.advanced) { const adv = values.advanced.trim(); try { @@ -141,7 +135,6 @@ function buildExprFromValues(values: any): Expr[] { } } - // action const action = values.action || 'drop'; if (action === 'drop') expr.push({ drop: null }); else if (action === 'accept') expr.push({ accept: null }); @@ -150,9 +143,6 @@ function buildExprFromValues(values: any): Expr[] { return expr; } -/** - * Deterministic short textual serializer for expr (preview) - */ function textFromExpr(expr: Expr): string { if (expr == null) return ''; if (Array.isArray(expr)) { @@ -211,12 +201,15 @@ function textFromExpr(expr: Expr): string { return String(expr); } +/* ---------- component ---------- */ + export const RuleBuilder: React.FC = ({ onCreated }) => { const [loading, setLoading] = useState(false); const [tables, setTables] = useState([]); const [form] = Form.useForm(); - const [preview, setPreview] = useState(''); - const [exprPreview, setExprPreview] = useState(null); + + const [cmdPreview, setCmdPreview] = useState(''); + const [requestJsonPreview, setRequestJsonPreview] = useState(null); // fetch tables/chains on mount useEffect(() => { @@ -269,8 +262,35 @@ export const RuleBuilder: React.FC = ({ onCreated }) => { const updatePreviews = useCallback(() => { const vals = form.getFieldsValue(); const expr = buildExprFromValues(vals); - setExprPreview(expr); - setPreview(textFromExpr(expr)); + + // compute family/table/chain choices for cmd + request JSON + let family = 'inet'; + let tableName = 'filter'; + let chain = 'input'; + + const tableSelect = vals.tableSelect; + if (tableSelect && tableSelect !== '__manual__') { + const [f, n] = String(tableSelect).split(':'); + family = f || family; + tableName = n || tableName; + } else { + family = vals.familyManual || family; + tableName = vals.tableManual || tableName; + } + + const chainVal = vals.chainSelect; + if (!chainVal || chainVal === '__manual_chain__') { + chain = vals.chainManual || chain; + } else { + chain = chainVal; + } + + const short = textFromExpr(expr); + const cmd = `add rule ${family} ${tableName} ${chain} ${short}`.trim(); + setCmdPreview(cmd); + + const reqObj = { family, table: tableName, chain, expr }; + setRequestJsonPreview(reqObj); }, [form]); useEffect(() => { @@ -302,6 +322,8 @@ export const RuleBuilder: React.FC = ({ onCreated }) => { } const expr = buildExprFromValues(values); + const cmd = `add rule ${family} ${tableName} ${chain} ${textFromExpr(expr)}`.trim(); + const reqObj = { family, table: tableName, chain, expr }; Modal.confirm({ title: 'Create rule (JSON)', @@ -314,25 +336,23 @@ export const RuleBuilder: React.FC = ({ onCreated }) => { - Preview: -
{preview || '(no textual preview available)'}
+ Generated nft command: +
{cmd || '(no textual preview available)'}
- Expression (JSON): -
{JSON.stringify(expr, null, 2)}
+ Full JSON NFT request: +
{JSON.stringify(reqObj, null, 2)}
), okText: 'Create', onOk: async () => { setLoading(true); try { - const req = { family, table: tableName, chain, expr }; - const res = await createRuleJson(req); + const res = await createRuleJson(reqObj); if (res && res.rc === 0) { message.success('Rule created'); 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) { @@ -366,7 +386,7 @@ export const RuleBuilder: React.FC = ({ onCreated }) => { }, }); }, - [form, preview, onCreated], + [form, onCreated], ); // prepare chain options for currently selected table @@ -412,6 +432,7 @@ export const RuleBuilder: React.FC = ({ onCreated }) => { onFinish={handleCreate} onValuesChange={onValuesChange} > + {/* Table selection */} @@ -446,6 +467,7 @@ export const RuleBuilder: React.FC = ({ onCreated }) => { + {/* Chain selection */} @@ -467,6 +489,7 @@ export const RuleBuilder: React.FC = ({ onCreated }) => { + {/* Protocol / addresses */} @@ -510,6 +533,7 @@ export const RuleBuilder: React.FC = ({ onCreated }) => { + {/* Ports / action */} @@ -532,6 +556,7 @@ export const RuleBuilder: React.FC = ({ onCreated }) => { + {/* Advanced */} @@ -551,22 +576,28 @@ export const RuleBuilder: React.FC = ({ onCreated }) => {
- Short preview: + nft command preview:
- - {preview || '(no textual preview available)'} + + {cmdPreview || '(no textual preview available)'}
- +