diff --git a/src/api/projects.tsx b/src/api/projects.tsx index 502e85ffcb..93f0434cf0 100644 --- a/src/api/projects.tsx +++ b/src/api/projects.tsx @@ -13,6 +13,7 @@ const projectEntitlements = [ "can_create_storage_volumes", "can_delete", "can_edit", + "can_view_events", ]; export const fetchProjects = async ( diff --git a/src/components/Navigation.tsx b/src/components/Navigation.tsx index 61732c3ced..1309d181ff 100644 --- a/src/components/Navigation.tsx +++ b/src/components/Navigation.tsx @@ -18,6 +18,7 @@ import { enablePermissionsFeature } from "util/permissions"; import type { Location } from "react-router-dom"; import { useLocation } from "react-router-dom"; import { useLoggedInUser } from "context/useLoggedInUser"; +import ProjectPermissionWarning from "pages/projects/ProjectPermissionWarning"; const isSmallScreen = () => isWidthBelow(620); @@ -473,6 +474,7 @@ const Navigation: FC = () => { <> )}
{loggedInUserName}
+ )} diff --git a/src/components/NoProject.tsx b/src/components/NoProject.tsx index 81647fc766..f604eb403d 100644 --- a/src/components/NoProject.tsx +++ b/src/components/NoProject.tsx @@ -4,30 +4,29 @@ import CustomLayout from "./CustomLayout"; const NoProject: FC = () => { const url = location.pathname; - const project = url.startsWith("/ui/project/") - ? url.split("/")[3] - : "default"; + const hasProjectInUrl = url.startsWith("/ui/project/"); + const project = hasProjectInUrl ? url.split("/")[3] : "default"; return ( -

Project not found

-

- The project {project} is missing or you do not have - access. -
- If you think this is an error in our product, please{" "} - - Report a bug - - . -

+ {hasProjectInUrl ? ( + <> +

Project not found

+

+ The project {project} is missing or you do not have + the viewer permission for it. +

+ + ) : ( + <> +

Permission denied

+

+ You do not have permission to view any project on this server. +

+ + )}
diff --git a/src/pages/profiles/ProfileList.tsx b/src/pages/profiles/ProfileList.tsx index c554788408..a8483840d9 100644 --- a/src/pages/profiles/ProfileList.tsx +++ b/src/pages/profiles/ProfileList.tsx @@ -3,6 +3,7 @@ import { useState } from "react"; import { Button, Col, + EmptyState, Icon, MainTable, Notification, @@ -169,6 +170,10 @@ const ProfileList: FC = () => { const { rows: sortedRows, updateSort } = useSortTableData({ rows }); + if (isLoading) { + return ; + } + return ( <> { Profiles - - { - setQuery(value); - }} - placeholder="Search" - value={query} - aria-label="Search" - /> - + {profiles.length > 0 && ( + + { + setQuery(value); + }} + placeholder="Search" + value={query} + aria-label="Search" + /> + + )} {featuresProfiles && ( @@ -226,40 +233,45 @@ const ProfileList: FC = () => { - {!isLoading && !featuresProfiles && ( + {!featuresProfiles && ( The feature has been disabled on a project level. All the available profiles are inherited from the{" "} default project. )} - - } + title="No profiles found" > - - ) : ( - <>No profile found matching this search - ) - } - onUpdateSort={updateSort} - /> - - +

There are no profiles in this project.

+ + )} + {profiles.length > 0 && ( + + + + + + )}
diff --git a/src/pages/projects/ProjectPermissionWarning.tsx b/src/pages/projects/ProjectPermissionWarning.tsx new file mode 100644 index 0000000000..65c6189880 --- /dev/null +++ b/src/pages/projects/ProjectPermissionWarning.tsx @@ -0,0 +1,71 @@ +import { Button, Icon, Modal, usePortal } from "@canonical/react-components"; +import { useCurrentProject } from "context/useCurrentProject"; +import { useProjectEntitlements } from "util/entitlements/projects"; +import { useEffect } from "react"; +import { useSettings } from "context/useSettings"; + +const ProjectPermissionWarning = () => { + const { isLoading: isSettingsLoading } = useSettings(); + const { project, isLoading: isProjectLoading } = useCurrentProject(); + const { canViewEvents } = useProjectEntitlements(); + const { openPortal, closePortal, isOpen, Portal } = usePortal({ + programmaticallyOpen: true, + }); + + const hasWarning = + !isProjectLoading && !isSettingsLoading && !canViewEvents(project); + + useEffect(() => { + if (hasWarning) { + openPortal(); + } + }, [hasWarning]); + + if (!hasWarning) { + return null; + } + + return ( +
+ + {isOpen && ( + + +

+ You do not have the viewer permission for the + selected project. +

+

+ Changes made in the UI will not be visible without manual reload + of the page. +

+

+ Your UI experience is limited because of that. Contact your + administrator to add the necessary permissions for your user. +

+ +
+
+ )} +
+ ); +}; + +export default ProjectPermissionWarning; diff --git a/src/pages/warnings/WarningList.tsx b/src/pages/warnings/WarningList.tsx index 9c64cab6d8..6be4e7fe8a 100644 --- a/src/pages/warnings/WarningList.tsx +++ b/src/pages/warnings/WarningList.tsx @@ -21,6 +21,7 @@ const WarningList: FC = () => { } = useQuery({ queryKey: [queryKeys.warnings], queryFn: fetchWarnings, + retry: false, // the api returns a 403 for users with limited permissions, surface the error right away }); if (error) { diff --git a/src/util/entitlements/projects.tsx b/src/util/entitlements/projects.tsx index 074da25e98..d28072cb0b 100644 --- a/src/util/entitlements/projects.tsx +++ b/src/util/entitlements/projects.tsx @@ -53,6 +53,13 @@ export const useProjectEntitlements = () => { const canEditProject = (project?: LxdProject) => hasEntitlement(isFineGrained, "can_edit", project?.access_entitlements); + const canViewEvents = (project?: LxdProject) => + hasEntitlement( + isFineGrained, + "can_view_events", + project?.access_entitlements, + ); + return { canCreateImageAliases, canCreateImages, @@ -62,5 +69,6 @@ export const useProjectEntitlements = () => { canCreateStorageVolumes, canDeleteProject, canEditProject, + canViewEvents, }; };