From 844f9ddc830e4fa96f3c190b1bcc7de05cf4915a Mon Sep 17 00:00:00 2001 From: Debian Date: Mon, 25 May 2026 12:07:10 +0200 Subject: [PATCH] feat(firewall): Move-up/down buttons for rule priority reordering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds ↑↓ icon buttons in the firewall rules table. Clicking swaps the rule's priority value with its sorted neighbor via two sequential PUTs. First rule's ↑ and last rule's ↓ are disabled. Viewer role cannot reorder. v1.1.102 Co-Authored-By: Claude Sonnet 4.6 --- VERSION | 2 +- cmd/edgeguard-api/main.go | 2 +- cmd/edgeguard-ctl/main.go | 2 +- cmd/edgeguard-scheduler/main.go | 2 +- management-ui/src/i18n/locales/de/common.json | 2 + management-ui/src/i18n/locales/en/common.json | 2 + management-ui/src/pages/Firewall/Rules.tsx | 45 ++++++++++++++++++- 7 files changed, 51 insertions(+), 6 deletions(-) diff --git a/VERSION b/VERSION index 2088b5b..b02d7a2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.101 +1.1.102 diff --git a/cmd/edgeguard-api/main.go b/cmd/edgeguard-api/main.go index eab2fb1..71b60c3 100644 --- a/cmd/edgeguard-api/main.go +++ b/cmd/edgeguard-api/main.go @@ -60,7 +60,7 @@ import ( usersvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/users" ) -var version = "1.1.101" +var version = "1.1.102" func main() { addr := os.Getenv("EDGEGUARD_API_ADDR") diff --git a/cmd/edgeguard-ctl/main.go b/cmd/edgeguard-ctl/main.go index f5bcc41..4852091 100644 --- a/cmd/edgeguard-ctl/main.go +++ b/cmd/edgeguard-ctl/main.go @@ -11,7 +11,7 @@ import ( "git.netcell-it.de/projekte/edgeguard-native/internal/services/setup" ) -var version = "1.1.101" +var version = "1.1.102" const usage = `edgeguard-ctl — EdgeGuard CLI diff --git a/cmd/edgeguard-scheduler/main.go b/cmd/edgeguard-scheduler/main.go index 282983f..b863ae5 100644 --- a/cmd/edgeguard-scheduler/main.go +++ b/cmd/edgeguard-scheduler/main.go @@ -35,7 +35,7 @@ import ( "git.netcell-it.de/projekte/edgeguard-native/internal/services/tlscerts" ) -var version = "1.1.101" +var version = "1.1.102" const ( // renewTickInterval — how often we re-evaluate expiring certs. diff --git a/management-ui/src/i18n/locales/de/common.json b/management-ui/src/i18n/locales/de/common.json index 5f4e316..94e17a1 100644 --- a/management-ui/src/i18n/locales/de/common.json +++ b/management-ui/src/i18n/locales/de/common.json @@ -109,6 +109,8 @@ "cidr": "CIDR", "actions": { "accept": "ACCEPT", "drop": "DROP", "reject": "REJECT" }, "kinds": { "any": "Beliebig", "object": "Objekt", "group": "Gruppe", "cidr": "CIDR" }, + "moveUp": "Nach oben (höhere Priorität)", + "moveDown": "Nach unten (niedrigere Priorität)", "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." }, diff --git a/management-ui/src/i18n/locales/en/common.json b/management-ui/src/i18n/locales/en/common.json index 7bfbddd..16d271d 100644 --- a/management-ui/src/i18n/locales/en/common.json +++ b/management-ui/src/i18n/locales/en/common.json @@ -109,6 +109,8 @@ "cidr": "CIDR", "actions": { "accept": "ACCEPT", "drop": "DROP", "reject": "REJECT" }, "kinds": { "any": "Any", "object": "Object", "group": "Group", "cidr": "CIDR" }, + "moveUp": "Move up (higher priority)", + "moveDown": "Move down (lower priority)", "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." }, diff --git a/management-ui/src/pages/Firewall/Rules.tsx b/management-ui/src/pages/Firewall/Rules.tsx index 9faf83e..8e129c0 100644 --- a/management-ui/src/pages/Firewall/Rules.tsx +++ b/management-ui/src/pages/Firewall/Rules.tsx @@ -1,9 +1,9 @@ -import { useState } from 'react' +import { useMemo, useState } from 'react' import { Button, Form, Input, InputNumber, Modal, Popconfirm, Select, Space, Switch, Tag, Tooltip, Typography, message } from 'antd' import type { ColumnsType } from 'antd/es/table' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { useTranslation } from 'react-i18next' -import { FireOutlined } from '@ant-design/icons' +import { ArrowDownOutlined, ArrowUpOutlined, FireOutlined } from '@ant-design/icons' const { Text } = Typography @@ -183,6 +183,20 @@ export default function RulesTab() { onError: (e: Error) => message.error(e.message), }) + const sortedRules = useMemo( + () => [...(rules ?? [])].sort((a, b) => a.priority - b.priority), + [rules], + ) + + const swap = useMutation({ + mutationFn: async ({ a, b }: { a: FwRule; b: FwRule }) => { + await apiClient.put(`/firewall/rules/${a.id}`, { ...a, priority: b.priority }) + await apiClient.put(`/firewall/rules/${b.id}`, { ...b, priority: a.priority }) + }, + onSuccess: () => { void qc.invalidateQueries({ queryKey: ['fw', 'rules'] }) }, + onError: (e: Error) => message.error(e.message), + }) + const editFromRow = (r: FwRule) => { setEditing(r) form.setFieldsValue({ @@ -246,6 +260,33 @@ export default function RulesTab() { ) }, }, + { + title: '', key: 'move', width: 64, + render: (_, row) => { + const idx = sortedRules.findIndex(r => r.id === row.id) + const swapping = swap.isPending + return ( + + +