fix(alerts+diag): quickToggle SMTP-Datenverlust + Diagnostics Viewer-Gate
Alerts: quickToggle schickte den Channel durch buildPayload(), das SMTP-
Settings aus einem flachen ChannelFormValues rekonstruiert. Bei Email-
Channels wurde damit jedes aktiv/inaktiv-Toggle zum SMTP-Config-Wipe,
weil die Flat-Fields (smtp_host, smtp_port, from, ...) undefined waren.
Fix: raw Channel-Objekt mit geändertem active-Flag direkt PUT-ten.
testFire-Button fehlte disabled={isViewer} + Tooltip.
Diagnostics: kein isViewer-Check, Viewer sahen funktionierende Run-
Buttons und bekamen 403 ohne Erklärung. Fix: isViewer aus Auth-Store,
disabled-Prop in ToolCard, alle 5 Cards + Enter-Handler gegattet.
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.126"
|
var version = "1.1.127"
|
||||||
|
|
||||||
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.126"
|
var version = "1.1.127"
|
||||||
|
|
||||||
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.126"
|
var version = "1.1.127"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// renewTickInterval — how often we re-evaluate expiring certs.
|
// renewTickInterval — how often we re-evaluate expiring certs.
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ export default function AlertsPage() {
|
|||||||
})
|
})
|
||||||
const quickToggle = useMutation({
|
const quickToggle = useMutation({
|
||||||
mutationFn: async ({ id, row, checked }: { id: number; row: Channel; checked: boolean }) => {
|
mutationFn: async ({ id, row, checked }: { id: number; row: Channel; checked: boolean }) => {
|
||||||
await apiClient.put(`/alerts/channels/${id}`, buildPayload({ ...row, active: checked } as ChannelFormValues))
|
await apiClient.put(`/alerts/channels/${id}`, { ...row, active: checked })
|
||||||
},
|
},
|
||||||
onSuccess: () => { void qc.invalidateQueries({ queryKey: ['alerts', 'channels'] }) },
|
onSuccess: () => { void qc.invalidateQueries({ queryKey: ['alerts', 'channels'] }) },
|
||||||
onError: (e: Error) => message.error(e.message),
|
onError: (e: Error) => message.error(e.message),
|
||||||
@@ -284,11 +284,14 @@ export default function AlertsPage() {
|
|||||||
subtitle={t('alerts.intro')}
|
subtitle={t('alerts.intro')}
|
||||||
extra={
|
extra={
|
||||||
<Space>
|
<Space>
|
||||||
|
<Tooltip title={isViewer ? t('auth.viewerBadge') : undefined}>
|
||||||
<Button icon={<ExperimentOutlined />}
|
<Button icon={<ExperimentOutlined />}
|
||||||
onClick={() => testFire.mutate()}
|
onClick={() => testFire.mutate()}
|
||||||
loading={testFire.isPending}>
|
loading={testFire.isPending}
|
||||||
|
disabled={isViewer}>
|
||||||
{t('alerts.test')}
|
{t('alerts.test')}
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { useTranslation } from 'react-i18next'
|
|||||||
|
|
||||||
import apiClient, { isEnvelope } from '../../api/client'
|
import apiClient, { isEnvelope } from '../../api/client'
|
||||||
import PageHeader from '../../components/PageHeader'
|
import PageHeader from '../../components/PageHeader'
|
||||||
|
import { useAuthStore } from '../../stores/auth'
|
||||||
|
|
||||||
const { Text } = Typography
|
const { Text } = Typography
|
||||||
|
|
||||||
@@ -30,7 +31,7 @@ interface DiagResult {
|
|||||||
// - Eigene Input-Form
|
// - Eigene Input-Form
|
||||||
// - "Ausführen"-Button → POST /diagnostics/<tool>
|
// - "Ausführen"-Button → POST /diagnostics/<tool>
|
||||||
// - Output-Pane mit monospace-Output, Status-Tag (OK/Error), Dauer
|
// - Output-Pane mit monospace-Output, Status-Tag (OK/Error), Dauer
|
||||||
function ToolCard({ icon, title, intro, children, result, loading, run }: {
|
function ToolCard({ icon, title, intro, children, result, loading, run, disabled }: {
|
||||||
icon: React.ReactNode
|
icon: React.ReactNode
|
||||||
title: string
|
title: string
|
||||||
intro: string
|
intro: string
|
||||||
@@ -38,6 +39,7 @@ function ToolCard({ icon, title, intro, children, result, loading, run }: {
|
|||||||
result: DiagResult | null
|
result: DiagResult | null
|
||||||
loading: boolean
|
loading: boolean
|
||||||
run: () => void
|
run: () => void
|
||||||
|
disabled?: boolean
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
return (
|
return (
|
||||||
@@ -45,10 +47,12 @@ function ToolCard({ icon, title, intro, children, result, loading, run }: {
|
|||||||
size="small"
|
size="small"
|
||||||
title={<Space>{icon}<Text strong>{title}</Text></Space>}
|
title={<Space>{icon}<Text strong>{title}</Text></Space>}
|
||||||
extra={
|
extra={
|
||||||
|
<Tooltip title={disabled ? t('auth.viewerBadge') : undefined}>
|
||||||
<Button type="primary" size="small" onClick={run} loading={loading}
|
<Button type="primary" size="small" onClick={run} loading={loading}
|
||||||
icon={<RadarChartOutlined />}>
|
disabled={disabled} icon={<RadarChartOutlined />}>
|
||||||
{t('diag.run')}
|
{t('diag.run')}
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Text type="secondary" style={{ fontSize: 12 }}>{intro}</Text>
|
<Text type="secondary" style={{ fontSize: 12 }}>{intro}</Text>
|
||||||
@@ -84,6 +88,7 @@ function ToolCard({ icon, title, intro, children, result, loading, run }: {
|
|||||||
|
|
||||||
export default function DiagnosticsPage() {
|
export default function DiagnosticsPage() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const isViewer = useAuthStore((s) => s.user?.role) === 'viewer'
|
||||||
|
|
||||||
const [pingTarget, setPingTarget] = useState('1.1.1.1')
|
const [pingTarget, setPingTarget] = useState('1.1.1.1')
|
||||||
const [traceTarget, setTraceTarget] = useState('1.1.1.1')
|
const [traceTarget, setTraceTarget] = useState('1.1.1.1')
|
||||||
@@ -145,11 +150,12 @@ export default function DiagnosticsPage() {
|
|||||||
result={pingRes}
|
result={pingRes}
|
||||||
loading={ping.isPending}
|
loading={ping.isPending}
|
||||||
run={() => ping.mutate()}
|
run={() => ping.mutate()}
|
||||||
|
disabled={isViewer}
|
||||||
>
|
>
|
||||||
<Input placeholder="1.1.1.1 oder example.com"
|
<Input placeholder="1.1.1.1 oder example.com"
|
||||||
value={pingTarget}
|
value={pingTarget}
|
||||||
onChange={(e) => setPingTarget(e.target.value)}
|
onChange={(e) => setPingTarget(e.target.value)}
|
||||||
onPressEnter={() => ping.mutate()} />
|
onPressEnter={() => { if (!isViewer) ping.mutate() }} />
|
||||||
</ToolCard>
|
</ToolCard>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} lg={12}>
|
<Col xs={24} lg={12}>
|
||||||
@@ -160,11 +166,12 @@ export default function DiagnosticsPage() {
|
|||||||
result={traceRes}
|
result={traceRes}
|
||||||
loading={trace.isPending}
|
loading={trace.isPending}
|
||||||
run={() => trace.mutate()}
|
run={() => trace.mutate()}
|
||||||
|
disabled={isViewer}
|
||||||
>
|
>
|
||||||
<Input placeholder="1.1.1.1 oder example.com"
|
<Input placeholder="1.1.1.1 oder example.com"
|
||||||
value={traceTarget}
|
value={traceTarget}
|
||||||
onChange={(e) => setTraceTarget(e.target.value)}
|
onChange={(e) => setTraceTarget(e.target.value)}
|
||||||
onPressEnter={() => trace.mutate()} />
|
onPressEnter={() => { if (!isViewer) trace.mutate() }} />
|
||||||
</ToolCard>
|
</ToolCard>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} lg={12}>
|
<Col xs={24} lg={12}>
|
||||||
@@ -175,12 +182,13 @@ export default function DiagnosticsPage() {
|
|||||||
result={digRes}
|
result={digRes}
|
||||||
loading={dig.isPending}
|
loading={dig.isPending}
|
||||||
run={() => dig.mutate()}
|
run={() => dig.mutate()}
|
||||||
|
disabled={isViewer}
|
||||||
>
|
>
|
||||||
<Space.Compact style={{ width: '100%' }}>
|
<Space.Compact style={{ width: '100%' }}>
|
||||||
<Input placeholder="netcell-it.de"
|
<Input placeholder="netcell-it.de"
|
||||||
value={digName}
|
value={digName}
|
||||||
onChange={(e) => setDigName(e.target.value)}
|
onChange={(e) => setDigName(e.target.value)}
|
||||||
onPressEnter={() => dig.mutate()}
|
onPressEnter={() => { if (!isViewer) dig.mutate() }}
|
||||||
style={{ flex: 1 }} />
|
style={{ flex: 1 }} />
|
||||||
<Select
|
<Select
|
||||||
value={digType}
|
value={digType}
|
||||||
@@ -199,11 +207,12 @@ export default function DiagnosticsPage() {
|
|||||||
result={curlRes}
|
result={curlRes}
|
||||||
loading={curl.isPending}
|
loading={curl.isPending}
|
||||||
run={() => curl.mutate()}
|
run={() => curl.mutate()}
|
||||||
|
disabled={isViewer}
|
||||||
>
|
>
|
||||||
<Input placeholder="https://example.com"
|
<Input placeholder="https://example.com"
|
||||||
value={curlUrl}
|
value={curlUrl}
|
||||||
onChange={(e) => setCurlUrl(e.target.value)}
|
onChange={(e) => setCurlUrl(e.target.value)}
|
||||||
onPressEnter={() => curl.mutate()} />
|
onPressEnter={() => { if (!isViewer) curl.mutate() }} />
|
||||||
</ToolCard>
|
</ToolCard>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24}>
|
<Col xs={24}>
|
||||||
@@ -214,12 +223,13 @@ export default function DiagnosticsPage() {
|
|||||||
result={tcpRes}
|
result={tcpRes}
|
||||||
loading={tcp.isPending}
|
loading={tcp.isPending}
|
||||||
run={() => tcp.mutate()}
|
run={() => tcp.mutate()}
|
||||||
|
disabled={isViewer}
|
||||||
>
|
>
|
||||||
<Space.Compact style={{ width: '100%' }}>
|
<Space.Compact style={{ width: '100%' }}>
|
||||||
<Input placeholder="10.0.5.14"
|
<Input placeholder="10.0.5.14"
|
||||||
value={tcpHost}
|
value={tcpHost}
|
||||||
onChange={(e) => setTcpHost(e.target.value)}
|
onChange={(e) => setTcpHost(e.target.value)}
|
||||||
onPressEnter={() => tcp.mutate()}
|
onPressEnter={() => { if (!isViewer) tcp.mutate() }}
|
||||||
style={{ flex: 1 }} />
|
style={{ flex: 1 }} />
|
||||||
<InputNumber
|
<InputNumber
|
||||||
min={1} max={65535}
|
min={1} max={65535}
|
||||||
|
|||||||
Reference in New Issue
Block a user