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,
};
};