fix(firewall): fehlende onError-Handler + i18n in Rules + NATRules
- 6 Mutations (create/update/del je Rules + NATRules) bekommen onError-Handler - Deutsche Placeholder-Texte → t() überführt - Action-Labels (accept/drop/reject) und Kind-Labels (any/object/group/cidr) in t() überführt statt hardkodiert anzeigen - CIDR-Label-Hardkodierung in Rules-Formular behoben - en/de: fw.rule.actions.*, fw.rule.kinds.*, fw.rule.namePlaceholder, fw.nat.namePlaceholder ergänzt 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.82"
|
var version = "1.1.83"
|
||||||
|
|
||||||
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.82"
|
var version = "1.1.83"
|
||||||
|
|
||||||
const usage = `edgeguard-ctl — EdgeGuard CLI
|
const usage = `edgeguard-ctl — EdgeGuard CLI
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,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.82"
|
var version = "1.1.83"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// renewTickInterval — how often we re-evaluate expiring certs.
|
// renewTickInterval — how often we re-evaluate expiring certs.
|
||||||
|
|||||||
@@ -105,6 +105,10 @@
|
|||||||
"hits": "Hits",
|
"hits": "Hits",
|
||||||
"add": "Regel hinzufügen", "edit": "Regel bearbeiten",
|
"add": "Regel hinzufügen", "edit": "Regel bearbeiten",
|
||||||
"deleteConfirm": "Diese Regel wirklich löschen?",
|
"deleteConfirm": "Diese Regel wirklich löschen?",
|
||||||
|
"namePlaceholder": "z.B. SSH von Office erlauben",
|
||||||
|
"cidr": "CIDR",
|
||||||
|
"actions": { "accept": "ACCEPT", "drop": "DROP", "reject": "REJECT" },
|
||||||
|
"kinds": { "any": "Beliebig", "object": "Objekt", "group": "Gruppe", "cidr": "CIDR" },
|
||||||
"emptyTitle": "Noch keine eigenen Firewall-Regeln.",
|
"emptyTitle": "Noch keine eigenen Firewall-Regeln.",
|
||||||
"emptyDesc": "Die System-Regeln oben halten SSH (rate-limited), HTTPS :443 und Mgmt-UI :3443 immer offen (Anti-Lockout). Eigene Regeln für app-spezifische Inbound-Ports oder zonenübergreifende Forwards anlegen."
|
"emptyDesc": "Die System-Regeln oben halten SSH (rate-limited), HTTPS :443 und Mgmt-UI :3443 immer offen (Anti-Lockout). Eigene Regeln für app-spezifische Inbound-Ports oder zonenübergreifende Forwards anlegen."
|
||||||
},
|
},
|
||||||
@@ -119,6 +123,7 @@
|
|||||||
"comment": "Kommentar",
|
"comment": "Kommentar",
|
||||||
"add": "NAT-Regel hinzufügen", "edit": "NAT-Regel bearbeiten",
|
"add": "NAT-Regel hinzufügen", "edit": "NAT-Regel bearbeiten",
|
||||||
"deleteConfirm": "Diese NAT-Regel wirklich löschen?",
|
"deleteConfirm": "Diese NAT-Regel wirklich löschen?",
|
||||||
|
"namePlaceholder": "z.B. HTTP zu Web-Backend weiterleiten",
|
||||||
"emptyTitle": "Noch keine NAT-Regeln.",
|
"emptyTitle": "Noch keine NAT-Regeln.",
|
||||||
"emptyDesc": "DNAT (z. B. extern :2030 → intern 10.10.20.12:22 für SSH zu einem internen Host) oder SNAT/MASQUERADE (Internet-Zugang für ein internes Subnetz über die Box-IP)."
|
"emptyDesc": "DNAT (z. B. extern :2030 → intern 10.10.20.12:22 für SSH zu einem internen Host) oder SNAT/MASQUERADE (Internet-Zugang für ein internes Subnetz über die Box-IP)."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -105,6 +105,10 @@
|
|||||||
"hits": "Hits",
|
"hits": "Hits",
|
||||||
"add": "Add rule", "edit": "Edit rule",
|
"add": "Add rule", "edit": "Edit rule",
|
||||||
"deleteConfirm": "Really delete this rule?",
|
"deleteConfirm": "Really delete this rule?",
|
||||||
|
"namePlaceholder": "e.g. Allow SSH from office",
|
||||||
|
"cidr": "CIDR",
|
||||||
|
"actions": { "accept": "ACCEPT", "drop": "DROP", "reject": "REJECT" },
|
||||||
|
"kinds": { "any": "Any", "object": "Object", "group": "Group", "cidr": "CIDR" },
|
||||||
"emptyTitle": "No custom firewall rules yet.",
|
"emptyTitle": "No custom firewall rules yet.",
|
||||||
"emptyDesc": "The system rules above keep SSH (rate-limited), HTTPS :443 and the mgmt UI :3443 open (anti-lockout). Add custom rules for app-specific inbound ports or cross-zone forwards."
|
"emptyDesc": "The system rules above keep SSH (rate-limited), HTTPS :443 and the mgmt UI :3443 open (anti-lockout). Add custom rules for app-specific inbound ports or cross-zone forwards."
|
||||||
},
|
},
|
||||||
@@ -119,6 +123,7 @@
|
|||||||
"comment": "Comment",
|
"comment": "Comment",
|
||||||
"add": "Add NAT rule", "edit": "Edit NAT rule",
|
"add": "Add NAT rule", "edit": "Edit NAT rule",
|
||||||
"deleteConfirm": "Really delete this NAT rule?",
|
"deleteConfirm": "Really delete this NAT rule?",
|
||||||
|
"namePlaceholder": "e.g. Forward HTTP to web backend",
|
||||||
"emptyTitle": "No NAT rules yet.",
|
"emptyTitle": "No NAT rules yet.",
|
||||||
"emptyDesc": "DNAT (e.g. external :2030 → internal 10.10.20.12:22 for SSH to an internal host) or SNAT/MASQUERADE (internet access for an internal subnet via the box IP)."
|
"emptyDesc": "DNAT (e.g. external :2030 → internal 10.10.20.12:22 for SSH to an internal host) or SNAT/MASQUERADE (internet access for an internal subnet via the box IP)."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ export default function NATRulesTab() {
|
|||||||
message.success(t('common.save')); setCreating(false); form.resetFields()
|
message.success(t('common.save')); setCreating(false); form.resetFields()
|
||||||
void qc.invalidateQueries({ queryKey: ['fw', 'nat'] })
|
void qc.invalidateQueries({ queryKey: ['fw', 'nat'] })
|
||||||
},
|
},
|
||||||
|
onError: (e: Error) => message.error(e.message),
|
||||||
})
|
})
|
||||||
const update = useMutation({
|
const update = useMutation({
|
||||||
mutationFn: async ({ id, v }: { id: number; v: FormValues }) => { await apiClient.put(`/firewall/nat-rules/${id}`, v) },
|
mutationFn: async ({ id, v }: { id: number; v: FormValues }) => { await apiClient.put(`/firewall/nat-rules/${id}`, v) },
|
||||||
@@ -76,10 +77,12 @@ export default function NATRulesTab() {
|
|||||||
message.success(t('common.save')); setEditing(null); form.resetFields()
|
message.success(t('common.save')); setEditing(null); form.resetFields()
|
||||||
void qc.invalidateQueries({ queryKey: ['fw', 'nat'] })
|
void qc.invalidateQueries({ queryKey: ['fw', 'nat'] })
|
||||||
},
|
},
|
||||||
|
onError: (e: Error) => message.error(e.message),
|
||||||
})
|
})
|
||||||
const del = useMutation({
|
const del = useMutation({
|
||||||
mutationFn: async (id: number) => { await apiClient.delete(`/firewall/nat-rules/${id}`) },
|
mutationFn: async (id: number) => { await apiClient.delete(`/firewall/nat-rules/${id}`) },
|
||||||
onSuccess: () => { void qc.invalidateQueries({ queryKey: ['fw', 'nat'] }) },
|
onSuccess: () => { void qc.invalidateQueries({ queryKey: ['fw', 'nat'] }) },
|
||||||
|
onError: (e: Error) => message.error(e.message),
|
||||||
})
|
})
|
||||||
const quickToggle = useMutation({
|
const quickToggle = useMutation({
|
||||||
mutationFn: async ({ id, row, checked }: { id: number; row: NATRule; checked: boolean }) => {
|
mutationFn: async ({ id, row, checked }: { id: number; row: NATRule; checked: boolean }) => {
|
||||||
@@ -190,7 +193,7 @@ export default function NATRulesTab() {
|
|||||||
onFinish={(v) => { if (editing) update.mutate({ id: editing.id, v }); else create.mutate(v) }}
|
onFinish={(v) => { if (editing) update.mutate({ id: editing.id, v }); else create.mutate(v) }}
|
||||||
>
|
>
|
||||||
<Form.Item label={t('fw.nat.name')} name="name">
|
<Form.Item label={t('fw.nat.name')} name="name">
|
||||||
<Input placeholder="Forward HTTP zu Web-Backend" />
|
<Input placeholder={t('fw.nat.namePlaceholder')} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Space size="middle">
|
<Space size="middle">
|
||||||
<Form.Item label={t('fw.nat.priority')} name="priority" rules={[{ required: true }]}>
|
<Form.Item label={t('fw.nat.priority')} name="priority" rules={[{ required: true }]}>
|
||||||
|
|||||||
@@ -158,6 +158,7 @@ export default function RulesTab() {
|
|||||||
message.success(t('common.save')); setCreating(false); form.resetFields()
|
message.success(t('common.save')); setCreating(false); form.resetFields()
|
||||||
void qc.invalidateQueries({ queryKey: ['fw', 'rules'] })
|
void qc.invalidateQueries({ queryKey: ['fw', 'rules'] })
|
||||||
},
|
},
|
||||||
|
onError: (e: Error) => message.error(e.message),
|
||||||
})
|
})
|
||||||
const update = useMutation({
|
const update = useMutation({
|
||||||
mutationFn: async ({ id, v }: { id: number; v: FormValues }) => { await apiClient.put(`/firewall/rules/${id}`, buildPayload(v)) },
|
mutationFn: async ({ id, v }: { id: number; v: FormValues }) => { await apiClient.put(`/firewall/rules/${id}`, buildPayload(v)) },
|
||||||
@@ -165,10 +166,12 @@ export default function RulesTab() {
|
|||||||
message.success(t('common.save')); setEditing(null); form.resetFields()
|
message.success(t('common.save')); setEditing(null); form.resetFields()
|
||||||
void qc.invalidateQueries({ queryKey: ['fw', 'rules'] })
|
void qc.invalidateQueries({ queryKey: ['fw', 'rules'] })
|
||||||
},
|
},
|
||||||
|
onError: (e: Error) => message.error(e.message),
|
||||||
})
|
})
|
||||||
const del = useMutation({
|
const del = useMutation({
|
||||||
mutationFn: async (id: number) => { await apiClient.delete(`/firewall/rules/${id}`) },
|
mutationFn: async (id: number) => { await apiClient.delete(`/firewall/rules/${id}`) },
|
||||||
onSuccess: () => { void qc.invalidateQueries({ queryKey: ['fw', 'rules'] }) },
|
onSuccess: () => { void qc.invalidateQueries({ queryKey: ['fw', 'rules'] }) },
|
||||||
|
onError: (e: Error) => message.error(e.message),
|
||||||
})
|
})
|
||||||
const quickToggle = useMutation({
|
const quickToggle = useMutation({
|
||||||
mutationFn: async ({ id, row, checked }: { id: number; row: FwRule; checked: boolean }) => {
|
mutationFn: async ({ id, row, checked }: { id: number; row: FwRule; checked: boolean }) => {
|
||||||
@@ -295,7 +298,7 @@ export default function RulesTab() {
|
|||||||
onFinish={(v) => { if (editing) update.mutate({ id: editing.id, v }); else create.mutate(v) }}
|
onFinish={(v) => { if (editing) update.mutate({ id: editing.id, v }); else create.mutate(v) }}
|
||||||
>
|
>
|
||||||
<Form.Item label={t('fw.rule.name')} name="name">
|
<Form.Item label={t('fw.rule.name')} name="name">
|
||||||
<Input placeholder="SSH von Office" />
|
<Input placeholder={t('fw.rule.namePlaceholder')} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Space size="middle" className="flex-wrap">
|
<Space size="middle" className="flex-wrap">
|
||||||
@@ -303,7 +306,7 @@ export default function RulesTab() {
|
|||||||
<InputNumber min={0} max={9999} />
|
<InputNumber min={0} max={9999} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t('fw.rule.action')} name="action" rules={[{ required: true }]}>
|
<Form.Item label={t('fw.rule.action')} name="action" rules={[{ required: true }]}>
|
||||||
<Select style={{ width: 140 }} options={(['accept','drop','reject'] as const).map(a => ({ value: a, label: a }))} />
|
<Select style={{ width: 140 }} options={(['accept','drop','reject'] as const).map(a => ({ value: a, label: t(`fw.rule.actions.${a}`) }))} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t('fw.rule.enabled')} name="enabled" valuePropName="checked">
|
<Form.Item label={t('fw.rule.enabled')} name="enabled" valuePropName="checked">
|
||||||
<Switch />
|
<Switch />
|
||||||
@@ -319,7 +322,7 @@ export default function RulesTab() {
|
|||||||
<Select style={{ width: 140 }} options={zoneOptions.map(z => ({ value: z, label: z }))} />
|
<Select style={{ width: 140 }} options={zoneOptions.map(z => ({ value: z, label: z }))} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t(`fw.rule.${side}Kind`)} name={`${side}_kind`} rules={[{ required: true }]}>
|
<Form.Item label={t(`fw.rule.${side}Kind`)} name={`${side}_kind`} rules={[{ required: true }]}>
|
||||||
<Select style={{ width: 120 }} options={(['any','object','group','cidr'] as const).map(k => ({ value: k, label: k }))} />
|
<Select style={{ width: 120 }} options={(['any','object','group','cidr'] as const).map(k => ({ value: k, label: t(`fw.rule.kinds.${k}`) }))} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item noStyle shouldUpdate={(p, c) => p[`${side}_kind`] !== c[`${side}_kind`]}>
|
<Form.Item noStyle shouldUpdate={(p, c) => p[`${side}_kind`] !== c[`${side}_kind`]}>
|
||||||
{({ getFieldValue }) => {
|
{({ getFieldValue }) => {
|
||||||
@@ -337,7 +340,7 @@ export default function RulesTab() {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
}
|
}
|
||||||
if (k === 'cidr') {
|
if (k === 'cidr') {
|
||||||
return <Form.Item label="CIDR" name={`${side}_cidr`} rules={[{ required: true }]}>
|
return <Form.Item label={t('fw.rule.kinds.cidr')} name={`${side}_cidr`} rules={[{ required: true }]}>
|
||||||
<Input placeholder="10.0.0.0/24" style={{ width: 200 }} />
|
<Input placeholder="10.0.0.0/24" style={{ width: 200 }} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
}
|
}
|
||||||
@@ -349,7 +352,7 @@ export default function RulesTab() {
|
|||||||
|
|
||||||
<Space size="middle" className="flex-wrap">
|
<Space size="middle" className="flex-wrap">
|
||||||
<Form.Item label={t('fw.rule.serviceKind')} name="service_kind" rules={[{ required: true }]}>
|
<Form.Item label={t('fw.rule.serviceKind')} name="service_kind" rules={[{ required: true }]}>
|
||||||
<Select style={{ width: 120 }} options={(['any','object','group'] as const).map(k => ({ value: k, label: k }))} />
|
<Select style={{ width: 120 }} options={(['any','object','group'] as const).map(k => ({ value: k, label: t(`fw.rule.kinds.${k}`) }))} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item noStyle shouldUpdate={(p, c) => p.service_kind !== c.service_kind}>
|
<Form.Item noStyle shouldUpdate={(p, c) => p.service_kind !== c.service_kind}>
|
||||||
{({ getFieldValue }) => {
|
{({ getFieldValue }) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user