Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: UI graceful handling of restricted permissions for instance related actions [WD-18840] #1094

Merged
merged 1 commit into from
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/api/instances.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,15 @@ import axios, { AxiosResponse } from "axios";
import type { UploadState } from "types/storage";
import { withEntitlementsQuery } from "util/entitlements/api";

export const instanceEntitlements = ["can_update_state"];
export const instanceEntitlements = [
"can_update_state",
"can_delete",
"can_edit",
"can_manage_backups",
"can_manage_snapshots",
"can_exec",
"can_access_console",
];

export const fetchInstance = (
name: string,
Expand Down
1 change: 1 addition & 0 deletions src/api/projects.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const projectEntitlements = [
"can_create_images",
"can_create_image_aliases",
"can_create_instances",
"can_create_storage_volumes",
];

export const fetchProjects = (
Expand Down
42 changes: 27 additions & 15 deletions src/components/ConfigurationRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,22 @@ export const getConfigurationRow = ({
}
};

const isDisabled = () => {
return disabled || !!formik.values.editRestriction;
};

const getDisabledReasonOrTitle = (title?: string) => {
if (formik.values.editRestriction) {
return formik.values.editRestriction;
}

if (disabledReason) {
return disabledReason;
}

return title;
};

const getForm = (): ReactNode => {
return (
<div className="override-form">
Expand All @@ -86,7 +102,7 @@ export const getConfigurationRow = ({
onBlur: formik.handleBlur,
onChange: formik.handleChange,
value,
disabled,
disabled: isDisabled(),
help: (
<ConfigFieldDescription
description={
Expand All @@ -104,8 +120,8 @@ export const getConfigurationRow = ({
onClick={toggleDefault}
type="button"
appearance="base"
title={disabled ? disabledReason : "Clear override"}
disabled={disabled}
title={getDisabledReasonOrTitle("Clear override")}
disabled={isDisabled()}
hasIcon
className="u-no-margin--bottom"
>
Expand All @@ -124,9 +140,9 @@ export const getConfigurationRow = ({
);

const wrapDisabledTooltip = (children: ReactNode): ReactNode => {
if (disabled && disabledReason) {
if ((disabled && disabledReason) || formik.values.editRestriction) {
return (
<Tooltip message={disabledReason} position="right">
<Tooltip message={getDisabledReasonOrTitle()} position="right">
{children}
</Tooltip>
);
Expand All @@ -150,14 +166,10 @@ export const getConfigurationRow = ({
className="u-no-margin--bottom"
type="button"
appearance="base"
title={
disabled
? disabledReason
: isOverridden
? "Edit"
: "Create override"
}
disabled={disabled}
title={getDisabledReasonOrTitle(
isOverridden ? "Edit" : "Create override",
)}
disabled={isDisabled()}
hasIcon
>
<Icon name="edit" />
Expand All @@ -173,9 +185,9 @@ export const getConfigurationRow = ({
onClick={toggleDefault}
className="u-no-margin--bottom"
type="button"
disabled={disabled}
disabled={isDisabled()}
appearance="base"
title="Create override"
title={formik.values.editRestriction ?? "Create override"}
hasIcon
>
<Icon name="edit" />
Expand Down
47 changes: 31 additions & 16 deletions src/components/NetworkListTable.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { FC } from "react";
import { useQuery } from "@tanstack/react-query";
import { queryKeys } from "util/queryKeys";
import { MainTable } from "@canonical/react-components";
import { MainTable, Notification } from "@canonical/react-components";
import Loader from "components/Loader";
import { fetchNetworks } from "api/networks";
import { isNicDevice } from "util/devices";
Expand Down Expand Up @@ -34,7 +34,8 @@ const NetworkListTable: FC<Props> = ({ onFailure, devices }) => {
.filter(isNicDevice)
.map((network) => network.network);

const hasNetworks = networkDevices.length > 0;
const instanceHasNetworks = networkDevices.length > 0;
const userHasNetworks = networks.length > 0;

const networksHeaders = [
{ content: "Name", sortKey: "name", className: "u-text--muted" },
Expand Down Expand Up @@ -100,20 +101,34 @@ const NetworkListTable: FC<Props> = ({ onFailure, devices }) => {
};
});

return (
<>
{isLoading && <Loader text="Loading networks..." />}
{!isLoading && hasNetworks && (
<MainTable
headers={networksHeaders}
rows={networksRows}
sortable
className={"network-table"}
/>
)}
{!isLoading && !hasNetworks && <>-</>}
</>
);
const getContent = () => {
if (isLoading) {
return <Loader text="Loading networks..." />;
}

if (instanceHasNetworks && !userHasNetworks) {
return (
<Notification severity="caution" title="Restricted permissions">
You do not have permission to view network details.
</Notification>
);
}

if (!instanceHasNetworks) {
return <>-</>;
}

return (
<MainTable
headers={networksHeaders}
rows={networksRows}
sortable
className={"network-table"}
/>
);
};

return getContent();
};

export default NetworkListTable;
6 changes: 4 additions & 2 deletions src/components/forms/CloudInitForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ const CloudInitForm: FC<Props> = ({ formik }) => {
}}
type="button"
appearance="base"
title={"Clear override"}
title={formik.values.editRestriction ?? "Clear override"}
disabled={!!formik.values.editRestriction}
hasIcon
className="u-no-margin--bottom"
>
Expand All @@ -92,8 +93,9 @@ const CloudInitForm: FC<Props> = ({ formik }) => {
className="u-no-margin--bottom"
type="button"
appearance="base"
title="Create override"
title={formik.values.editRestriction ?? "Create override"}
hasIcon
disabled={!!formik.values.editRestriction}
>
<Icon name="edit" />
</Button>
Expand Down
8 changes: 6 additions & 2 deletions src/components/forms/DiskDeviceFormCustom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,12 @@ const DiskDeviceFormCustom: FC<Props> = ({ formik, project, profiles }) => {
className="u-no-margin--bottom"
hasIcon
dense
title="Edit"
title={formik.values.editRestriction ?? "Edit"}
onClick={() => {
ensureEditMode(formik);
focusField(fieldName);
}}
disabled={!!formik.values.editRestriction}
>
<Icon name="edit" />
</Button>
Expand All @@ -108,6 +109,7 @@ const DiskDeviceFormCustom: FC<Props> = ({ formik, project, profiles }) => {
ensureEditMode(formik);
void formik.setFieldValue(`devices.${index}.name`, name);
}}
disableReason={formik.values.editRestriction}
/>
),
inherited: "",
Expand All @@ -117,6 +119,7 @@ const DiskDeviceFormCustom: FC<Props> = ({ formik, project, profiles }) => {
ensureEditMode(formik);
removeDevice(index, formik);
}}
disabledReason={formik.values.editRestriction}
/>
),
}),
Expand Down Expand Up @@ -149,8 +152,9 @@ const DiskDeviceFormCustom: FC<Props> = ({ formik, project, profiles }) => {
id: `devices.${index}.pool`,
appearance: "base",
className: "u-no-margin--bottom",
title: "Select storage volume",
title: formik.values.editRestriction ?? "Select storage volume",
dense: true,
disabled: !!formik.values.editRestriction,
}}
>
<Icon name="edit" />
Expand Down
7 changes: 6 additions & 1 deletion src/components/forms/DiskDeviceFormInherited.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,13 @@ const DiskDeviceFormInherited: FC<Props> = ({
<Button
appearance="base"
type="button"
title="Reattach device"
title={formik.values.editRestriction ?? "Reattach device"}
onClick={() => {
ensureEditMode(formik);
removeDevice(noneDeviceId, formik);
}}
className="has-icon u-no-margin--bottom"
disabled={!!formik.values.editRestriction}
>
<Icon name="connected"></Icon>
<span>Reattach</span>
Expand All @@ -70,6 +71,7 @@ const DiskDeviceFormInherited: FC<Props> = ({
ensureEditMode(formik);
addNoneDevice(item.key, formik);
}}
disabledReason={formik.values.editRestriction}
/>
),
}),
Expand All @@ -82,6 +84,7 @@ const DiskDeviceFormInherited: FC<Props> = ({
inheritValue: item.disk.source,
readOnly: readOnly,
isDeactivated: isNoneDevice,
disabledReason: formik.values.editRestriction,
}),
);
} else {
Expand All @@ -95,6 +98,7 @@ const DiskDeviceFormInherited: FC<Props> = ({
),
readOnly: readOnly,
isDeactivated: isNoneDevice,
disabledReason: formik.values.editRestriction,
}),
);
}
Expand All @@ -105,6 +109,7 @@ const DiskDeviceFormInherited: FC<Props> = ({
inheritValue: item.disk.path,
readOnly: readOnly,
isDeactivated: isNoneDevice,
disabledReason: formik.values.editRestriction,
}),
);
});
Expand Down
11 changes: 8 additions & 3 deletions src/components/forms/DiskDeviceFormRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,10 @@ const DiskDeviceFormRoot: FC<Props> = ({ formik, pools, profiles }) => {
}}
type="button"
appearance="base"
title="Clear override"
title={formik.values.editRestriction ?? "Clear override"}
hasIcon
className="u-no-margin--bottom"
disabled={!!formik.values.editRestriction}
>
<Icon name="close" className="clear-configuration-icon" />
</Button>
Expand All @@ -81,9 +82,10 @@ const DiskDeviceFormRoot: FC<Props> = ({ formik, pools, profiles }) => {
}}
type="button"
appearance="base"
title="Create override"
title={formik.values.editRestriction ?? "Create override"}
className="u-no-margin--bottom"
hasIcon
disabled={!!formik.values.editRestriction}
>
<Icon name="edit" />
</Button>
Expand All @@ -97,6 +99,7 @@ const DiskDeviceFormRoot: FC<Props> = ({ formik, pools, profiles }) => {
inheritValue: inheritValue?.pool ?? "",
inheritSource,
readOnly: readOnly,
disabledReason: formik.values.editRestriction,
overrideValue: hasRootStorage && (
<>
{formRootDevice?.pool}
Expand Down Expand Up @@ -148,6 +151,7 @@ const DiskDeviceFormRoot: FC<Props> = ({ formik, pools, profiles }) => {
inheritValue?.size ?? (inheritValue ? "unlimited" : ""),
inheritSource,
readOnly: readOnly,
disabledReason: formik.values.editRestriction,
overrideValue: hasRootStorage && (
<>
{formRootDevice?.size ?? "unlimited"}
Expand All @@ -158,9 +162,10 @@ const DiskDeviceFormRoot: FC<Props> = ({ formik, pools, profiles }) => {
}}
type="button"
appearance="base"
title="Edit"
title={formik.values.editRestriction ?? "Edit"}
className="u-no-margin--bottom"
hasIcon
disabled={!!formik.values.editRestriction}
>
<Icon name="edit" />
</Button>
Expand Down
10 changes: 8 additions & 2 deletions src/components/forms/GPUDeviceForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ const GPUDevicesForm: FC<Props> = ({ formik, project }) => {
<Button
appearance="base"
type="button"
title="Reattach volume"
title="Reattach GPU"
onClick={() => {
ensureEditMode(formik);
removeDevice(noneDeviceId, formik);
Expand All @@ -123,6 +123,8 @@ const GPUDevicesForm: FC<Props> = ({ formik, project }) => {
}}
className="has-icon u-no-margin--bottom"
dense
title={formik.values.editRestriction ?? "Detach GPU"}
disabled={!!formik.values.editRestriction}
>
<Icon name="disconnect"></Icon>
<span>Detach</span>
Expand Down Expand Up @@ -165,6 +167,7 @@ const GPUDevicesForm: FC<Props> = ({ formik, project }) => {
ensureEditMode(formik);
void formik.setFieldValue(`devices.${index}.name`, name);
}}
disableReason={formik.values.editRestriction}
/>
),
inherited: "",
Expand All @@ -179,7 +182,8 @@ const GPUDevicesForm: FC<Props> = ({ formik, project }) => {
appearance="base"
hasIcon
dense
title="Detach GPU"
title={formik.values.editRestriction ?? "Detach GPU"}
disabled={!!formik.values.editRestriction}
>
<Icon name="disconnect" />
<span>Detach</span>
Expand Down Expand Up @@ -213,6 +217,7 @@ const GPUDevicesForm: FC<Props> = ({ formik, project }) => {
void formik.setFieldValue(`devices.${index}.pci`, pci);
void formik.setFieldValue(`devices.${index}.id`, id);
}}
disableReason={formik.values.editRestriction}
/>
),
readOnly: false,
Expand Down Expand Up @@ -269,6 +274,7 @@ const GPUDevicesForm: FC<Props> = ({ formik, project }) => {
ensureEditMode(formik);
addGPUCard(card);
}}
disabledReason={formik.values.editRestriction}
/>
</ScrollableForm>
);
Expand Down
Loading