diff --git a/frontend/src/component/admin/banners/Banners.tsx b/frontend/src/component/admin/banners/Banners.tsx
index 9e4a2070a8df..db86315e1495 100644
--- a/frontend/src/component/admin/banners/Banners.tsx
+++ b/frontend/src/component/admin/banners/Banners.tsx
@@ -3,6 +3,7 @@ import { PermissionGuard } from 'component/common/PermissionGuard/PermissionGuar
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { PremiumFeature } from 'component/common/PremiumFeature/PremiumFeature';
import { BannersTable } from './BannersTable/BannersTable';
+import { UPDATE_INSTANCE_BANNERS } from '@server/types/permissions';
export const Banners = () => {
const { isEnterprise } = useUiConfig();
@@ -13,7 +14,7 @@ export const Banners = () => {
return (
diff --git a/frontend/src/component/admin/maintenance/index.tsx b/frontend/src/component/admin/maintenance/index.tsx
index 505dd10e4963..9774adbc312e 100644
--- a/frontend/src/component/admin/maintenance/index.tsx
+++ b/frontend/src/component/admin/maintenance/index.tsx
@@ -6,10 +6,11 @@ import { Box, styled } from '@mui/material';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { MaintenanceTooltip } from './MaintenanceTooltip';
import { MaintenanceToggle } from './MaintenanceToggle';
+import { UPDATE_MAINTENANCE_MODE } from '@server/types/permissions';
export const MaintenanceAdmin = () => (
diff --git a/frontend/src/component/admin/roles/RoleForm/RolePermissionCategories/RolePermissionCategories.tsx b/frontend/src/component/admin/roles/RoleForm/RolePermissionCategories/RolePermissionCategories.tsx
index 0fbe05ea7967..d8097bc22a25 100644
--- a/frontend/src/component/admin/roles/RoleForm/RolePermissionCategories/RolePermissionCategories.tsx
+++ b/frontend/src/component/admin/roles/RoleForm/RolePermissionCategories/RolePermissionCategories.tsx
@@ -42,6 +42,9 @@ export const RolePermissionCategories = ({
});
const releasePlansEnabled = useUiFlag('releasePlans');
+ const granularAdminPermissionsEnabled = useUiFlag(
+ 'granularAdminPermissions',
+ );
const isProjectRole = PROJECT_ROLE_TYPES.includes(type);
@@ -85,10 +88,15 @@ export const RolePermissionCategories = ({
releasePlansEnabled ||
label !== 'Release plan templates',
)
+ .filter(
+ ({ label }) =>
+ granularAdminPermissionsEnabled ||
+ label !== 'Instance maintenance',
+ )
.map(({ label, type, permissions }) => (
permission.permission === 'ADMIN',
+ permissions = permissions.filter((permission) =>
+ MAINTENANCE_MODE_PERMISSIONS.includes(permission.permission),
);
}
return permissions;
diff --git a/frontend/src/interfaces/uiConfig.ts b/frontend/src/interfaces/uiConfig.ts
index 19263be2b3e2..b135df6b0219 100644
--- a/frontend/src/interfaces/uiConfig.ts
+++ b/frontend/src/interfaces/uiConfig.ts
@@ -94,6 +94,7 @@ export type UiFlags = {
showUserDeviceCount?: boolean;
flagOverviewRedesign?: boolean;
licensedUsers?: boolean;
+ granularAdminPermissions?: boolean;
};
export interface IVersionInfo {
diff --git a/src/lib/features/maintenance/maintenance-controller.ts b/src/lib/features/maintenance/maintenance-controller.ts
index bba95b8d5921..71df5702ed0c 100644
--- a/src/lib/features/maintenance/maintenance-controller.ts
+++ b/src/lib/features/maintenance/maintenance-controller.ts
@@ -1,4 +1,9 @@
-import { ADMIN, type IUnleashConfig, type IUnleashServices } from '../../types';
+import {
+ ADMIN,
+ UPDATE_MAINTENANCE_MODE,
+ type IUnleashConfig,
+ type IUnleashServices,
+} from '../../types';
import type { Request, Response } from 'express';
import Controller from '../../routes/controller';
import type { Logger } from '../../logger';
@@ -38,7 +43,7 @@ export default class MaintenanceController extends Controller {
this.route({
method: 'post',
path: '',
- permission: ADMIN,
+ permission: [ADMIN, UPDATE_MAINTENANCE_MODE],
handler: this.toggleMaintenance,
middleware: [
this.openApiService.validPath({
@@ -58,7 +63,7 @@ export default class MaintenanceController extends Controller {
this.route({
method: 'get',
path: '',
- permission: ADMIN,
+ permission: [ADMIN, UPDATE_MAINTENANCE_MODE],
handler: this.getMaintenance,
middleware: [
this.openApiService.validPath({
diff --git a/src/lib/types/experimental.ts b/src/lib/types/experimental.ts
index 493d439efa99..3896f6e21074 100644
--- a/src/lib/types/experimental.ts
+++ b/src/lib/types/experimental.ts
@@ -60,6 +60,7 @@ export type IFlagKey =
| 'deleteStaleUserSessions'
| 'memorizeStats'
| 'licensedUsers'
+ | 'granularAdminPermissions'
| 'streaming'
| 'etagVariant'
| 'oidcRedirect';
@@ -282,6 +283,10 @@ const flags: IFlags = {
process.env.UNLEASH_EXPERIMENTAL_FLAG_LICENSED_USERS,
false,
),
+ granularAdminPermissions: parseEnvVarBoolean(
+ process.env.UNLEASH_EXPERIMENTAL_GRANULAR_ADMIN_PERMISSIONS,
+ false,
+ ),
streaming: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_STREAMING,
false,
diff --git a/src/lib/types/permissions.ts b/src/lib/types/permissions.ts
index 245526ca2b3e..2ce1d977a011 100644
--- a/src/lib/types/permissions.ts
+++ b/src/lib/types/permissions.ts
@@ -41,6 +41,9 @@ export const CREATE_TAG_TYPE = 'CREATE_TAG_TYPE';
export const UPDATE_TAG_TYPE = 'UPDATE_TAG_TYPE';
export const DELETE_TAG_TYPE = 'DELETE_TAG_TYPE';
+export const UPDATE_MAINTENANCE_MODE = 'UPDATE_MAINTENANCE_MODE';
+export const UPDATE_INSTANCE_BANNERS = 'UPDATE_INSTANCE_BANNERS';
+
// Project
export const CREATE_FEATURE = 'CREATE_FEATURE';
export const UPDATE_FEATURE = 'UPDATE_FEATURE';
@@ -83,7 +86,7 @@ export const RELEASE_PLAN_TEMPLATE_DELETE = 'RELEASE_PLAN_TEMPLATE_DELETE';
export const ROOT_PERMISSION_CATEGORIES = [
{
- label: 'Addon',
+ label: 'Integration',
permissions: [CREATE_ADDON, UPDATE_ADDON, DELETE_ADDON],
},
{
@@ -141,4 +144,17 @@ export const ROOT_PERMISSION_CATEGORIES = [
RELEASE_PLAN_TEMPLATE_UPDATE,
],
},
+ {
+ label: 'Instance maintenance',
+ permissions: [UPDATE_MAINTENANCE_MODE, UPDATE_INSTANCE_BANNERS],
+ },
+];
+
+// Used on Frontend, to allow admin panel use for users with custom root roles
+export const MAINTENANCE_MODE_PERMISSIONS = [
+ ADMIN,
+ READ_ROLE,
+ READ_CLIENT_API_TOKEN,
+ READ_FRONTEND_API_TOKEN,
+ UPDATE_MAINTENANCE_MODE,
];
diff --git a/src/server-dev.ts b/src/server-dev.ts
index 1b0d7b0eb7e5..d4b7e36deb20 100644
--- a/src/server-dev.ts
+++ b/src/server-dev.ts
@@ -57,6 +57,7 @@ process.nextTick(async () => {
showUserDeviceCount: true,
flagOverviewRedesign: false,
licensedUsers: true,
+ granularAdminPermissions: true,
},
},
authentication: {