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"
|
usersvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "1.1.138"
|
var version = "1.1.139"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
addr := os.Getenv("EDGEGUARD_API_ADDR")
|
addr := os.Getenv("EDGEGUARD_API_ADDR")
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/setup"
|
"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
|
const usage = `edgeguard-ctl — EdgeGuard CLI
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ import (
|
|||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/tlscerts"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/tlscerts"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "1.1.138"
|
var version = "1.1.139"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// renewTickInterval — how often we re-evaluate expiring certs.
|
// renewTickInterval — how often we re-evaluate expiring certs.
|
||||||
|
|||||||
@@ -109,6 +109,9 @@
|
|||||||
"cidr": "CIDR",
|
"cidr": "CIDR",
|
||||||
"actions": { "accept": "ACCEPT", "drop": "DROP", "reject": "REJECT" },
|
"actions": { "accept": "ACCEPT", "drop": "DROP", "reject": "REJECT" },
|
||||||
"kinds": { "any": "Beliebig", "object": "Objekt", "group": "Gruppe", "cidr": "CIDR" },
|
"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)",
|
"moveUp": "Nach oben (höhere Priorität)",
|
||||||
"moveDown": "Nach unten (niedrigere Priorität)",
|
"moveDown": "Nach unten (niedrigere Priorität)",
|
||||||
"emptyTitle": "Noch keine eigenen Firewall-Regeln.",
|
"emptyTitle": "Noch keine eigenen Firewall-Regeln.",
|
||||||
|
|||||||
@@ -109,6 +109,9 @@
|
|||||||
"cidr": "CIDR",
|
"cidr": "CIDR",
|
||||||
"actions": { "accept": "ACCEPT", "drop": "DROP", "reject": "REJECT" },
|
"actions": { "accept": "ACCEPT", "drop": "DROP", "reject": "REJECT" },
|
||||||
"kinds": { "any": "Any", "object": "Object", "group": "Group", "cidr": "CIDR" },
|
"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)",
|
"moveUp": "Move up (higher priority)",
|
||||||
"moveDown": "Move down (lower priority)",
|
"moveDown": "Move down (lower priority)",
|
||||||
"emptyTitle": "No custom firewall rules yet.",
|
"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 { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {
|
import {
|
||||||
ArrowDownOutlined, ArrowUpOutlined, EyeOutlined, FireOutlined, PlusOutlined,
|
ArrowDownOutlined, ArrowUpOutlined, CopyOutlined, EyeOutlined, FireOutlined, PlusOutlined,
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons'
|
||||||
|
|
||||||
const { Text } = Typography
|
const { Text } = Typography
|
||||||
@@ -233,6 +233,23 @@ export default function RulesTab() {
|
|||||||
onError: (e: Error) => message.error(e.message),
|
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) => {
|
const editFromRow = (r: FwRule) => {
|
||||||
setEditing(r)
|
setEditing(r)
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
@@ -376,7 +393,7 @@ export default function RulesTab() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '', key: 'actions', width: 100,
|
title: '', key: 'actions', width: 120,
|
||||||
render: (_, row) => (
|
render: (_, row) => (
|
||||||
<Space size={4}>
|
<Space size={4}>
|
||||||
<Tooltip title={isViewer ? t('auth.viewerBadge') : t('common.edit')}>
|
<Tooltip title={isViewer ? t('auth.viewerBadge') : t('common.edit')}>
|
||||||
@@ -384,6 +401,15 @@ export default function RulesTab() {
|
|||||||
{t('common.edit')}
|
{t('common.edit')}
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</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 ? (
|
{isViewer ? (
|
||||||
<Tooltip title={t('auth.viewerBadge')}>
|
<Tooltip title={t('auth.viewerBadge')}>
|
||||||
<Button size="small" danger disabled>{t('common.delete')}</Button>
|
<Button size="small" danger disabled>{t('common.delete')}</Button>
|
||||||
@@ -536,7 +562,14 @@ export default function RulesTab() {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
}
|
}
|
||||||
if (k === 'cidr') {
|
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 }} />
|
<Input placeholder="10.0.0.0/24" style={{ width: 200 }} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user