diff --git a/betterdesk-server/auth/permissions.go b/betterdesk-server/auth/permissions.go
index 6b67e6e7..b28f2aba 100644
--- a/betterdesk-server/auth/permissions.go
+++ b/betterdesk-server/auth/permissions.go
@@ -24,8 +24,9 @@ const (
PermUserDelete = "user.delete"
// Server configuration
- PermServerConfig = "server.config" // read/write server_config
- PermServerKeys = "server.keys" // manage API keys
+ PermServerConfig = "server.config" // read/write server_config
+ PermServerKeys = "server.keys" // manage API keys
+ PermServerAttestation = "server.attestation" // run/view server performance attestation
// Organization permissions
PermOrgCreate = "org.create"
@@ -61,7 +62,7 @@ var AllPermissions = []string{
PermDeviceView, PermDeviceConnect, PermDeviceEdit, PermDeviceDelete,
PermDeviceBan, PermDeviceChangeID,
PermUserView, PermUserCreate, PermUserEdit, PermUserDelete,
- PermServerConfig, PermServerKeys,
+ PermServerConfig, PermServerKeys, PermServerAttestation,
PermOrgCreate, PermOrgEdit, PermOrgDelete, PermOrgManageUsers, PermOrgManageDevices,
PermAuditView, PermMetricsView, PermBlocklistEdit,
PermCDAPView, PermCDAPCommand, PermCDAPTerminal, PermCDAPFiles,
@@ -88,7 +89,7 @@ var DefaultRolePermissions = map[string]map[string]bool{
// Server Admin: infrastructure + monitoring, read-only user visibility.
// Cannot create/edit/delete users, cannot manage orgs.
RoleServerAdmin: buildPermMap([]string{
- PermServerConfig, PermServerKeys,
+ PermServerConfig, PermServerKeys, PermServerAttestation,
PermBlocklistEdit,
PermUserView, // read-only
PermDeviceView, // read-only
diff --git a/web-nodejs/lang/en.json b/web-nodejs/lang/en.json
index 6fd8de6c..073386d6 100644
--- a/web-nodejs/lang/en.json
+++ b/web-nodejs/lang/en.json
@@ -2130,6 +2130,7 @@
"perm_user_delete": "Delete users",
"perm_server_config": "Server configuration",
"perm_server_keys": "Manage API keys",
+ "perm_server_attestation": "Server performance attestation",
"perm_org_create": "Create organizations",
"perm_org_edit": "Edit organizations",
"perm_org_delete": "Delete organizations",
@@ -3546,6 +3547,7 @@
"starting": "Starting…",
"complete": "Benchmark complete",
"signal_peers": "Signal peers",
- "relay_sessions": "Relay sessions"
+ "relay_sessions": "Relay sessions",
+ "page_error": "Failed to load the Server Attestation page"
}
}
diff --git a/web-nodejs/lang/pl.json b/web-nodejs/lang/pl.json
index b51d3766..28e316d2 100644
--- a/web-nodejs/lang/pl.json
+++ b/web-nodejs/lang/pl.json
@@ -2131,6 +2131,7 @@
"perm_user_delete": "Usuwanie użytkowników",
"perm_server_config": "Konfiguracja serwera",
"perm_server_keys": "Zarządzanie kluczami API",
+ "perm_server_attestation": "Atest wydajności serwera",
"perm_org_create": "Tworzenie organizacji",
"perm_org_edit": "Edycja organizacji",
"perm_org_delete": "Usuwanie organizacji",
@@ -3547,6 +3548,7 @@
"starting": "Uruchamianie…",
"complete": "Benchmark zakończony",
"signal_peers": "Peery signal",
- "relay_sessions": "Sesje relay"
+ "relay_sessions": "Sesje relay",
+ "page_error": "Nie udało się załadować strony atestu serwera"
}
}
diff --git a/web-nodejs/middleware/auth.js b/web-nodejs/middleware/auth.js
index c53db888..93123741 100644
--- a/web-nodejs/middleware/auth.js
+++ b/web-nodejs/middleware/auth.js
@@ -17,7 +17,7 @@ const DEFAULT_ROLE_PERMISSIONS = {
// Server Admin: infrastructure + read-only users
server_admin: new Set([
- 'server.config', 'server.keys',
+ 'server.config', 'server.keys', 'server.attestation',
'blocklist.edit',
'user.view',
'device.view',
diff --git a/web-nodejs/public/js/permissions.js b/web-nodejs/public/js/permissions.js
index 06cb6fee..8826a4f3 100644
--- a/web-nodejs/public/js/permissions.js
+++ b/web-nodejs/public/js/permissions.js
@@ -39,7 +39,7 @@
{
id: 'server',
icon: 'dns',
- permissions: ['server.config', 'server.keys']
+ permissions: ['server.config', 'server.keys', 'server.attestation']
},
{
id: 'org',
diff --git a/web-nodejs/routes/server-attestation.routes.js b/web-nodejs/routes/server-attestation.routes.js
index 5ca8a8a7..8768e4c2 100644
--- a/web-nodejs/routes/server-attestation.routes.js
+++ b/web-nodejs/routes/server-attestation.routes.js
@@ -9,7 +9,7 @@ const router = express.Router();
const { requireAuth, requirePermission } = require('../middleware/auth');
const attestation = require('../services/serverAttestation');
-const REQUIRED_PERMISSION = 'server.config';
+const REQUIRED_PERMISSION = 'server.attestation';
const auth = [requireAuth, requirePermission(REQUIRED_PERMISSION)];
let runPromise = null;
@@ -17,15 +17,23 @@ let runPromise = null;
// ─── Page ─────────────────────────────────────────────────────────────────────
router.get('/server-attestation', ...auth, async (req, res) => {
- const lastResult = await attestation.getLastResult();
- res.render('server-attestation', {
- title: req.t('server_attestation.title'),
- pageStyles: ['server-attestation'],
- pageScripts: ['server-attestation'],
- currentPage: 'server-attestation',
- breadcrumb: [{ label: req.t('server_attestation.title') }],
- lastResult
- });
+ try {
+ const lastResult = await attestation.getLastResult();
+ res.render('server-attestation', {
+ title: req.t('server_attestation.title'),
+ pageStyles: ['server-attestation'],
+ pageScripts: ['server-attestation'],
+ currentPage: 'server-attestation',
+ breadcrumb: [{ label: req.t('server_attestation.title') }],
+ lastResult
+ });
+ } catch (err) {
+ console.error('[ServerAttestation] page render failed:', err);
+ res.status(500).render('errors/500', {
+ title: 'Error',
+ message: req.t('server_attestation.page_error') || 'Failed to load Server Attestation page'
+ });
+ }
});
// ─── Authenticated API ────────────────────────────────────────────────────────
diff --git a/web-nodejs/views/partials/sidebar.ejs b/web-nodejs/views/partials/sidebar.ejs
index 407f70e8..07144a48 100644
--- a/web-nodejs/views/partials/sidebar.ejs
+++ b/web-nodejs/views/partials/sidebar.ejs
@@ -48,6 +48,8 @@
data-category="server-mgmt" title="<%= _('nav.server_management') || 'Server Management' %>">
dns
+ <% } %>
+ <% if (hasPermission('server.attestation')) { %>