improv FE
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:
@@ -29,14 +29,11 @@ interface RuleBuilderProps {
|
|||||||
onCreated?: () => Promise<void> | void;
|
onCreated?: () => Promise<void> | void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/* ---------- helpers ---------- */
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
function buildExprFromValues(values: any): Expr[] {
|
function buildExprFromValues(values: any): Expr[] {
|
||||||
const expr: Expr[] = [];
|
const expr: Expr[] = [];
|
||||||
|
|
||||||
// protocol
|
|
||||||
const protoChoice = values.protocolChoice ?? 'preset';
|
const protoChoice = values.protocolChoice ?? 'preset';
|
||||||
if (protoChoice === 'preset') {
|
if (protoChoice === 'preset') {
|
||||||
const preset = values.protocolPreset;
|
const preset = values.protocolPreset;
|
||||||
@@ -90,7 +87,6 @@ function buildExprFromValues(values: any): Expr[] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// source/destination addresses
|
|
||||||
if (values.saddr) {
|
if (values.saddr) {
|
||||||
expr.push({
|
expr.push({
|
||||||
match: {
|
match: {
|
||||||
@@ -110,7 +106,6 @@ function buildExprFromValues(values: any): Expr[] {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ports for tcp/udp
|
|
||||||
if (values.sport) {
|
if (values.sport) {
|
||||||
const useUdp =
|
const useUdp =
|
||||||
values.protocolPreset === 'udp' ||
|
values.protocolPreset === 'udp' ||
|
||||||
@@ -130,7 +125,6 @@ function buildExprFromValues(values: any): Expr[] {
|
|||||||
expr.push(obj);
|
expr.push(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
// advanced free-text (try JSON, otherwise string token)
|
|
||||||
if (values.advanced) {
|
if (values.advanced) {
|
||||||
const adv = values.advanced.trim();
|
const adv = values.advanced.trim();
|
||||||
try {
|
try {
|
||||||
@@ -141,7 +135,6 @@ function buildExprFromValues(values: any): Expr[] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// action
|
|
||||||
const action = values.action || 'drop';
|
const action = values.action || 'drop';
|
||||||
if (action === 'drop') expr.push({ drop: null });
|
if (action === 'drop') expr.push({ drop: null });
|
||||||
else if (action === 'accept') expr.push({ accept: null });
|
else if (action === 'accept') expr.push({ accept: null });
|
||||||
@@ -150,9 +143,6 @@ function buildExprFromValues(values: any): Expr[] {
|
|||||||
return expr;
|
return expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Deterministic short textual serializer for expr (preview)
|
|
||||||
*/
|
|
||||||
function textFromExpr(expr: Expr): string {
|
function textFromExpr(expr: Expr): string {
|
||||||
if (expr == null) return '';
|
if (expr == null) return '';
|
||||||
if (Array.isArray(expr)) {
|
if (Array.isArray(expr)) {
|
||||||
@@ -211,12 +201,15 @@ function textFromExpr(expr: Expr): string {
|
|||||||
return String(expr);
|
return String(expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------- component ---------- */
|
||||||
|
|
||||||
export const RuleBuilder: React.FC<RuleBuilderProps> = ({ onCreated }) => {
|
export const RuleBuilder: React.FC<RuleBuilderProps> = ({ onCreated }) => {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [tables, setTables] = useState<TableOut[]>([]);
|
const [tables, setTables] = useState<TableOut[]>([]);
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [preview, setPreview] = useState('');
|
|
||||||
const [exprPreview, setExprPreview] = useState<Expr | null>(null);
|
const [cmdPreview, setCmdPreview] = useState<string>('');
|
||||||
|
const [requestJsonPreview, setRequestJsonPreview] = useState<any>(null);
|
||||||
|
|
||||||
// fetch tables/chains on mount
|
// fetch tables/chains on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -269,8 +262,35 @@ export const RuleBuilder: React.FC<RuleBuilderProps> = ({ onCreated }) => {
|
|||||||
const updatePreviews = useCallback(() => {
|
const updatePreviews = useCallback(() => {
|
||||||
const vals = form.getFieldsValue();
|
const vals = form.getFieldsValue();
|
||||||
const expr = buildExprFromValues(vals);
|
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]);
|
}, [form]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -302,6 +322,8 @@ export const RuleBuilder: React.FC<RuleBuilderProps> = ({ onCreated }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const expr = buildExprFromValues(values);
|
const expr = buildExprFromValues(values);
|
||||||
|
const cmd = `add rule ${family} ${tableName} ${chain} ${textFromExpr(expr)}`.trim();
|
||||||
|
const reqObj = { family, table: tableName, chain, expr };
|
||||||
|
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: 'Create rule (JSON)',
|
title: 'Create rule (JSON)',
|
||||||
@@ -314,25 +336,23 @@ export const RuleBuilder: React.FC<RuleBuilderProps> = ({ onCreated }) => {
|
|||||||
</b>
|
</b>
|
||||||
</Text>
|
</Text>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Text strong>Preview:</Text>
|
<Text strong>Generated nft command:</Text>
|
||||||
<pre style={{ whiteSpace: 'pre-wrap', marginTop: 8 }}>{preview || '(no textual preview available)'}</pre>
|
<pre style={{ whiteSpace: 'pre-wrap', marginTop: 8 }}>{cmd || '(no textual preview available)'}</pre>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Text strong>Expression (JSON):</Text>
|
<Text strong>Full JSON NFT request:</Text>
|
||||||
<pre style={{ whiteSpace: 'pre-wrap', marginTop: 8 }}>{JSON.stringify(expr, null, 2)}</pre>
|
<pre style={{ whiteSpace: 'pre-wrap', marginTop: 8 }}>{JSON.stringify(reqObj, null, 2)}</pre>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
okText: 'Create',
|
okText: 'Create',
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const req = { family, table: tableName, chain, expr };
|
const res = await createRuleJson(reqObj);
|
||||||
const res = await createRuleJson(req);
|
|
||||||
if (res && res.rc === 0) {
|
if (res && res.rc === 0) {
|
||||||
message.success('Rule created');
|
message.success('Rule created');
|
||||||
if (onCreated) await onCreated();
|
if (onCreated) await onCreated();
|
||||||
form.resetFields(['advanced']);
|
form.resetFields(['advanced']);
|
||||||
} else {
|
} else {
|
||||||
// server returned 2xx but rc != 0
|
|
||||||
message.error(`Create failed: ${res?.stderr ?? 'unknown error'}`);
|
message.error(`Create failed: ${res?.stderr ?? 'unknown error'}`);
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
@@ -366,7 +386,7 @@ export const RuleBuilder: React.FC<RuleBuilderProps> = ({ onCreated }) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[form, preview, onCreated],
|
[form, onCreated],
|
||||||
);
|
);
|
||||||
|
|
||||||
// prepare chain options for currently selected table
|
// prepare chain options for currently selected table
|
||||||
@@ -412,6 +432,7 @@ export const RuleBuilder: React.FC<RuleBuilderProps> = ({ onCreated }) => {
|
|||||||
onFinish={handleCreate}
|
onFinish={handleCreate}
|
||||||
onValuesChange={onValuesChange}
|
onValuesChange={onValuesChange}
|
||||||
>
|
>
|
||||||
|
{/* Table selection */}
|
||||||
<Row gutter={16}>
|
<Row gutter={16}>
|
||||||
<Col xs={24} sm={12}>
|
<Col xs={24} sm={12}>
|
||||||
<Form.Item name="tableSelect" label="Table (family:name)" rules={[{ required: true }]}>
|
<Form.Item name="tableSelect" label="Table (family:name)" rules={[{ required: true }]}>
|
||||||
@@ -446,6 +467,7 @@ export const RuleBuilder: React.FC<RuleBuilderProps> = ({ onCreated }) => {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
|
{/* Chain selection */}
|
||||||
<Row gutter={16}>
|
<Row gutter={16}>
|
||||||
<Col xs={24} sm={12}>
|
<Col xs={24} sm={12}>
|
||||||
<Form.Item name="chainSelect" label="Chain (select)">
|
<Form.Item name="chainSelect" label="Chain (select)">
|
||||||
@@ -467,6 +489,7 @@ export const RuleBuilder: React.FC<RuleBuilderProps> = ({ onCreated }) => {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
|
{/* Protocol / addresses */}
|
||||||
<Row gutter={16}>
|
<Row gutter={16}>
|
||||||
<Col xs={24} sm={8}>
|
<Col xs={24} sm={8}>
|
||||||
<Form.Item label="Protocol selection" style={{ marginBottom: 0 }}>
|
<Form.Item label="Protocol selection" style={{ marginBottom: 0 }}>
|
||||||
@@ -510,6 +533,7 @@ export const RuleBuilder: React.FC<RuleBuilderProps> = ({ onCreated }) => {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
|
{/* Ports / action */}
|
||||||
<Row gutter={16}>
|
<Row gutter={16}>
|
||||||
<Col xs={24} sm={6}>
|
<Col xs={24} sm={6}>
|
||||||
<Form.Item name="sport" label="Source Port">
|
<Form.Item name="sport" label="Source Port">
|
||||||
@@ -532,6 +556,7 @@ export const RuleBuilder: React.FC<RuleBuilderProps> = ({ onCreated }) => {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
|
{/* Advanced */}
|
||||||
<Row>
|
<Row>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<Form.Item name="advanced" label="Advanced (optional)">
|
<Form.Item name="advanced" label="Advanced (optional)">
|
||||||
@@ -551,22 +576,28 @@ export const RuleBuilder: React.FC<RuleBuilderProps> = ({ onCreated }) => {
|
|||||||
<Form.Item label="Preview">
|
<Form.Item label="Preview">
|
||||||
<Space style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
<Space style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
<div style={{ flex: 1, marginRight: 12 }}>
|
<div style={{ flex: 1, marginRight: 12 }}>
|
||||||
<Text strong>Short preview:</Text>
|
<Text strong>nft command preview:</Text>
|
||||||
<div style={{ marginTop: 6 }}>
|
<div style={{ marginTop: 6 }}>
|
||||||
<Text code copyable={{ text: preview || '' }}>
|
<Text code copyable={{ text: cmdPreview || '' }}>
|
||||||
{preview || '(no textual preview available)'}
|
{cmdPreview || '(no textual preview available)'}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ width: 320 }}>
|
<div style={{ width: 320 }}>
|
||||||
<Tooltip title="Copy JSON expr">
|
<Tooltip title="Click to copy expr JSON. Shift+click to copy full request JSON.">
|
||||||
<Button
|
<Button
|
||||||
icon={<CopyOutlined />}
|
icon={<CopyOutlined />}
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => {
|
onClick={(e) => {
|
||||||
if (!exprPreview) return;
|
// shiftKey => copy full request JSON
|
||||||
navigator.clipboard?.writeText(JSON.stringify(exprPreview, null, 2));
|
if (e.shiftKey && requestJsonPreview) {
|
||||||
|
navigator.clipboard?.writeText(JSON.stringify(requestJsonPreview, null, 2));
|
||||||
|
message.success('Full request JSON copied');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!cmdPreview) return;
|
||||||
|
navigator.clipboard?.writeText(JSON.stringify(cmdPreview, null, 2));
|
||||||
message.success('Expression JSON copied');
|
message.success('Expression JSON copied');
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -580,9 +611,9 @@ export const RuleBuilder: React.FC<RuleBuilderProps> = ({ onCreated }) => {
|
|||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
<Text strong>Expression (JSON):</Text>
|
<Text strong>Full JSON NFT request:</Text>
|
||||||
<pre style={{ whiteSpace: 'pre-wrap', background: '#f6f6f6', padding: 12 }}>
|
<pre style={{ whiteSpace: 'pre-wrap', background: '#f6f6f6', padding: 12 }}>
|
||||||
{JSON.stringify(exprPreview, null, 2)}
|
{JSON.stringify(requestJsonPreview, null, 2)}
|
||||||
</pre>
|
</pre>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user