diff --git a/VERSION b/VERSION
index 8731870..69658cd 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.1.89
+1.1.90
diff --git a/cmd/edgeguard-api/main.go b/cmd/edgeguard-api/main.go
index 0da789e..eb689bc 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.89"
+var version = "1.1.90"
func main() {
addr := os.Getenv("EDGEGUARD_API_ADDR")
diff --git a/cmd/edgeguard-ctl/main.go b/cmd/edgeguard-ctl/main.go
index 3818852..bfb96bb 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.89"
+var version = "1.1.90"
const usage = `edgeguard-ctl — EdgeGuard CLI
diff --git a/cmd/edgeguard-scheduler/main.go b/cmd/edgeguard-scheduler/main.go
index 3f70e61..f9e0695 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.89"
+var version = "1.1.90"
const (
// renewTickInterval — how often we re-evaluate expiring certs.
diff --git a/management-ui/src/pages/Cluster/index.tsx b/management-ui/src/pages/Cluster/index.tsx
index 82a0f8e..e3e8607 100644
--- a/management-ui/src/pages/Cluster/index.tsx
+++ b/management-ui/src/pages/Cluster/index.tsx
@@ -7,6 +7,7 @@ import { useTranslation } from 'react-i18next'
import apiClient, { isEnvelope } from '../../api/client'
import PageHeader from '../../components/PageHeader'
+import { useAuthStore } from '../../stores/auth'
const { Text } = Typography
@@ -116,6 +117,7 @@ function useTickingNow(intervalMs = 1000): number {
export default function ClusterPage() {
const { t } = useTranslation()
const now = useTickingNow()
+ const isViewer = useAuthStore((s) => s.user?.role) === 'viewer'
const { data, isLoading } = useQuery({
queryKey: ['cluster', 'status'],
@@ -246,22 +248,30 @@ export default function ClusterPage() {
{
title: t('common.actions'), key: 'actions', width: 110,
render: (_, r) => (
- removePeer.mutate(r.id)}
- >
- }
- loading={removePeer.isPending && removePeer.variables === r.id}
+ isViewer ? (
+
+ } disabled>
+ {t('cluster.removePeerBtn')}
+
+
+ ) : (
+ removePeer.mutate(r.id)}
>
- {t('cluster.removePeerBtn')}
-
-
+ }
+ loading={removePeer.isPending && removePeer.variables === r.id}
+ >
+ {t('cluster.removePeerBtn')}
+
+
+ )
),
},
]
@@ -274,13 +284,16 @@ export default function ClusterPage() {
subtitle={t('cluster.intro', { count: 1 + data.peers.length })}
extra={
- }
- loading={generateToken.isPending}
- onClick={() => generateToken.mutate()}
- >
- {t('cluster.generateJoinToken')}
-
+
+ }
+ loading={generateToken.isPending}
+ disabled={isViewer}
+ onClick={() => generateToken.mutate()}
+ >
+ {t('cluster.generateJoinToken')}
+
+
{data.mode === 'cluster' ? t('cluster.modeCluster') : t('cluster.modeSingle')}
@@ -363,16 +376,22 @@ export default function ClusterPage() {
{(certStatus.data?.has_ca || certStatus.data?.has_peer) && (
renewSelf.mutate()}
- >
-
-
+ isViewer ? (
+
+
+
+ ) : (
+ renewSelf.mutate()}
+ >
+
+
+ )
)}
>
diff --git a/management-ui/src/pages/Settings/index.tsx b/management-ui/src/pages/Settings/index.tsx
index d0d5277..0a1c47e 100644
--- a/management-ui/src/pages/Settings/index.tsx
+++ b/management-ui/src/pages/Settings/index.tsx
@@ -1,4 +1,4 @@
-import { Alert, Button, Card, Descriptions, Form, Input, InputNumber, Select, Space, Spin, Switch, Typography, message } from 'antd'
+import { Alert, Button, Card, Descriptions, Form, Input, InputNumber, Select, Space, Spin, Switch, Tooltip, Typography, message } from 'antd'
import { CloudDownloadOutlined, CloudSyncOutlined, CodeOutlined, DatabaseOutlined, ExclamationCircleOutlined, FileSearchOutlined, GlobalOutlined, LockOutlined, MailOutlined, ReloadOutlined, SettingOutlined, StopOutlined, ToolOutlined } from '@ant-design/icons'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { useEffect, useState } from 'react'
@@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next'
import apiClient, { isEnvelope } from '../../api/client'
import PageHeader from '../../components/PageHeader'
+import { useAuthStore } from '../../stores/auth'
interface SetupStatus {
completed: boolean
@@ -33,6 +34,7 @@ interface ChangePasswordValues {
export default function SettingsPage() {
const { t } = useTranslation()
const qc = useQueryClient()
+ const isViewer = useAuthStore((s) => s.user?.role) === 'viewer'
const [msg, msgCtx] = message.useMessage()
const [pwForm] = Form.useForm()
@@ -343,27 +345,36 @@ export default function SettingsPage() {
size="small"
>
- }
- loading={haproxyReload.isPending}
- onClick={() => haproxyReload.mutate()}
- >
- {t('settings.haproxyReloadBtn')}
-
- }
- loading={renderConfigs.isPending}
- onClick={() => renderConfigs.mutate()}
- >
- {t('settings.renderConfigsBtn')}
-
- }
- loading={triggerBackup.isPending}
- onClick={() => triggerBackup.mutate()}
- >
- {t('settings.backupNowBtn')}
-
+
+ }
+ loading={haproxyReload.isPending}
+ disabled={isViewer}
+ onClick={() => haproxyReload.mutate()}
+ >
+ {t('settings.haproxyReloadBtn')}
+
+
+
+ }
+ loading={renderConfigs.isPending}
+ disabled={isViewer}
+ onClick={() => renderConfigs.mutate()}
+ >
+ {t('settings.renderConfigsBtn')}
+
+
+
+ }
+ loading={triggerBackup.isPending}
+ disabled={isViewer}
+ onClick={() => triggerBackup.mutate()}
+ >
+ {t('settings.backupNowBtn')}
+
+
{t('settings.actionsHint')}
@@ -396,14 +407,17 @@ export default function SettingsPage() {
)}
- }
- loading={restartingService === svc}
- onClick={() => serviceRestart.mutate(svc)}
- >
- {t('settings.serviceRestartBtn')}
-
+
+ }
+ loading={restartingService === svc}
+ disabled={isViewer}
+ onClick={() => serviceRestart.mutate(svc)}
+ >
+ {t('settings.serviceRestartBtn')}
+
+
)
})}
@@ -501,9 +515,11 @@ export default function SettingsPage() {
-
+
+
+
@@ -532,6 +548,7 @@ export default function SettingsPage() {
toggleMaintenance.mutate({
enabled: checked,
message: maintMessage || maintenance?.message || '',
@@ -574,7 +591,7 @@ export default function SettingsPage() {
step={1}
value={backupRetention?.keep ?? 0}
onChange={(v) => setBackupRetention.mutate((v as number) ?? 0)}
- disabled={setBackupRetention.isPending}
+ disabled={setBackupRetention.isPending || isViewer}
addonAfter={t('settings.backupRetentionUnit')}
style={{ width: 200 }}
/>
@@ -603,7 +620,7 @@ export default function SettingsPage() {
step={30}
value={auditRetention?.days ?? 0}
onChange={(v) => setAuditRetention.mutate((v as number) ?? 0)}
- disabled={setAuditRetention.isPending}
+ disabled={setAuditRetention.isPending || isViewer}
addonAfter={t('settings.auditRetentionUnit')}
style={{ width: 200 }}
/>
@@ -629,6 +646,7 @@ export default function SettingsPage() {
toggleAutoUpdate.mutate(checked)}
/>
@@ -651,6 +669,7 @@ export default function SettingsPage() {
toggleIPv6.mutate(checked)}
/>