diff --git a/backend/src/routes/api/groups-config/groupsConfigUtil.ts b/backend/src/routes/api/groups-config/groupsConfigUtil.ts index 8c125ebca4..10d2e37fe8 100644 --- a/backend/src/routes/api/groups-config/groupsConfigUtil.ts +++ b/backend/src/routes/api/groups-config/groupsConfigUtil.ts @@ -1,3 +1,9 @@ +/** + * @fileOverview + * @deprecated + * The Whole file is deprecated. The exposed functions of the module are deprecated to help. + * We are moving to direct k8s auth control and doing away with the OdhDashboardConfig group info. + */ import { FastifyRequest } from 'fastify'; import createError from 'http-errors'; import { @@ -13,6 +19,7 @@ import { isUserAdmin } from '../../../utils/adminUtils'; const SYSTEM_AUTHENTICATED = 'system:authenticated'; +/** @deprecated - see RHOAIENG-16988 */ export const getGroupsConfig = async (fastify: KubeFastifyInstance): Promise => { const { customObjectsApi } = fastify.kube; @@ -27,6 +34,7 @@ export const getGroupsConfig = async (fastify: KubeFastifyInstance): Promise groupStatus.filter((group) => group.enabled).map((group) => group.name); +/** @deprecated - see RHOAIENG-16988 */ export const updateGroupsConfig = async ( fastify: KubeFastifyInstance, request: FastifyRequest<{ Body: GroupsConfig }>, @@ -151,6 +159,7 @@ const getError = ( }; /** + * @deprecated - see RHOAIENG-16988 * Check if any selected groups has been deleted and update the configuration if so * @param fastify Fastify instance * @param groupsData Custom Resource Data for group configuration diff --git a/backend/src/routes/api/groups-config/index.ts b/backend/src/routes/api/groups-config/index.ts index 551b249eac..4da9193d89 100644 --- a/backend/src/routes/api/groups-config/index.ts +++ b/backend/src/routes/api/groups-config/index.ts @@ -1,9 +1,14 @@ +/** + * @fileOverview + * @deprecated see RHOAIENG-16988 + */ import { FastifyRequest } from 'fastify'; import { GroupsConfig, KubeFastifyInstance } from '../../../types'; import { getGroupsConfig, updateGroupsConfig } from './groupsConfigUtil'; import { secureAdminRoute } from '../../../utils/route-security'; export default async (fastify: KubeFastifyInstance): Promise => { + /** @deprecated - see RHOAIENG-16988 */ fastify.get( '/', secureAdminRoute(fastify)(async (request, reply) => { @@ -16,6 +21,7 @@ export default async (fastify: KubeFastifyInstance): Promise => { }), ); + /** @deprecated - see RHOAIENG-16988 */ fastify.put( '/', secureAdminRoute(fastify)(async (request: FastifyRequest<{ Body: GroupsConfig }>, reply) => { diff --git a/backend/src/types.ts b/backend/src/types.ts index 59a132bd14..6a0b19ba75 100644 --- a/backend/src/types.ts +++ b/backend/src/types.ts @@ -46,8 +46,11 @@ export type DashboardConfig = K8sResourceCommon & { disableStorageClasses: boolean; disableNIMModelServing: boolean; }; + /** @deprecated -- replacing this with Platform Auth resource -- remove when this is no longer in the CRD */ groupsConfig?: { + /** @deprecated -- see above */ adminGroups: string; + /** @deprecated -- see above */ allowedGroups: string; }; notebookSizes?: NotebookSize[]; @@ -1261,3 +1264,14 @@ export type ResourceAccessReviewResponse = { groups?: string[]; users?: string[]; }; + +export type AuthKind = K8sResourceCommon & { + metadata: { + name: 'auth'; // singleton, immutable name + namespace: never; // Cluster resource + }; + spec: { + adminGroups: string[]; + allowedGroups: string[]; + }; +}; diff --git a/backend/src/utils/adminUtils.ts b/backend/src/utils/adminUtils.ts index e42a0c7848..519d3496d1 100644 --- a/backend/src/utils/adminUtils.ts +++ b/backend/src/utils/adminUtils.ts @@ -7,6 +7,7 @@ import { KubeFastifyInstance, ResourceAccessReviewResponse } from '../types'; import { getAdminGroups, getAllGroupsByUser, getAllowedGroups, getGroup } from './groupsUtils'; import { flatten, uniq } from 'lodash'; import { getNamespaces } from '../utils/notebookUtils'; +import { getAuth } from './resourceUtils'; const SYSTEM_AUTHENTICATED = 'system:authenticated'; /** Usernames with invalid characters can start with `b64:` to keep their unwanted characters */ @@ -24,6 +25,12 @@ const getGroupUserList = async ( }; export const getAdminUserList = async (fastify: KubeFastifyInstance): Promise => { + const auth = getAuth(); + if (auth) { + return getGroupUserList(fastify, auth.spec.adminGroups); + } + + // FIXME: see RHOAIENG-16988 const adminGroups = getAdminGroups(); const adminGroupsList = adminGroups .split(',') @@ -61,6 +68,12 @@ export const getClusterAdminUserList = async (fastify: KubeFastifyInstance): Pro }; export const getAllowedUserList = async (fastify: KubeFastifyInstance): Promise => { + const auth = getAuth(); + if (auth) { + return getGroupUserList(fastify, auth.spec.allowedGroups); + } + + // FIXME: see RHOAIENG-16988 const allowedGroups = getAllowedGroups(); const allowedGroupList = allowedGroups .split(',') @@ -74,8 +87,16 @@ export const getGroupsConfig = async ( username: string, ): Promise => { try { - const adminGroups = getAdminGroups(); - const adminGroupsList = adminGroups.split(','); + const auth = getAuth(); + + let adminGroupsList: string[]; + if (auth) { + adminGroupsList = auth.spec.adminGroups; + } else { + // FIXME: see RHOAIENG-16988 + const adminGroups = getAdminGroups(); + adminGroupsList = adminGroups.split(','); + } if (adminGroupsList.includes(SYSTEM_AUTHENTICATED)) { throw new Error('It is not allowed to set system:authenticated as admin group.'); @@ -102,8 +123,17 @@ export const isUserAllowed = async ( username: string, ): Promise => { try { - const allowedGroups = getAllowedGroups(); - const allowedGroupsList = allowedGroups.split(','); + const auth = getAuth(); + + let allowedGroupsList: string[]; + if (auth) { + allowedGroupsList = auth.spec.allowedGroups; + } else { + // FIXME: see RHOAIENG-16988 + const allowedGroups = getAllowedGroups(); + allowedGroupsList = allowedGroups.split(','); + } + if (allowedGroupsList.includes(SYSTEM_AUTHENTICATED)) { return true; } else { diff --git a/backend/src/utils/groupsUtils.ts b/backend/src/utils/groupsUtils.ts index 2cfc266b7a..be738b7316 100644 --- a/backend/src/utils/groupsUtils.ts +++ b/backend/src/utils/groupsUtils.ts @@ -15,6 +15,7 @@ export class MissingGroupError extends Error { } } +/** @deprecated - see RHOAIENG-16988 */ export const getGroupsCR = (): GroupsConfigBody => { if (getDashboardConfig().spec.groupsConfig) { return getDashboardConfig().spec.groupsConfig; @@ -22,6 +23,7 @@ export const getGroupsCR = (): GroupsConfigBody => { throw new Error(`Failed to retrieve Dashboard CR groups configuration`); }; +/** @deprecated - see RHOAIENG-16988 */ export const updateGroupsCR = async ( fastify: KubeFastifyInstance, groupsConfigBody: GroupsConfigBody, @@ -38,6 +40,7 @@ export const updateGroupsCR = async ( } }; +/** @deprecated - see RHOAIENG-16988 */ export const getAdminGroups = (): string => { try { return getGroupsCR().adminGroups; @@ -46,6 +49,7 @@ export const getAdminGroups = (): string => { } }; +/** @deprecated - see RHOAIENG-16988 */ export const getAllowedGroups = (): string => { try { return getGroupsCR().allowedGroups; @@ -71,6 +75,7 @@ export const getGroup = async ( } }; +/** @deprecated - see RHOAIENG-16988 */ export const getAllGroups = async (customObjectsApi: CustomObjectsApi): Promise => { try { const adminGroupResponse = await customObjectsApi.listClusterCustomObject( diff --git a/backend/src/utils/resourceUtils.ts b/backend/src/utils/resourceUtils.ts index 2ac5ed2c63..067c31f54c 100644 --- a/backend/src/utils/resourceUtils.ts +++ b/backend/src/utils/resourceUtils.ts @@ -22,6 +22,7 @@ import { TolerationOperator, DataScienceClusterKindStatus, KnownLabels, + AuthKind, } from '../types'; import { DEFAULT_ACTIVE_TIMEOUT, @@ -50,6 +51,7 @@ const quickStartsVersion = 'v1'; const quickStartsPlural = 'odhquickstarts'; let dashboardConfigWatcher: ResourceWatcher; +let authWatcher: ResourceWatcher; let clusterStatusWatcher: ResourceWatcher; let subscriptionWatcher: ResourceWatcher; let appWatcher: ResourceWatcher; @@ -549,8 +551,16 @@ const fetchConsoleLinks = async (fastify: KubeFastifyInstance) => { }); }; +const fetchAuthKind = (fastify: KubeFastifyInstance): Promise => { + return fastify.kube.customObjectsApi + .getClusterCustomObject('services.platform.opendatahub.io', 'v1alpha1', 'auths', 'auth') + .then((response) => response.body as AuthKind) + .then((auth) => [auth]); +}; + export const initializeWatchedResources = (fastify: KubeFastifyInstance): void => { dashboardConfigWatcher = new ResourceWatcher(fastify, fetchDashboardCR); + authWatcher = new ResourceWatcher(fastify, fetchAuthKind); clusterStatusWatcher = new ResourceWatcher( fastify, fetchWatchedClusterStatus, @@ -627,6 +637,10 @@ export const getApplication = (appName: string): OdhApplication => { return apps.find((app) => app.metadata.name === appName); }; +export const getAuth = (): AuthKind => { + return authWatcher.getResources()[0]; +}; + export const getDocs = (): OdhDocument[] => { return docWatcher.getResources(); }; diff --git a/frontend/src/__mocks__/mockGroupConfig.ts b/frontend/src/__mocks__/mockGroupConfig.ts index aa29a91add..de533e7a25 100644 --- a/frontend/src/__mocks__/mockGroupConfig.ts +++ b/frontend/src/__mocks__/mockGroupConfig.ts @@ -1,4 +1,4 @@ -import { GroupsConfig } from '~/pages/groupSettings/groupTypes'; +import { GroupsConfig } from '~/concepts/userConfigs/groupTypes'; export const mockGroupSettings = (): GroupsConfig => ({ adminGroups: [ diff --git a/frontend/src/__tests__/cypress/cypress/support/commands/odh.ts b/frontend/src/__tests__/cypress/cypress/support/commands/odh.ts index 2e5d2c4662..02719d7168 100644 --- a/frontend/src/__tests__/cypress/cypress/support/commands/odh.ts +++ b/frontend/src/__tests__/cypress/cypress/support/commands/odh.ts @@ -24,7 +24,7 @@ import type { import type { StartNotebookData } from '~/pages/projects/types'; import type { AllowedUser } from '~/pages/notebookController/screens/admin/types'; -import type { GroupsConfig } from '~/pages/groupSettings/groupTypes'; +import type { GroupsConfig } from '~/concepts/userConfigs/groupTypes'; import type { StatusResponse } from '~/redux/types'; import type { BYONImage, diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index ceee524c0f..117a6518b7 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -1,4 +1,5 @@ // Normal SDK/pass-through network API calls +export * from './k8s/auth'; export * from './k8s/builds'; export * from './k8s/configMaps'; export * from './k8s/events'; diff --git a/frontend/src/api/k8s/auth.ts b/frontend/src/api/k8s/auth.ts new file mode 100644 index 0000000000..0d1da4a978 --- /dev/null +++ b/frontend/src/api/k8s/auth.ts @@ -0,0 +1,34 @@ +import { k8sGetResource, k8sPatchResource, Patch } from '@openshift/dynamic-plugin-sdk-utils'; +import { AuthModel } from '~/api'; +import { AuthKind } from '~/k8sTypes'; + +export const AUTH_SINGLETON_NAME = 'auth'; + +export const getAuthResource = (): Promise => + k8sGetResource({ model: AuthModel, queryOptions: { name: AUTH_SINGLETON_NAME } }); + +export type GroupData = { adminGroups?: string[]; allowedGroups?: string[] }; +export const patchAuthResource = (groupData: GroupData): Promise => { + const { adminGroups, allowedGroups } = groupData; + const patches: Patch[] = []; + if (adminGroups) { + patches.push({ + value: adminGroups, + op: 'replace', + path: '/spec/adminGroups', + }); + } + if (allowedGroups) { + patches.push({ + value: allowedGroups, + op: 'replace', + path: '/spec/allowedGroups', + }); + } + + return k8sPatchResource({ + model: AuthModel, + queryOptions: { name: AUTH_SINGLETON_NAME }, + patches, + }); +}; diff --git a/frontend/src/api/models/odh.ts b/frontend/src/api/models/odh.ts index daf18aef99..95993cb863 100644 --- a/frontend/src/api/models/odh.ts +++ b/frontend/src/api/models/odh.ts @@ -41,3 +41,10 @@ export const InferenceServiceModel: K8sModelCommon = { kind: 'InferenceService', plural: 'inferenceservices', }; + +export const AuthModel: K8sModelCommon = { + apiVersion: 'v1alpha1', + apiGroup: 'services.platform.opendatahub.io', + kind: 'Auth', + plural: 'auths', +}; diff --git a/frontend/src/pages/groupSettings/groupTypes.ts b/frontend/src/concepts/userConfigs/groupTypes.ts similarity index 71% rename from frontend/src/pages/groupSettings/groupTypes.ts rename to frontend/src/concepts/userConfigs/groupTypes.ts index e399469ffd..d1cbc86986 100644 --- a/frontend/src/pages/groupSettings/groupTypes.ts +++ b/frontend/src/concepts/userConfigs/groupTypes.ts @@ -1,6 +1,9 @@ -export type GroupsConfig = { +export type GroupValues = { adminGroups: GroupStatus[]; allowedGroups: GroupStatus[]; +}; + +export type GroupsConfig = GroupValues & { errorAdmin?: string; errorUser?: string; }; @@ -11,7 +14,7 @@ export enum GroupsConfigField { } export type GroupStatus = { - id: number; + id: number | string; name: string; enabled: boolean; }; diff --git a/frontend/src/concepts/userConfigs/useWatchGroups.tsx b/frontend/src/concepts/userConfigs/useWatchGroups.tsx new file mode 100644 index 0000000000..bffa974146 --- /dev/null +++ b/frontend/src/concepts/userConfigs/useWatchGroups.tsx @@ -0,0 +1,147 @@ +import * as React from 'react'; +import { GroupsConfig } from '~/concepts/userConfigs/groupTypes'; +import { fetchGroupsSettings, updateGroupsSettings } from '~/services/groupSettingsService'; +import { + fetchAuthGroups, + updateAuthGroups, + useDoesUserHaveAuthAccess, +} from '~/concepts/userConfigs/utils'; +import useNotification from '~/utilities/useNotification'; +import { useGroups } from '~/api'; + +export const useWatchGroups = (): { + groupSettings: GroupsConfig; + loaded: boolean; + isLoading: boolean; + isGroupSettingsChanged: boolean; + loadError: Error | undefined; + updateGroups: (group: GroupsConfig) => void; + setGroupSettings: (group: GroupsConfig) => void; + setIsGroupSettingsChanged: (changed: boolean) => void; +} => { + const [loaded, setLoaded] = React.useState(false); + const [loadError, setLoadError] = React.useState(undefined); + const [isLoading, setIsLoading] = React.useState(false); + const [isGroupSettingsChanged, setIsGroupSettingsChanged] = React.useState(false); + const notification = useNotification(); + const [groupSettings, setGroupSettings] = React.useState({ + adminGroups: [], + allowedGroups: [], + }); + const { errorAdmin, errorUser } = groupSettings; + const [canUpdateAuthResource, isAuthCheckDone] = useDoesUserHaveAuthAccess(); + const [groupsData, groupsDataLoaded] = useGroups(); + + const hasDirectAccessDataLoaded = groupsDataLoaded && isAuthCheckDone; + + React.useEffect(() => { + const fetchGroups = () => { + setIsLoading(true); + if (canUpdateAuthResource) { + fetchAuthGroups(groupsData) + .then(({ allowedGroups, adminGroups }) => { + setGroupSettings({ allowedGroups, adminGroups }); + setLoadError(undefined); + setLoaded(true); + }) + .catch((error) => { + notification.error(`Error`, `Error getting group settings`); + setLoadError(error); + setLoaded(false); + }) + .finally(() => { + setIsLoading(false); + setIsGroupSettingsChanged(false); + }); + } else { + // TODO: This path should be deprecated in RHOAI 2.17 -- remove once we fully move to Auth + // eslint-disable-next-line no-console + console.log( + 'This user does not have access to update the Auth resource -- falling back on reading OdhDashboardConfig', + ); + fetchGroupsSettings() + .then((groupsResponse) => { + setGroupSettings(groupsResponse); + setLoadError(undefined); + setLoaded(true); + }) + .catch((error) => { + notification.error(`Error`, `Error updating group settings`); + setLoadError(error); + setLoaded(false); + }) + .finally(() => { + setIsLoading(false); + setIsGroupSettingsChanged(false); + }); + } + }; + + if (hasDirectAccessDataLoaded) { + fetchGroups(); + } + }, [notification, canUpdateAuthResource, hasDirectAccessDataLoaded, groupsData]); + + React.useEffect(() => { + if (errorAdmin) { + notification.error(`Group error`, errorAdmin); + } + if (errorUser) { + notification.error(`Group error`, errorUser); + } + }, [errorAdmin, errorUser, notification]); + + const updateGroups = React.useCallback( + (group: GroupsConfig) => { + setIsLoading(true); + if (canUpdateAuthResource) { + updateAuthGroups(group, groupsData) + .then((groupsResponse) => { + setGroupSettings(groupsResponse); + }) + .catch((error) => { + setLoadError(error); + setLoaded(false); + }) + .finally(() => { + setIsLoading(false); + setIsGroupSettingsChanged(false); + }); + } else { + // TODO: This path should be deprecated in RHOAI 2.17 -- remove once we fully move to Auth + // eslint-disable-next-line no-console + console.log( + 'This user does not have access to update the Auth resource -- falling back on updating OdhDashboardConfig', + ); + updateGroupsSettings(group) + .then((response) => { + setGroupSettings(response); + notification.success( + 'Group settings changes saved', + 'It may take up to 2 minutes for configuration changes to be applied.', + ); + }) + .catch((error) => { + setLoadError(error); + setLoaded(false); + }) + .finally(() => { + setIsLoading(false); + setIsGroupSettingsChanged(false); + }); + } + }, + [canUpdateAuthResource, groupsData, notification], + ); + + return { + groupSettings, + loaded, + isLoading, + isGroupSettingsChanged, + loadError, + updateGroups, + setGroupSettings, + setIsGroupSettingsChanged, + }; +}; diff --git a/frontend/src/concepts/userConfigs/utils.ts b/frontend/src/concepts/userConfigs/utils.ts new file mode 100644 index 0000000000..d0a04f3f9b --- /dev/null +++ b/frontend/src/concepts/userConfigs/utils.ts @@ -0,0 +1,63 @@ +import { AccessReviewResourceAttributes, AuthKind, GroupKind } from '~/k8sTypes'; +import { + AUTH_SINGLETON_NAME, + AuthModel, + getAuthResource, + patchAuthResource, + useAccessReview, +} from '~/api'; +import { GroupValues } from './groupTypes'; + +const authCheck: AccessReviewResourceAttributes = { + group: AuthModel.apiGroup, + resource: AuthModel.plural, + name: AUTH_SINGLETON_NAME, + verb: 'update', // If they can get the data but not updated, we can assume they cannot access it +}; + +export const useDoesUserHaveAuthAccess = (): ReturnType => + useAccessReview(authCheck); + +const ALL_USERS = 'system:authenticated'; + +const compileGroupValues = ( + adminGroups: string[], + allowedGroups: string[], + groups: GroupKind[], +): GroupValues => ({ + allowedGroups: [ + ...groups.map((group) => ({ + id: group.metadata.name, + name: group.metadata.name, + enabled: allowedGroups.includes(group.metadata.name), + })), + { id: ALL_USERS, name: ALL_USERS, enabled: allowedGroups.includes(ALL_USERS) }, + ], + adminGroups: groups.map((group) => ({ + id: group.metadata.name, + name: group.metadata.name, + enabled: adminGroups.includes(group.metadata.name), + })), +}); +const convertAuthToGroupValues = (auth: AuthKind, groups: GroupKind[]): GroupValues => { + const { + spec: { adminGroups, allowedGroups }, + } = auth; + + return compileGroupValues(adminGroups, allowedGroups, groups); +}; + +export const fetchAuthGroups = async (groups: GroupKind[]): Promise => + getAuthResource().then((auth) => convertAuthToGroupValues(auth, groups)); + +export const updateAuthGroups = async ( + groupConfigs: GroupValues, + groups: GroupKind[], +): Promise => { + const adminGroups = groupConfigs.adminGroups.filter((g) => g.enabled).map((g) => g.name); + const allowedGroups = groupConfigs.allowedGroups.filter((g) => g.enabled).map((g) => g.name); + + return patchAuthResource({ adminGroups, allowedGroups }).then((auth) => + convertAuthToGroupValues(auth, groups), + ); +}; diff --git a/frontend/src/k8sTypes.ts b/frontend/src/k8sTypes.ts index e6e4cd51a8..3fcd416934 100644 --- a/frontend/src/k8sTypes.ts +++ b/frontend/src/k8sTypes.ts @@ -1338,3 +1338,14 @@ export type ModelRegistryKind = K8sResourceCommon & { conditions?: K8sCondition[]; }; }; + +export type AuthKind = K8sResourceCommon & { + metadata: { + name: 'auth'; // singleton, immutable name + namespace: never; // Cluster resource + }; + spec: { + adminGroups: string[]; + allowedGroups: string[]; + }; +}; diff --git a/frontend/src/pages/groupSettings/GroupSettings.tsx b/frontend/src/pages/groupSettings/GroupSettings.tsx index 2c05278acc..d662856b59 100644 --- a/frontend/src/pages/groupSettings/GroupSettings.tsx +++ b/frontend/src/pages/groupSettings/GroupSettings.tsx @@ -12,10 +12,10 @@ import ApplicationsPage from '~/pages/ApplicationsPage'; import { isGroupEmpty } from '~/utilities/utils'; import SettingSection from '~/components/SettingSection'; import { MultiSelection, SelectionOptions } from '~/components/MultiSelection'; -import { useWatchGroups } from '~/utilities/useWatchGroups'; +import { useWatchGroups } from '~/concepts/userConfigs/useWatchGroups'; import TitleWithIcon from '~/concepts/design/TitleWithIcon'; import { ProjectObjectType } from '~/concepts/design/utils'; -import { GroupsConfigField } from './groupTypes'; +import { GroupsConfigField } from '~/concepts/userConfigs/groupTypes'; const GroupSettings: React.FC = () => { const { @@ -45,7 +45,7 @@ const GroupSettings: React.FC = () => { setGroupSettings({ ...groupSettings, adminGroups: newState.map((opt) => ({ - id: Number(opt.id), + id: opt.id, name: opt.name, enabled: opt.selected || false, })), @@ -55,7 +55,7 @@ const GroupSettings: React.FC = () => { setGroupSettings({ ...groupSettings, allowedGroups: newState.map((opt) => ({ - id: Number(opt.id), + id: opt.id, name: opt.name, enabled: opt.selected || false, })), @@ -103,7 +103,7 @@ const GroupSettings: React.FC = () => { setValue={(newState) => handleMenuItemSelection(newState, GroupsConfigField.ADMIN)} selectionRequired noSelectedOptionsMessage="One or more group must be selected" - popperProps={{ appendTo: 'inline' }} + popperProps={{ appendTo: document.body }} /> {groupSettings.errorAdmin ? ( { setValue={(newState) => handleMenuItemSelection(newState, GroupsConfigField.USER)} selectionRequired noSelectedOptionsMessage="One or more group must be selected" - popperProps={{ appendTo: 'inline' }} + popperProps={{ appendTo: document.body }} /> {groupSettings.errorUser ? ( => { const url = '/api/groups-config'; return axios @@ -11,6 +15,10 @@ export const fetchGroupsSettings = (): Promise => { }); }; +/** + * @deprecated Use Auth Resource instead + * @see updateAuthGroups + */ export const updateGroupsSettings = (settings: GroupsConfig): Promise => { const url = '/api/groups-config'; return axios diff --git a/frontend/src/utilities/__tests__/useWatchGroups.spec.tsx b/frontend/src/utilities/__tests__/useWatchGroups.spec.tsx index aa487f6b03..c853329f5f 100644 --- a/frontend/src/utilities/__tests__/useWatchGroups.spec.tsx +++ b/frontend/src/utilities/__tests__/useWatchGroups.spec.tsx @@ -1,8 +1,8 @@ import { act } from 'react'; import { testHook } from '~/__tests__/unit/testUtils/hooks'; -import { GroupsConfig } from '~/pages/groupSettings/groupTypes'; +import { GroupsConfig } from '~/concepts/userConfigs/groupTypes'; import { fetchGroupsSettings, updateGroupsSettings } from '~/services/groupSettingsService'; -import { useWatchGroups } from '~/utilities/useWatchGroups'; +import { useWatchGroups } from '~/concepts/userConfigs/useWatchGroups'; import useNotification from '~/utilities/useNotification'; jest.mock('~/services/groupSettingsService', () => ({ diff --git a/frontend/src/utilities/useWatchGroups.tsx b/frontend/src/utilities/useWatchGroups.tsx deleted file mode 100644 index 4b2ec57418..0000000000 --- a/frontend/src/utilities/useWatchGroups.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import * as React from 'react'; -import { GroupsConfig } from '~/pages/groupSettings/groupTypes'; -import { fetchGroupsSettings, updateGroupsSettings } from '~/services/groupSettingsService'; -import useNotification from './useNotification'; - -export const useWatchGroups = (): { - groupSettings: GroupsConfig; - loaded: boolean; - isLoading: boolean; - isGroupSettingsChanged: boolean; - loadError: Error | undefined; - updateGroups: (group: GroupsConfig) => void; - setGroupSettings: (group: GroupsConfig) => void; - setIsGroupSettingsChanged: (changed: boolean) => void; -} => { - const [loaded, setLoaded] = React.useState(false); - const [loadError, setLoadError] = React.useState(undefined); - const [isLoading, setIsLoading] = React.useState(false); - const [isGroupSettingsChanged, setIsGroupSettingsChanged] = React.useState(false); - const notification = useNotification(); - const [groupSettings, setGroupSettings] = React.useState({ - adminGroups: [], - allowedGroups: [], - }); - const { errorAdmin, errorUser } = groupSettings; - - React.useEffect(() => { - const fetchGroups = () => { - setIsLoading(true); - fetchGroupsSettings() - .then((groupsResponse) => { - setGroupSettings(groupsResponse); - setLoadError(undefined); - setLoaded(true); - }) - .catch((error) => { - notification.error(`Error`, `Error updating group settings`); - setLoadError(error); - setLoaded(false); - }) - .finally(() => { - setIsLoading(false); - setIsGroupSettingsChanged(false); - }); - }; - fetchGroups(); - }, [notification]); - - React.useEffect(() => { - if (errorAdmin) { - notification.error(`Group error`, errorAdmin); - } - if (errorUser) { - notification.error(`Group error`, errorUser); - } - }, [errorAdmin, errorUser, notification]); - - const updateGroups = React.useCallback( - (group: GroupsConfig) => { - setIsLoading(true); - updateGroupsSettings(group) - .then((response) => { - setGroupSettings(response); - notification.success( - 'Group settings changes saved', - 'It may take up to 2 minutes for configuration changes to be applied.', - ); - }) - .catch((error) => { - setLoadError(error); - setLoaded(false); - }) - .finally(() => { - setIsLoading(false); - setIsGroupSettingsChanged(false); - }); - }, - [notification], - ); - - return { - groupSettings, - loaded, - isLoading, - isGroupSettingsChanged, - loadError, - updateGroups, - setGroupSettings, - setIsGroupSettingsChanged, - }; -}; diff --git a/manifests/core-bases/base/cluster-role.yaml b/manifests/core-bases/base/cluster-role.yaml index d2c172e765..2ec2c00b34 100644 --- a/manifests/core-bases/base/cluster-role.yaml +++ b/manifests/core-bases/base/cluster-role.yaml @@ -219,3 +219,9 @@ rules: - '' resources: - endpoints + - verbs: + - get + apiGroups: + - 'services.platform.opendatahub.io' + resources: + - auths