Skip to content

Commit

Permalink
feat: [M3-6759] - Handle VPC Account Grants / Permissions / Capabilit…
Browse files Browse the repository at this point in the history
…ies (#9585)

* add vpc grant and permissions

* yarn changeset

* Update packages/api-v4/.changeset/pr-9585-changed-1692736077405.md

Co-authored-by: Dajahi Wiley <114682940+dwiley-akamai@users.noreply.github.com>

* Update packages/manager/.changeset/pr-9585-upcoming-features-1692736112038.md

Co-authored-by: Dajahi Wiley <114682940+dwiley-akamai@users.noreply.github.com>

* fix unresolved merge conflict issues

* update vpc global grant text

* add missing files

* Update packages/manager/src/features/VPC/VPCLanding/VPCEditDrawer.tsx

Co-authored-by: Dajahi Wiley <114682940+dwiley-akamai@users.noreply.github.com>

* add comment to explain VPC permissions

---------

Co-authored-by: Dajahi Wiley <114682940+dwiley-akamai@users.noreply.github.com>
  • Loading branch information
2 people authored and abailly-akamai committed Sep 7, 2023
1 parent a97d8e6 commit 6c4d506
Show file tree
Hide file tree
Showing 13 changed files with 111 additions and 23 deletions.
5 changes: 5 additions & 0 deletions packages/api-v4/.changeset/pr-9585-changed-1692736077405.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/api-v4": Changed
---

Change `Account` and `Grant`-related types to include VPC-related grants and capabilities ([#9585](https://github.com/linode/manager/pull/9585))
3 changes: 2 additions & 1 deletion packages/api-v4/src/account/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,8 @@ export type GrantType =
| 'stackscript'
| 'volume'
| 'database'
| 'firewall';
| 'firewall'
| 'vpc';

export type Grants = GlobalGrants & Record<GrantType, Grant[]>;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

Add VPC-related permissions, capabilities, and grants ([#9585](https://github.com/linode/manager/pull/9585))
10 changes: 8 additions & 2 deletions packages/manager/src/MainContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,12 @@ const MainContent = (props: CombinedProps) => {
account?.capabilities ?? []
);

const showVPCs = isFeatureEnabled(
'VPCs',
Boolean(flags.vpc),
account?.capabilities ?? []
);

const defaultRoot = _isManagedAccount ? '/managed' : '/linodes';

const shouldDisplayMainContentBanner =
Expand Down Expand Up @@ -358,9 +364,9 @@ const MainContent = (props: CombinedProps) => {
{flags.selfServeBetas ? (
<Route component={BetaRoutes} path="/betas" />
) : null}
{flags.vpc && (
{showVPCs ? (
<Route component={VPC} path="/vpcs" />
)}
) : null}
<Redirect exact from="/" to={defaultRoot} />
{/** We don't want to break any bookmarks. This can probably be removed eventually. */}
<Redirect from="/dashboard" to={defaultRoot} />
Expand Down
12 changes: 9 additions & 3 deletions packages/manager/src/components/PrimaryNav/PrimaryNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as React from 'react';
import { Link, LinkProps, useLocation } from 'react-router-dom';

import Account from 'src/assets/icons/account.svg';
import Beta from 'src/assets/icons/entityIcons/beta.svg';
import Storage from 'src/assets/icons/entityIcons/bucket.svg';
import Database from 'src/assets/icons/entityIcons/database.svg';
import Domain from 'src/assets/icons/entityIcons/domain.svg';
Expand All @@ -18,7 +19,6 @@ import Volume from 'src/assets/icons/entityIcons/volume.svg';
import VPC from 'src/assets/icons/entityIcons/vpc.svg';
import TooltipIcon from 'src/assets/icons/get_help.svg';
import Longview from 'src/assets/icons/longview.svg';
import Beta from 'src/assets/icons/entityIcons/beta.svg';
import AkamaiLogo from 'src/assets/logo/akamai-logo.svg';
import { BetaChip } from 'src/components/BetaChip/BetaChip';
import { Divider } from 'src/components/Divider';
Expand Down Expand Up @@ -127,6 +127,12 @@ export const PrimaryNav = (props: Props) => {
account?.capabilities ?? []
);

const showVPCs = isFeatureEnabled(
'VPCs',
Boolean(flags.vpc),
account?.capabilities ?? []
);

const prefetchObjectStorage = () => {
if (!enableObjectPrefetch) {
setEnableObjectPrefetch(true);
Expand Down Expand Up @@ -177,7 +183,7 @@ export const PrimaryNav = (props: Props) => {
},
{
display: 'VPC',
hide: !flags.vpc,
hide: !showVPCs,
href: '/vpcs',
icon: <VPC />,
isBeta: true,
Expand Down Expand Up @@ -273,7 +279,7 @@ export const PrimaryNav = (props: Props) => {
allowMarketplacePrefetch,
flags.databaseBeta,
flags.aglb,
flags.vpc,
showVPCs,
]
);

Expand Down
19 changes: 18 additions & 1 deletion packages/manager/src/factories/grants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ export const grantsFactory = Factory.Sync.makeFactory<Grants>({
add_nodebalancers: true,
add_stackscripts: true,
add_volumes: true,
add_vpcs: true,
cancel_account: false,
longview_subscription: true,
add_vpcs: true,
},
image: [
{
Expand Down Expand Up @@ -85,4 +85,21 @@ export const grantsFactory = Factory.Sync.makeFactory<Grants>({
permissions: 'read_only',
},
],
vpc: [
{
id: 123,
label: 'example-entity1',
permissions: 'read_only',
},
{
id: 124,
label: 'example-entity2',
permissions: 'read_write',
},
{
id: 125,
label: 'example-entity3',
permissions: null,
},
],
});
3 changes: 2 additions & 1 deletion packages/manager/src/features/OneClickApps/oneClickApps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -825,7 +825,8 @@ export const oneClickApps: OCA[] = [
{
href:
'https://www.linode.com/docs/products/tools/marketplace/guides/hashicorp-nomad-clients-cluster',
title: 'Deploy HashiCorp Nomad Clients Cluster through the Linode Marketplace',
title:
'Deploy HashiCorp Nomad Clients Cluster through the Linode Marketplace',
},
],
summary: 'Flexible scheduling and orchestration for diverse workloads.',
Expand Down
11 changes: 10 additions & 1 deletion packages/manager/src/features/TopMenu/AddNewMenu/AddNewMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import VPCIcon from 'src/assets/icons/entityIcons/vpc.svg';
import { Button } from 'src/components/Button/Button';
import { Divider } from 'src/components/Divider';
import { useFlags } from 'src/hooks/useFlags';
import { useAccount } from 'src/queries/account';
import { isFeatureEnabled } from 'src/utilities/accountCapabilities';

interface LinkProps {
attr?: { [key: string]: boolean };
Expand All @@ -37,10 +39,17 @@ interface LinkProps {

export const AddNewMenu = () => {
const theme = useTheme();
const { data: account } = useAccount();
const [anchorEl, setAnchorEl] = React.useState<HTMLElement | null>(null);
const flags = useFlags();
const open = Boolean(anchorEl);

const showVPCs = isFeatureEnabled(
'VPCs',
Boolean(flags.vpc),
account?.capabilities ?? []
);

const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
};
Expand Down Expand Up @@ -80,7 +89,7 @@ export const AddNewMenu = () => {
{
description: 'Create a private and isolated network',
entity: 'VPC',
hide: !flags.vpc,
hide: !showVPCs,
icon: VPCIcon,
link: '/vpcs/create',
},
Expand Down
3 changes: 3 additions & 0 deletions packages/manager/src/features/Users/UserPermissions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ class UserPermissions extends React.Component<CombinedProps, State> {
'domain',
'longview',
'database',
'vpc',
];

entitySetAllTo = (entity: GrantType, value: GrantLevel) => () => {
Expand Down Expand Up @@ -283,6 +284,7 @@ class UserPermissions extends React.Component<CombinedProps, State> {
'add_volumes',
'add_firewalls',
'add_databases',
'add_vpcs',
'cancel_account',
];

Expand Down Expand Up @@ -462,6 +464,7 @@ class UserPermissions extends React.Component<CombinedProps, State> {
add_nodebalancers: 'Can add NodeBalancers to this account ($)',
add_stackscripts: 'Can create StackScripts under this account',
add_volumes: 'Can add Block Storage Volumes to this account ($)',
add_vpcs: 'Can add VPCs to this account',
cancel_account: 'Can cancel the entire account',
longview_subscription:
'Can modify this account\u{2019}s Longview subscription ($)',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const entityNameMap: Record<GrantType, string> = {
longview: 'Longview Clients',
firewall: 'Firewalls',
database: 'Databases',
vpc: 'VPCs',
};

interface Props {
Expand Down
23 changes: 22 additions & 1 deletion packages/manager/src/features/VPCs/VPCLanding/VPCEditDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Drawer } from 'src/components/Drawer';
import { RegionSelect } from 'src/components/EnhancedSelect/variants/RegionSelect';
import { Notice } from 'src/components/Notice/Notice';
import { TextField } from 'src/components/TextField';
import { useGrants, useProfile } from 'src/queries/profile';
import { useRegionsQuery } from 'src/queries/regions';
import { useUpdateVPCMutation } from 'src/queries/vpcs';
import { getErrorMap } from 'src/utilities/errorUtils';
Expand All @@ -22,6 +23,17 @@ const REGION_HELPER_TEXT = 'Region cannot be changed during beta.';
export const VPCEditDrawer = (props: Props) => {
const { onClose, open, vpc } = props;

const { data: profile } = useProfile();
const { data: grants } = useGrants();

const vpcPermissions = grants?.vpc.find((v) => v.id === vpc?.id);

// there isn't a 'view VPC/Subnet' grant that does anything, so all VPCs get returned even for restricted users
// with permissions set to 'None'. Therefore, we're treating those as read_only as well
const readOnly =
Boolean(profile?.restricted) &&
(vpcPermissions?.permissions === 'read_only' || grants?.vpc.length === 0);

const {
error,
isLoading,
Expand Down Expand Up @@ -55,15 +67,24 @@ export const VPCEditDrawer = (props: Props) => {
return (
<Drawer onClose={onClose} open={open} title="Edit VPC">
{errorMap.none && <Notice text={errorMap.none} variant="error" />}
{readOnly && (
<Notice
important
text={`You don't have permissions to edit ${vpc?.label}. Please contact an account administrator for details.`}
variant="error"
/>
)}
<form onSubmit={form.handleSubmit}>
<TextField
disabled={readOnly}
errorText={errorMap.label}
label="Label"
name="label"
onChange={form.handleChange}
value={form.values.label}
/>
<TextField
disabled={readOnly}
errorText={errorMap.description}
label="Description"
multiline
Expand All @@ -84,7 +105,7 @@ export const VPCEditDrawer = (props: Props) => {
<ActionsPanel
primaryButtonProps={{
'data-testid': 'save-button',
disabled: !form.dirty,
disabled: !form.dirty || readOnly,
label: 'Save',
loading: isLoading,
type: 'submit',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ import {
gettingStartedGuides,
youtubeLinkData,
} from 'src/features/Linodes/LinodesLanding/LinodesLandingEmptyStateData';
import {
headers,
linkAnalyticsEvent,
} from './VPCEmptyStateData';
import { headers, linkAnalyticsEvent } from './VPCEmptyStateData';
import { sendEvent } from 'src/utilities/analytics';

export const VPCEmptyState = () => {
Expand Down
34 changes: 25 additions & 9 deletions packages/manager/src/features/VPCs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,38 @@ import { Route, Switch } from 'react-router-dom';
import { DocumentTitleSegment } from 'src/components/DocumentTitle';
import { ProductInformationBanner } from 'src/components/ProductInformationBanner/ProductInformationBanner';
import { SuspenseLoader } from 'src/components/SuspenseLoader';
import { useFlags } from 'src/hooks/useFlags';
import { useAccount } from 'src/queries/account';
import { isFeatureEnabled } from 'src/utilities/accountCapabilities';

const VPCCreate = React.lazy(() => import('./VPCCreate/VPCCreate'));
const VPCDetail = React.lazy(() => import('./VPCDetail/VPCDetail'));
const VPCLanding = React.lazy(() => import('./VPCLanding/VPCLanding'));

const VPC = () => {
const flags = useFlags();
const { data: account } = useAccount();

const showVPCs = isFeatureEnabled(
'VPCs',
Boolean(flags.vpc),
account?.capabilities ?? []
);

return (
<React.Suspense fallback={<SuspenseLoader />}>
<DocumentTitleSegment segment="VPC" />
<ProductInformationBanner bannerLocation="VPC" />
<Switch>
<Route component={VPCCreate} path="/vpcs/create" />
<Route component={VPCDetail} path="/vpcs/:vpcId/:tab?" />
<Route component={VPCLanding} path="/vpcs" />
</Switch>
</React.Suspense>
<>
{showVPCs ? (
<React.Suspense fallback={<SuspenseLoader />}>
<DocumentTitleSegment segment="VPC" />
<ProductInformationBanner bannerLocation="VPC" />
<Switch>
<Route component={VPCCreate} path="/vpcs/create" />
<Route component={VPCDetail} path="/vpcs/:vpcId/:tab?" />
<Route component={VPCLanding} path="/vpcs" />
</Switch>
</React.Suspense>
) : null}
</>
);
};

Expand Down

0 comments on commit 6c4d506

Please sign in to comment.