Skip to content

Commit

Permalink
rewiew: review changes
Browse files Browse the repository at this point in the history
Signed-off-by: Mason Hu <[email protected]>
  • Loading branch information
MasWho committed Feb 11, 2025
1 parent e4d6e04 commit 35102cc
Show file tree
Hide file tree
Showing 39 changed files with 164 additions and 137 deletions.
2 changes: 1 addition & 1 deletion src/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { AuthProvider } from "context/auth";
import { EventQueueProvider } from "context/eventQueue";
import { InstanceLoadingProvider } from "context/instanceLoading";
import { ProjectProvider } from "context/project";
import { ProjectProvider } from "context/useCurrentProject";
import Events from "pages/instances/Events";
import App from "./App";
import ErrorBoundary from "components/ErrorBoundary";
Expand Down
21 changes: 15 additions & 6 deletions src/api/images.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,25 @@ import { withEntitlementsQuery } from "util/entitlements/api";

const imageEntitlements = ["can_delete"];

export const fetchImageList = (
export const fetchImagesInProject = (
project: string,
isFineGrained: boolean | null,
): Promise<LxdImage[]> => {
const entitlements = `&${withEntitlementsQuery(isFineGrained, imageEntitlements)}`;
return new Promise((resolve, reject) => {
fetch(`/1.0/images?recursion=1&project=${project}${entitlements}`)
.then(handleResponse)
.then((data: LxdApiResponse<LxdImage[]>) => resolve(data.metadata))
.catch(reject);
});
};

export const fetchImagesInAllProjects = (
isFineGrained: boolean | null,
project?: string,
): Promise<LxdImage[]> => {
const entitlements = `&${withEntitlementsQuery(isFineGrained, imageEntitlements)}`;
const url =
"/1.0/images?recursion=1" +
(project ? `&project=${project}` : "&all-projects=1");
return new Promise((resolve, reject) => {
fetch(url + entitlements)
fetch(`/1.0/images?recursion=1&all-projects=1${entitlements}`)
.then(handleResponse)
.then((data: LxdApiResponse<LxdImage[]>) => resolve(data.metadata))
.catch(reject);
Expand Down
4 changes: 2 additions & 2 deletions src/components/Logo.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { FC } from "react";
import { useProject } from "context/project";
import { useCurrentProject } from "context/useCurrentProject";
import { NavLink } from "react-router-dom";
import { useSettings } from "context/useSettings";

const Logo: FC = () => {
const { project, isLoading } = useProject();
const { project, isLoading } = useCurrentProject();
const { data: settings } = useSettings();

const isMicroCloud = Boolean(settings?.config?.["user.microcloud"]);
Expand Down
4 changes: 2 additions & 2 deletions src/components/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import classnames from "classnames";
import Logo from "./Logo";
import ProjectSelector from "pages/projects/ProjectSelector";
import { getElementAbsoluteHeight, isWidthBelow, logout } from "util/helpers";
import { useProject } from "context/project";
import { useCurrentProject } from "context/useCurrentProject";
import { useMenuCollapsed } from "context/menuCollapsed";
import { useDocs } from "context/useDocs";
import NavLink from "components/NavLink";
Expand Down Expand Up @@ -37,7 +37,7 @@ const Navigation: FC = () => {
const { isRestricted, isOidc } = useAuth();
const docBaseLink = useDocs();
const { menuCollapsed, setMenuCollapsed } = useMenuCollapsed();
const { project, isLoading } = useProject();
const { project, isLoading } = useCurrentProject();
const [projectName, setProjectName] = useState(
project && !isLoading ? project.name : "default",
);
Expand Down
9 changes: 6 additions & 3 deletions src/components/UsedByItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { FC } from "react";
import ResourceLink from "./ResourceLink";
import { LxdUsedBy } from "util/usedBy";
import { ResourceIconType } from "./ResourceIcon";
import { useImages } from "context/useImages";
import { useImagesInProject } from "context/useImages";

interface Props {
item: LxdUsedBy;
Expand All @@ -19,8 +19,11 @@ const UsedByItem: FC<Props> = ({
to,
projectLinkDetailPage = "instances",
}) => {
const enabled = type === "image";
const { data: images = [] } = useImages(activeProject, enabled);
const isImageQueryEnabled = type === "image";
const { data: images = [] } = useImagesInProject(
activeProject,
isImageQueryEnabled,
);

const image = images.find((image) => image.fingerprint === item.name);
const label = image?.properties?.description || item.name;
Expand Down
4 changes: 2 additions & 2 deletions src/components/forms/CpuLimitSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { FC } from "react";
import { RadioInput } from "@canonical/react-components";
import { CpuLimit, CPU_LIMIT_TYPE } from "types/limits";
import CpuLimitInput from "components/forms/CpuLimitInput";
import { useProject } from "context/project";
import { useCurrentProject } from "context/useCurrentProject";

interface Props {
cpuLimit?: CpuLimit;
Expand All @@ -11,7 +11,7 @@ interface Props {
}

const CpuLimitSelector: FC<Props> = ({ cpuLimit, setCpuLimit, help }) => {
const { project } = useProject();
const { project } = useCurrentProject();

if (!cpuLimit) {
return null;
Expand Down
4 changes: 2 additions & 2 deletions src/components/forms/InstanceSnapshotsForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import ScrollableConfigurationTable from "components/forms/ScrollableConfigurati
import { getInstanceKey } from "util/instanceConfigFields";
import { optionRenderer } from "util/formFields";
import SnapshotScheduleInput from "components/SnapshotScheduleInput";
import { useProject } from "context/project";
import { useCurrentProject } from "context/useCurrentProject";
import { isSnapshotsDisabled } from "util/snapshots";
import SnapshotDiabledWarningLink from "components/SnapshotDiabledWarningLink";

Expand All @@ -37,7 +37,7 @@ interface Props {
}

const InstanceSnapshotsForm: FC<Props> = ({ formik }) => {
const { project } = useProject();
const { project } = useCurrentProject();
const snapshotDisabled = isSnapshotsDisabled(project);

return (
Expand Down
4 changes: 2 additions & 2 deletions src/components/forms/MemoryLimitSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import { FC } from "react";
import { Input, RadioInput, Select } from "@canonical/react-components";
import { BYTES_UNITS, MemoryLimit, MEM_LIMIT_TYPE } from "types/limits";
import MemoryLimitAvailable from "components/forms/MemoryLimitAvailable";
import { useProject } from "context/project";
import { useCurrentProject } from "context/useCurrentProject";

interface Props {
memoryLimit?: MemoryLimit;
setMemoryLimit: (memoryLimit: MemoryLimit) => void;
}

const MemoryLimitSelector: FC<Props> = ({ memoryLimit, setMemoryLimit }) => {
const { project } = useProject();
const { project } = useCurrentProject();

if (!memoryLimit) {
return null;
Expand Down
35 changes: 18 additions & 17 deletions src/context/auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,26 @@ export const AuthProvider: FC<ProviderProps> = ({ children }) => {
const { hasEntitiesWithEntitlements, isSettingsLoading } =
useSupportedFeatures();

const { data: currentIdentity } = useQuery({
queryKey: [queryKeys.currentIdentity],
queryFn: fetchCurrentIdentity,
retry: false, // avoid retry for older versions of lxd less than 5.21 due to missing endpoint
});

const isFineGrained = () => {
if (isSettingsLoading) {
return null;
}
if (hasEntitiesWithEntitlements) {
return currentIdentity?.fine_grained ?? null;
}
return false;
};

const { data: projects = [], isLoading: isProjectsLoading } = useQuery({
queryKey: [queryKeys.projects],
queryFn: () => fetchProjects(false),
enabled: settings?.auth === "trusted",
queryFn: () => fetchProjects(isFineGrained()),
enabled: settings?.auth === "trusted" && isFineGrained() !== null,
});

const defaultProject =
Expand All @@ -58,26 +74,11 @@ export const AuthProvider: FC<ProviderProps> = ({ children }) => {
enabled: isTls,
});

const { data: currentIdentity } = useQuery({
queryKey: [queryKeys.currentIdentity],
queryFn: fetchCurrentIdentity,
retry: false, // avoid retry for older versions of lxd less than 5.21 due to missing endpoint
});

const fingerprint = isTls ? settings.auth_user_name : undefined;
const certificate = certificates.find(
(certificate) => certificate.fingerprint === fingerprint,
);
const isRestricted = certificate?.restricted ?? defaultProject !== "default";
const isFineGrained = () => {
if (isSettingsLoading) {
return null;
}
if (hasEntitiesWithEntitlements) {
return currentIdentity?.fine_grained ?? null;
}
return false;
};

return (
<AuthContext.Provider
Expand Down
6 changes: 3 additions & 3 deletions src/context/project.tsx → src/context/useCurrentProject.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createContext, FC, ReactNode, useContext } from "react";
import type { LxdProject } from "types/project";
import { useLocation } from "react-router-dom";
import { useProjectFetch } from "./useProjects";
import { useProject } from "./useProjects";

interface ContextProps {
project?: LxdProject;
Expand All @@ -26,7 +26,7 @@ export const ProjectProvider: FC<ProviderProps> = ({ children }) => {

const enabled = project.length > 0;
const retry = false;
const { data, isLoading } = useProjectFetch(project, enabled, retry);
const { data, isLoading } = useProject(project, enabled, retry);

return (
<ProjectContext.Provider
Expand All @@ -40,6 +40,6 @@ export const ProjectProvider: FC<ProviderProps> = ({ children }) => {
);
};

export function useProject() {
export function useCurrentProject() {
return useContext(ProjectContext);
}
25 changes: 16 additions & 9 deletions src/context/useImages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,28 @@ import { useQuery } from "@tanstack/react-query";
import { queryKeys } from "util/queryKeys";
import { UseQueryResult } from "@tanstack/react-query";
import { useAuth } from "./auth";
import { fetchImageList } from "api/images";
import { fetchImagesInAllProjects, fetchImagesInProject } from "api/images";
import type { LxdImage } from "types/image";

export const useImages = (
project?: string,
export const useImagesInProject = (
project: string,
enabled?: boolean,
): UseQueryResult<LxdImage[]> => {
const { isFineGrained } = useAuth();
const keys = [queryKeys.images];
if (project) {
keys.push(project);
}
return useQuery({
queryKey: keys,
queryFn: () => fetchImageList(isFineGrained, project),
queryKey: [queryKeys.images, project],
queryFn: () => fetchImagesInProject(project, isFineGrained),
enabled: (enabled ?? true) && isFineGrained !== null,
});
};

export const useImagesInAllProjects = (
enabled?: boolean,
): UseQueryResult<LxdImage[]> => {
const { isFineGrained } = useAuth();
return useQuery({
queryKey: [queryKeys.images],
queryFn: () => fetchImagesInAllProjects(isFineGrained),
enabled: (enabled ?? true) && isFineGrained !== null,
});
};
2 changes: 1 addition & 1 deletion src/context/useProjects.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const useProjects = (): UseQueryResult<LxdProject[]> => {
});
};

export const useProjectFetch = (
export const useProject = (
project: string,
enabled?: boolean,
retry?: boolean,
Expand Down
4 changes: 2 additions & 2 deletions src/pages/images/CustomIsoSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useQuery } from "@tanstack/react-query";
import { loadIsoVolumes } from "context/loadIsoVolumes";
import { queryKeys } from "util/queryKeys";
import Loader from "components/Loader";
import { useProject } from "context/project";
import { useCurrentProject } from "context/useCurrentProject";
import type { LxdImageType, RemoteImage } from "types/image";
import type { IsoImage } from "types/iso";
import { useSupportedFeatures } from "context/useSupportedFeatures";
Expand All @@ -23,7 +23,7 @@ const CustomIsoSelector: FC<Props> = ({
onUpload,
onCancel,
}) => {
const { project } = useProject();
const { project } = useCurrentProject();
const projectName = project?.name ?? "";
const { hasStorageVolumesAll } = useSupportedFeatures();

Expand Down
20 changes: 11 additions & 9 deletions src/pages/images/ImageList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import PageHeader from "components/PageHeader";
import CustomIsoBtn from "pages/storage/actions/CustomIsoBtn";
import DownloadImageBtn from "./actions/DownloadImageBtn";
import UploadImageBtn from "pages/images/actions/UploadImageBtn";
import { useImages } from "context/useImages";
import { useImagesInProject } from "context/useImages";
import { useImageEntitlements } from "util/entitlements/images";

const ImageList: FC = () => {
Expand All @@ -43,7 +43,7 @@ const ImageList: FC = () => {
return <>Missing project</>;
}

const { data: images = [], error, isLoading } = useImages(project);
const { data: images = [], error, isLoading } = useImagesInProject(project);

if (error) {
notify.failure("Loading images failed", error);
Expand Down Expand Up @@ -95,7 +95,9 @@ const ImageList: FC = () => {
.includes(query.toLowerCase()),
);

const deletableImages = filteredImages.filter(canDeleteImage);
const deletableImages = filteredImages
.filter(canDeleteImage)
.map((image) => image.fingerprint);

const rows = filteredImages.map((image) => {
const actions = (
Expand All @@ -105,7 +107,7 @@ const ImageList: FC = () => {
items={[
<CreateInstanceFromImageBtn
key="launch"
project={project}
projectName={project}
image={localLxdToRemoteImage(image)}
/>,
<DownloadImageBtn key="download" image={image} project={project} />,
Expand All @@ -120,7 +122,9 @@ const ImageList: FC = () => {
return {
key: image.fingerprint,
// disable image selection if user does not have entitlement to delete
name: canDeleteImage(image) ? image.fingerprint : "",
name: deletableImages.includes(image.fingerprint)
? image.fingerprint
: "",
columns: [
{
content: description,
Expand Down Expand Up @@ -227,7 +231,7 @@ const ImageList: FC = () => {
)}
</PageHeader.Left>
<PageHeader.BaseActions>
<UploadImageBtn project={project} />
<UploadImageBtn projectName={project} />
<CustomIsoBtn project={project} />
</PageHeader.BaseActions>
</PageHeader>
Expand Down Expand Up @@ -266,9 +270,7 @@ const ImageList: FC = () => {
parentName="project"
selectedNames={selectedNames}
setSelectedNames={setSelectedNames}
filteredNames={deletableImages.map(
(item) => item.fingerprint,
)}
filteredNames={deletableImages}
/>
)
}
Expand Down
4 changes: 2 additions & 2 deletions src/pages/images/ImageSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { instanceCreationTypes } from "util/instanceOptions";
import { useSettings } from "context/useSettings";
import ScrollableTable from "components/ScrollableTable";
import { useParams } from "react-router-dom";
import { useImages } from "context/useImages";
import { useImagesInProject } from "context/useImages";

interface Props {
onSelect: (image: RemoteImage, type?: LxdImageType) => void;
Expand Down Expand Up @@ -98,7 +98,7 @@ const ImageSelector: FC<Props> = ({ onSelect, onClose }) => {
});

const { data: localImages = [], isLoading: isLocalImageLoading } =
useImages(project);
useImagesInProject(project ?? "default");

const isLoading =
isCiLoading ||
Expand Down
Loading

0 comments on commit 35102cc

Please sign in to comment.