feat(firewall): Regel duplizieren + CIDR-Validierung (v1.1.139)
- Duplicate-Button (CopyOutlined): klont Regel mit priority+1, disabled=true, Name "(copy)" - CIDR-Felder: Pattern-Validator für IPv4/IPv6-CIDR-Notation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -60,7 +60,7 @@ import (
|
||||
usersvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/users"
|
||||
)
|
||||
|
||||
var version = "1.1.138"
|
||||
var version = "1.1.139"
|
||||
|
||||
func main() {
|
||||
addr := os.Getenv("EDGEGUARD_API_ADDR")
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/setup"
|
||||
)
|
||||
|
||||
var version = "1.1.138"
|
||||
var version = "1.1.139"
|
||||
|
||||
const usage = `edgeguard-ctl — EdgeGuard CLI
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ import (
|
||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/tlscerts"
|
||||
)
|
||||
|
||||
var version = "1.1.138"
|
||||
var version = "1.1.139"
|
||||
|
||||
const (
|
||||
// renewTickInterval — how often we re-evaluate expiring certs.
|
||||
|
||||
@@ -109,6 +109,9 @@
|
||||
"cidr": "CIDR",
|
||||
"actions": { "accept": "ACCEPT", "drop": "DROP", "reject": "REJECT" },
|
||||
"kinds": { "any": "Beliebig", "object": "Objekt", "group": "Gruppe", "cidr": "CIDR" },
|
||||
"duplicate": "Regel duplizieren",
|
||||
"duplicated": "Regel dupliziert (deaktiviert, Priorität +1).",
|
||||
"cidrInvalid": "Gültiges CIDR eingeben (z.B. 10.0.0.0/24)",
|
||||
"moveUp": "Nach oben (höhere Priorität)",
|
||||
"moveDown": "Nach unten (niedrigere Priorität)",
|
||||
"emptyTitle": "Noch keine eigenen Firewall-Regeln.",
|
||||
|
||||
@@ -109,6 +109,9 @@
|
||||
"cidr": "CIDR",
|
||||
"actions": { "accept": "ACCEPT", "drop": "DROP", "reject": "REJECT" },
|
||||
"kinds": { "any": "Any", "object": "Object", "group": "Group", "cidr": "CIDR" },
|
||||
"duplicate": "Duplicate rule",
|
||||
"duplicated": "Rule duplicated (disabled, priority +1).",
|
||||
"cidrInvalid": "Enter a valid CIDR (e.g. 10.0.0.0/24)",
|
||||
"moveUp": "Move up (higher priority)",
|
||||
"moveDown": "Move down (lower priority)",
|
||||
"emptyTitle": "No custom firewall rules yet.",
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { ColumnsType } from 'antd/es/table'
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
ArrowDownOutlined, ArrowUpOutlined, EyeOutlined, FireOutlined, PlusOutlined,
|
||||
ArrowDownOutlined, ArrowUpOutlined, CopyOutlined, EyeOutlined, FireOutlined, PlusOutlined,
|
||||
} from '@ant-design/icons'
|
||||
|
||||
const { Text } = Typography
|
||||
@@ -233,6 +233,23 @@ export default function RulesTab() {
|
||||
onError: (e: Error) => message.error(e.message),
|
||||
})
|
||||
|
||||
const duplicate = useMutation({
|
||||
mutationFn: async (r: FwRule) => {
|
||||
const { id: _id, created_at: _ca, updated_at: _ua, ...rest } = r as FwRule & { created_at?: unknown; updated_at?: unknown }
|
||||
await apiClient.post('/firewall/rules', {
|
||||
...rest,
|
||||
name: r.name ? `${r.name} (copy)` : undefined,
|
||||
priority: r.priority + 1,
|
||||
enabled: false,
|
||||
})
|
||||
},
|
||||
onSuccess: () => {
|
||||
message.success(t('fw.rule.duplicated'))
|
||||
void qc.invalidateQueries({ queryKey: ['fw', 'rules'] })
|
||||
},
|
||||
onError: (e: Error) => message.error(e.message),
|
||||
})
|
||||
|
||||
const editFromRow = (r: FwRule) => {
|
||||
setEditing(r)
|
||||
form.setFieldsValue({
|
||||
@@ -376,7 +393,7 @@ export default function RulesTab() {
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '', key: 'actions', width: 100,
|
||||
title: '', key: 'actions', width: 120,
|
||||
render: (_, row) => (
|
||||
<Space size={4}>
|
||||
<Tooltip title={isViewer ? t('auth.viewerBadge') : t('common.edit')}>
|
||||
@@ -384,6 +401,15 @@ export default function RulesTab() {
|
||||
{t('common.edit')}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title={isViewer ? t('auth.viewerBadge') : t('fw.rule.duplicate')}>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<CopyOutlined />}
|
||||
disabled={isViewer}
|
||||
loading={duplicate.isPending && duplicate.variables?.id === row.id}
|
||||
onClick={() => duplicate.mutate(row)}
|
||||
/>
|
||||
</Tooltip>
|
||||
{isViewer ? (
|
||||
<Tooltip title={t('auth.viewerBadge')}>
|
||||
<Button size="small" danger disabled>{t('common.delete')}</Button>
|
||||
@@ -536,7 +562,14 @@ export default function RulesTab() {
|
||||
</Form.Item>
|
||||
}
|
||||
if (k === 'cidr') {
|
||||
return <Form.Item label={t('fw.rule.kinds.cidr')} name={`${side}_cidr`} rules={[{ required: true }]}>
|
||||
return <Form.Item
|
||||
label={t('fw.rule.kinds.cidr')}
|
||||
name={`${side}_cidr`}
|
||||
rules={[
|
||||
{ required: true },
|
||||
{ pattern: /^(\d{1,3}\.){3}\d{1,3}\/\d{1,2}$|^[0-9a-fA-F:]+\/\d{1,3}$/, message: t('fw.rule.cidrInvalid') },
|
||||
]}
|
||||
>
|
||||
<Input placeholder="10.0.0.0/24" style={{ width: 200 }} />
|
||||
</Form.Item>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user