From a797027c7e95e8fe1a34c803b29b3f04b26befcf Mon Sep 17 00:00:00 2001 From: Davor Date: Tue, 21 Jun 2022 11:54:07 +0200 Subject: [PATCH 1/9] add calls to new 'me' endpoint --- src/components/UserModal/UserModal.tsx | 27 ++++++++++++--- src/services/users/hooks/use-users.ts | 12 +++++++ src/services/users/redux/actions.ts | 48 ++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 4 deletions(-) diff --git a/src/components/UserModal/UserModal.tsx b/src/components/UserModal/UserModal.tsx index 00fcb9b..a851fad 100644 --- a/src/components/UserModal/UserModal.tsx +++ b/src/components/UserModal/UserModal.tsx @@ -11,8 +11,18 @@ import { UserModalProps } from './types'; export const UserModal = ({ open, onClose, userId, setUserId }: UserModalProps) => { const [deleteModal, setDeleteModal] = useState(false); - const { user, loadUser, editUserById, createNewUser, userModalLoading, deleteUserById, clearSelectedUser } = - useUsers(); + const [isPersonalModal, setPersonalModal] = useState(false); + const { + user, + loadUser, + loadPersonalInfo, + editUserById, + editPersonalInfo, + createNewUser, + userModalLoading, + deleteUserById, + clearSelectedUser, + } = useUsers(); const { currentUser, isAdmin } = useAuth(); const { control, reset, handleSubmit } = useForm({ @@ -26,7 +36,13 @@ export const UserModal = ({ open, onClose, userId, setUserId }: UserModalProps) useEffect(() => { if (userId) { - loadUser(userId); + const currentUserId = currentUser?.id; + if (currentUserId === userId) { + setPersonalModal(true); + loadPersonalInfo(); + } else { + loadUser(userId); + } } reset(initialUserForm); @@ -45,7 +61,9 @@ export const UserModal = ({ open, onClose, userId, setUserId }: UserModalProps) const handleSave = async () => { try { - if (userId) { + if (isPersonalModal) { + await handleSubmit((data) => editPersonalInfo(data))(); + } else if (userId) { await handleSubmit((data) => editUserById(data))(); } else { await handleSubmit((data) => createNewUser(data))(); @@ -134,6 +152,7 @@ export const UserModal = ({ open, onClose, userId, setUserId }: UserModalProps) name={`app_roles.${index}.role`} label="Role" options={[ + { value: UserRole.NoAccess, name: 'No Access' }, { value: UserRole.User, name: 'User' }, { value: UserRole.Admin, name: 'Admin' }, ]} diff --git a/src/services/users/hooks/use-users.ts b/src/services/users/hooks/use-users.ts index 0e5dd35..75febdc 100644 --- a/src/services/users/hooks/use-users.ts +++ b/src/services/users/hooks/use-users.ts @@ -3,7 +3,9 @@ import { getUsers, fetchUsers, fetchUserById, + fetchPersonalInfo, updateUserById, + updatePersonalInfo, createUser, deleteUser, clearCurrentUser, @@ -25,6 +27,10 @@ export function useUsers() { return dispatch(fetchUserById(id)); } + function loadPersonalInfo() { + return dispatch(fetchPersonalInfo()); + } + function clearSelectedUser() { return dispatch(clearCurrentUser()); } @@ -33,6 +39,10 @@ export function useUsers() { return dispatch(updateUserById(data)); } + function editPersonalInfo(data: any) { + return dispatch(updatePersonalInfo(data)); + } + function createNewUser(data: any) { return dispatch(createUser(data)); } @@ -46,7 +56,9 @@ export function useUsers() { user, loadUser, loadUsers, + loadPersonalInfo, editUserById, + editPersonalInfo, userModalLoading, userTableLoading, createNewUser, diff --git a/src/services/users/redux/actions.ts b/src/services/users/redux/actions.ts index 7c35034..3f78b8c 100644 --- a/src/services/users/redux/actions.ts +++ b/src/services/users/redux/actions.ts @@ -69,6 +69,26 @@ export const fetchUserById = (id: string) => async (dispatch: Dispatch) => dispatch(setUserModalLoading(false)); }; +export const fetchPersonalInfo = () => async (dispatch: Dispatch) => { + dispatch(setUserModalLoading(true)); + + try { + const { data } = await performApiCall({ + path: '/me', + method: 'GET', + }); + + dispatch({ + type: UserActionTypes.FETCH_USER, + payload: transformUser(data), + }); + } catch (err) { + console.error(err); + } + + dispatch(setUserModalLoading(false)); +}; + export const updateUserById = (user: any) => async (dispatch: Dispatch, getState: any) => { dispatch(setUserModalLoading(true)); @@ -103,6 +123,34 @@ export const updateUserById = (user: any) => async (dispatch: Dispatch, get dispatch(setUserModalLoading(false)); }; +export const updatePersonalInfo = (user: any) => async (dispatch: Dispatch, getState: any) => { + dispatch(setUserModalLoading(true)); + + try { + const { data } = await performApiCall({ + path: '/me', + method: 'PUT', + body: transformRequestUser(user), + }); + + dispatch({ + type: UserActionTypes.UPDATE_USER, + payload: transformUser(data), + }); + + dispatch({ + type: AuthActionTypes.UPDATE_AUTH_USER, + payload: transformUser(data), + }); + + showToast('Personal information updated successfully.', ToastType.Success); + } catch (err) { + console.error(err); + } + + dispatch(setUserModalLoading(false)); +}; + export const createUser = (user: any) => async (dispatch: Dispatch) => { dispatch(setUserModalLoading(true)); From 354d77a3a72ae7f87c5d71d150a7a1b993274cdf Mon Sep 17 00:00:00 2001 From: Davor Date: Wed, 6 Jul 2022 20:26:48 +0200 Subject: [PATCH 2/9] Grey out app section if chosen dashboard role is admin --- src/components/UserModal/UserModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/UserModal/UserModal.tsx b/src/components/UserModal/UserModal.tsx index 644d536..aa25589 100644 --- a/src/components/UserModal/UserModal.tsx +++ b/src/components/UserModal/UserModal.tsx @@ -213,8 +213,8 @@ export const UserModal = ({ open, onClose, userId, setUserId }: UserModalProps) )}
-
-
    +
    +
      {fields.map((item, index) => { if (item.name === 'dashboard') { return null; From 4e259038478d1fb63bff4e0f6cd678cc833f06c0 Mon Sep 17 00:00:00 2001 From: Davor Date: Wed, 6 Jul 2022 20:40:56 +0200 Subject: [PATCH 3/9] fix build, remove unused --- src/services/users/redux/actions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/users/redux/actions.ts b/src/services/users/redux/actions.ts index 3f78b8c..1b59cc2 100644 --- a/src/services/users/redux/actions.ts +++ b/src/services/users/redux/actions.ts @@ -123,7 +123,7 @@ export const updateUserById = (user: any) => async (dispatch: Dispatch, get dispatch(setUserModalLoading(false)); }; -export const updatePersonalInfo = (user: any) => async (dispatch: Dispatch, getState: any) => { +export const updatePersonalInfo = (user: any) => async (dispatch: Dispatch) => { dispatch(setUserModalLoading(true)); try { From d4c4c6104f6c5e6d22a11f6a7a692a1262518bb9 Mon Sep 17 00:00:00 2001 From: Davor Date: Wed, 13 Jul 2022 12:05:20 +0200 Subject: [PATCH 4/9] hide app access when selected dashboard role is admin --- src/components/UserModal/UserModal.tsx | 87 ++++++++++++++------------ 1 file changed, 46 insertions(+), 41 deletions(-) diff --git a/src/components/UserModal/UserModal.tsx b/src/components/UserModal/UserModal.tsx index aa25589..2828a29 100644 --- a/src/components/UserModal/UserModal.tsx +++ b/src/components/UserModal/UserModal.tsx @@ -11,6 +11,7 @@ import { UserModalProps } from './types'; export const UserModal = ({ open, onClose, userId, setUserId }: UserModalProps) => { const [deleteModal, setDeleteModal] = useState(false); + const [isAdminRoleSelected, setAdminRoleSelected] = useState(true); const [isPersonalModal, setPersonalModal] = useState(false); const { user, @@ -63,7 +64,9 @@ export const UserModal = ({ open, onClose, userId, setUserId }: UserModalProps) }); useEffect(() => { - if (dashboardRole === UserRole.Admin) { + const isAdminDashboardRoleSelected = dashboardRole === UserRole.Admin; + setAdminRoleSelected(isAdminDashboardRoleSelected); + if (isAdminDashboardRoleSelected) { fields.forEach((field, index) => update(index, { name: field.name, role: UserRole.Admin })); } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -197,13 +200,13 @@ export const UserModal = ({ open, onClose, userId, setUserId }: UserModalProps)
- {isAdmin && ( + {isAdmin && !userModalLoading && (

App Access

- {dashboardRole === UserRole.Admin && ( + {isAdminRoleSelected && (
)} -
-
-
    - {fields.map((item, index) => { - if (item.name === 'dashboard') { - return null; - } + {!isAdminRoleSelected && ( +
    +
    +
      + {fields.map((item, index) => { + if (item.name === 'dashboard') { + return null; + } - return ( -
    • -
      -
      - {item.name -

      - {_.find(appAccessList, ['name', item.name!])?.label} -

      + return ( +
    • +
      +
      + {item.name +

      + {_.find(appAccessList, ['name', item.name!])?.label} +

      +
      +
      + -
      -
      -
    • - ); - })} -
    + + ); + })} +
+
-
+ )}
)}
From 45a9033299493eab228d87a0bba877f3b458ff24 Mon Sep 17 00:00:00 2001 From: Luka Radenovic Date: Wed, 13 Jul 2022 14:32:34 +0200 Subject: [PATCH 5/9] Fix logout link to be dynamically generated --- .env.example | 1 - src/components/Header/Header.tsx | 14 ++++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.env.example b/.env.example index ce5f179..c32c7c4 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1 @@ REACT_APP_API_URL=http://stackspin_proxy:8081/api/v1 -REACT_APP_HYDRA_PUBLIC_URL=https://sso.init.stackspin.net diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index 29cc3c5..9428ec8 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -1,4 +1,4 @@ -import React, { Fragment, useState } from 'react'; +import React, { Fragment, useMemo, useState } from 'react'; import { Disclosure, Menu, Transition } from '@headlessui/react'; import { MenuIcon, XIcon } from '@heroicons/react/outline'; import { useAuth } from 'src/services/auth'; @@ -26,8 +26,6 @@ function filterNavigationByDashboardRole(isAdmin: boolean) { return navigation.filter((item) => !item.requiresAdmin); } -const HYDRA_URL = process.env.REACT_APP_HYDRA_PUBLIC_URL; - // eslint-disable-next-line @typescript-eslint/no-empty-interface interface HeaderProps {} @@ -50,7 +48,15 @@ const Header: React.FC = () => { const navigationItems = filterNavigationByDashboardRole(isAdmin); - const signOutUrl = `${HYDRA_URL}/oauth2/sessions/logout`; + // eslint-disable-next-line react-hooks/exhaustive-deps + const signOutUrl = useMemo(() => { + const { hostname } = window.location; + // If we are developing locally, we need to use the init cluster's public URL + if (hostname === 'localhost') { + return `https://sso.init.stackspin.net/oauth2/sessions/logout`; + } + return `https://sso.${hostname.replace('dashboard', '')}/oauth2/sessions/logout`; + }, []); return ( <> From 3f455aedd94296fb77c2ffe64dc8fe0ec15c14d9 Mon Sep 17 00:00:00 2001 From: Luka Radenovic Date: Wed, 13 Jul 2022 14:35:45 +0200 Subject: [PATCH 6/9] Cleanup singOutUrl --- src/components/Header/Header.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index 9428ec8..7e67730 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -48,14 +48,13 @@ const Header: React.FC = () => { const navigationItems = filterNavigationByDashboardRole(isAdmin); - // eslint-disable-next-line react-hooks/exhaustive-deps const signOutUrl = useMemo(() => { const { hostname } = window.location; // If we are developing locally, we need to use the init cluster's public URL if (hostname === 'localhost') { return `https://sso.init.stackspin.net/oauth2/sessions/logout`; } - return `https://sso.${hostname.replace('dashboard', '')}/oauth2/sessions/logout`; + return `https://${hostname.replace('dashboard', 'sso')}/oauth2/sessions/logout`; }, []); return ( From d0b86c08092742e0cba01daa332cab165f6189fa Mon Sep 17 00:00:00 2001 From: Luka Radenovic Date: Wed, 13 Jul 2022 14:59:15 +0200 Subject: [PATCH 7/9] Add HYDRA_PUBLIC_URL env variable and fix replace of sing out url --- .env.example | 1 + src/components/Header/Header.tsx | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index c32c7c4..f3afcc4 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,2 @@ REACT_APP_API_URL=http://stackspin_proxy:8081/api/v1 +REACT_APP_HYDRA_PUBLIC_URL=https://sso.init.stackspin.net \ No newline at end of file diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index 7e67730..97a24ed 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -9,6 +9,8 @@ import _ from 'lodash'; import { UserModal } from '../UserModal'; +const HYDRA_LOGOUT_URL = `${process.env.REACT_APP_HYDRA_PUBLIC_URL}/oauth2/sessions/logout`; + const navigation = [ { name: 'Dashboard', to: '/dashboard', requiresAdmin: false }, { name: 'Users', to: '/users', requiresAdmin: true }, @@ -52,9 +54,9 @@ const Header: React.FC = () => { const { hostname } = window.location; // If we are developing locally, we need to use the init cluster's public URL if (hostname === 'localhost') { - return `https://sso.init.stackspin.net/oauth2/sessions/logout`; + return HYDRA_LOGOUT_URL; } - return `https://${hostname.replace('dashboard', 'sso')}/oauth2/sessions/logout`; + return `https://${hostname.replace(/^dashboard/, 'sso')}/oauth2/sessions/logout`; }, []); return ( From b7c66a92308d7ce7462c1024f69e33d899927d76 Mon Sep 17 00:00:00 2001 From: Davor Date: Wed, 13 Jul 2022 16:05:56 +0200 Subject: [PATCH 8/9] remove No access role for dashboard --- src/components/UserModal/UserModal.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/UserModal/UserModal.tsx b/src/components/UserModal/UserModal.tsx index 2828a29..0993b94 100644 --- a/src/components/UserModal/UserModal.tsx +++ b/src/components/UserModal/UserModal.tsx @@ -172,7 +172,6 @@ export const UserModal = ({ open, onClose, userId, setUserId }: UserModalProps) name={`app_roles.${index}.role`} label="Role" options={[ - { value: UserRole.NoAccess, name: 'No Access' }, { value: UserRole.User, name: 'User' }, { value: UserRole.Admin, name: 'Admin' }, ]} From d5bd0326de6bede1a6cc3f371abcec4c1fcbec65 Mon Sep 17 00:00:00 2001 From: Maarten de Waard Date: Thu, 14 Jul 2022 10:35:33 +0200 Subject: [PATCH 9/9] Release chart version 1.1.0 --- deployment/helmchart/CHANGELOG.md | 11 +++++++++++ deployment/helmchart/Chart.yaml | 2 +- deployment/helmchart/values.yaml | 4 ++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/deployment/helmchart/CHANGELOG.md b/deployment/helmchart/CHANGELOG.md index 7cfd647..72b6183 100644 --- a/deployment/helmchart/CHANGELOG.md +++ b/deployment/helmchart/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## [1.1.0] + +### Bug fixes + +* Logging out of dashboard now calls SSO signout URL based on current domain + +### Features + +* Dashboard admin users automatically have admin rights in all apps +* App-specific rights for dashboard admin users are not editable + ## [1.0.5] ### Bug fixes diff --git a/deployment/helmchart/Chart.yaml b/deployment/helmchart/Chart.yaml index 4cefaa8..a0ba170 100644 --- a/deployment/helmchart/Chart.yaml +++ b/deployment/helmchart/Chart.yaml @@ -23,4 +23,4 @@ name: stackspin-dashboard sources: - https://open.greenhost.net/stackspin/dashboard/ - https://open.greenhost.net/stackspin/dashboard-backend/ -version: 1.0.5 +version: 1.1.0 diff --git a/deployment/helmchart/values.yaml b/deployment/helmchart/values.yaml index fff5256..01c48fb 100644 --- a/deployment/helmchart/values.yaml +++ b/deployment/helmchart/values.yaml @@ -68,7 +68,7 @@ dashboard: image: registry: open.greenhost.net:4567 repository: stackspin/dashboard/dashboard - tag: 0-2-6 + tag: 0-2-7 ## Optionally specify an array of imagePullSecrets. ## Secrets must be manually created in the namespace. ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ @@ -235,7 +235,7 @@ backend: image: registry: open.greenhost.net:4567 repository: stackspin/dashboard-backend/dashboard-backend - tag: 0-2-7 + tag: 0-2-8 ## Optionally specify an array of imagePullSecrets. ## Secrets must be manually created in the namespace. ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/