Skip to content

Commit 818b613

Browse files
feat: [UIE-8862] - IAM RBAC: CM Integration - dev tools (linode#12361)
* feat: [UIE-8862] - IAM RBAC: CM Integration - dev tools * Added changeset: Add the ability to edit user account and entity permissions in the dev tools --------- Co-authored-by: cpathipa <[email protected]>
1 parent e4aded4 commit 818b613

14 files changed

+556
-16
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@linode/manager": Added
3+
---
4+
5+
Add the ability to edit user account and entity permissions in the dev tools ([#12361](https://github.com/linode/manager/pull/12361))

packages/manager/src/dev-tools/ServiceWorkerTool.tsx

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import {
1616
getCustomMaintenanceData,
1717
getCustomNotificationsData,
1818
getCustomProfileData,
19+
getCustomUserAccountPermissionsData,
20+
getCustomUserEntityPermissionsData,
1921
getExtraPresets,
2022
getExtraPresetsMap,
2123
getSeeders,
@@ -27,6 +29,8 @@ import {
2729
saveCustomMaintenanceData,
2830
saveCustomNotificationsData,
2931
saveCustomProfileData,
32+
saveCustomUserAccountPermissionsData,
33+
saveCustomUserEntityPermissionsData,
3034
saveExtraPresets,
3135
saveExtraPresetsMap,
3236
saveMSWEnabled,
@@ -39,6 +43,7 @@ import type {
3943
AccountMaintenance,
4044
Event,
4145
Notification,
46+
PermissionType,
4247
Profile,
4348
} from '@linode/api-v4';
4449
import type {
@@ -74,6 +79,16 @@ export const ServiceWorkerTool = () => {
7479
const [customProfileData, setCustomProfileData] = React.useState<
7580
null | Profile | undefined
7681
>(getCustomProfileData());
82+
const [
83+
customUserAccountPermissionsData,
84+
setCustomUserAccountPermissionsData,
85+
] = React.useState<null | PermissionType[] | undefined>(
86+
getCustomUserAccountPermissionsData()
87+
);
88+
const [customUserEntityPermissionsData, setCustomUserEntityPermissionsData] =
89+
React.useState<null | PermissionType[] | undefined>(
90+
getCustomUserEntityPermissionsData()
91+
);
7792
const [customEventsData, setCustomEventsData] = React.useState<
7893
Event[] | null | undefined
7994
>(getCustomEventsData());
@@ -104,6 +119,10 @@ export const ServiceWorkerTool = () => {
104119
React.useEffect(() => {
105120
const currentAccountData = getCustomAccountData();
106121
const currentProfileData = getCustomProfileData();
122+
const currentUserAccountPermissionsData =
123+
getCustomUserAccountPermissionsData();
124+
const currentUserEntityPermissionsData =
125+
getCustomUserEntityPermissionsData();
107126
const currentEventsData = getCustomEventsData();
108127
const currentMaintenanceData = getCustomMaintenanceData();
109128
const currentNotificationsData = getCustomNotificationsData();
@@ -120,12 +139,21 @@ export const ServiceWorkerTool = () => {
120139
JSON.stringify(currentNotificationsData) !==
121140
JSON.stringify(customNotificationsData);
122141

142+
const hasCustomUserAccountPermissionsChanges =
143+
JSON.stringify(currentUserAccountPermissionsData) !==
144+
JSON.stringify(customUserAccountPermissionsData);
145+
const hasCustomUserEntityPermissionsChanges =
146+
JSON.stringify(currentUserEntityPermissionsData) !==
147+
JSON.stringify(customUserEntityPermissionsData);
148+
123149
if (
124150
hasCustomAccountChanges ||
125151
hasCustomProfileChanges ||
126152
hasCustomEventsChanges ||
127153
hasCustomMaintenanceChanges ||
128-
hasCustomNotificationsChanges
154+
hasCustomNotificationsChanges ||
155+
hasCustomUserAccountPermissionsChanges ||
156+
hasCustomUserEntityPermissionsChanges
129157
) {
130158
setSaveState((prev) => ({
131159
...prev,
@@ -138,6 +166,8 @@ export const ServiceWorkerTool = () => {
138166
customMaintenanceData,
139167
customNotificationsData,
140168
customProfileData,
169+
customUserAccountPermissionsData,
170+
customUserEntityPermissionsData,
141171
]);
142172

143173
const globalHandlers = {
@@ -171,6 +201,18 @@ export const ServiceWorkerTool = () => {
171201
) {
172202
saveCustomNotificationsData(customNotificationsData);
173203
}
204+
if (
205+
extraPresets.includes('userAccountPermissions:custom') &&
206+
customUserAccountPermissionsData
207+
) {
208+
saveCustomUserAccountPermissionsData(customUserAccountPermissionsData);
209+
}
210+
if (
211+
extraPresets.includes('userEntityPermissions:custom') &&
212+
customUserEntityPermissionsData
213+
) {
214+
saveCustomUserEntityPermissionsData(customUserEntityPermissionsData);
215+
}
174216

175217
const promises = seeders.map((seederId) => {
176218
const seeder = dbSeeders.find((dbSeeder) => dbSeeder.id === seederId);
@@ -200,6 +242,10 @@ export const ServiceWorkerTool = () => {
200242
setCustomEventsData(getCustomEventsData());
201243
setCustomMaintenanceData(getCustomMaintenanceData());
202244
setCustomNotificationsData(getCustomNotificationsData());
245+
setCustomUserAccountPermissionsData(
246+
getCustomUserAccountPermissionsData()
247+
);
248+
setCustomUserEntityPermissionsData(getCustomUserEntityPermissionsData());
203249
setSaveState({
204250
hasSaved: false,
205251
hasUnsavedChanges: false,
@@ -219,6 +265,8 @@ export const ServiceWorkerTool = () => {
219265
setCustomEventsData(null);
220266
setCustomMaintenanceData(null);
221267
setCustomNotificationsData(null);
268+
setCustomUserAccountPermissionsData(null);
269+
setCustomUserEntityPermissionsData(null);
222270

223271
saveBaselinePreset('baseline:static-mocking');
224272
saveExtraPresets([]);
@@ -230,6 +278,8 @@ export const ServiceWorkerTool = () => {
230278
saveCustomEventsData(null);
231279
saveCustomMaintenanceData(null);
232280
saveCustomNotificationsData(null);
281+
saveCustomUserAccountPermissionsData(null);
282+
saveCustomUserEntityPermissionsData(null);
233283

234284
setSaveState({
235285
hasSaved: false,
@@ -445,12 +495,24 @@ export const ServiceWorkerTool = () => {
445495
customMaintenanceData={customMaintenanceData}
446496
customNotificationsData={customNotificationsData}
447497
customProfileData={customProfileData}
498+
customUserAccountPermissionsData={
499+
customUserAccountPermissionsData
500+
}
501+
customUserEntityPermissionsData={
502+
customUserEntityPermissionsData
503+
}
448504
handlers={extraPresets}
449505
onCustomAccountChange={setCustomAccountData}
450506
onCustomEventsChange={setCustomEventsData}
451507
onCustomMaintenanceChange={setCustomMaintenanceData}
452508
onCustomNotificationsChange={setCustomNotificationsData}
453509
onCustomProfileChange={setCustomProfileData}
510+
onCustomUserAccountPermissionsChange={
511+
setCustomUserAccountPermissionsData
512+
}
513+
onCustomUserEntityPermissionsChange={
514+
setCustomUserEntityPermissionsData
515+
}
454516
onPresetCountChange={presetHandlers.changeCount}
455517
onSelectChange={presetHandlers.changeSelect}
456518
onTogglePreset={presetHandlers.toggle}

packages/manager/src/dev-tools/components/ExtraPresetOptions.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,15 @@ import { ExtraPresetNotifications } from './ExtraPresetNotifications';
1010
import { ExtraPresetOptionCheckbox } from './ExtraPresetOptionCheckbox';
1111
import { ExtraPresetOptionSelect } from './ExtraPresetOptionSelect';
1212
import { ExtraPresetProfile } from './ExtraPresetProfile';
13+
import { ExtraPresetUserAccountPermissions } from './ExtraPresetUserAccountPermissions';
14+
import { ExtraPresetUserEntityPermissions } from './ExtraPresetUserEntityPermissions';
1315

1416
import type {
1517
Account,
1618
AccountMaintenance,
1719
Event,
1820
Notification,
21+
PermissionType,
1922
Profile,
2023
} from '@linode/api-v4';
2124

@@ -25,6 +28,8 @@ export interface ExtraPresetOptionsProps {
2528
customMaintenanceData?: AccountMaintenance[] | null;
2629
customNotificationsData?: Notification[] | null;
2730
customProfileData?: null | Profile;
31+
customUserAccountPermissionsData?: null | PermissionType[];
32+
customUserEntityPermissionsData?: null | PermissionType[];
2833
handlers: string[];
2934
onCustomAccountChange?: (data: Account | null | undefined) => void;
3035
onCustomEventsChange?: (data: Event[] | null | undefined) => void;
@@ -35,6 +40,12 @@ export interface ExtraPresetOptionsProps {
3540
data: Notification[] | null | undefined
3641
) => void;
3742
onCustomProfileChange?: (data: null | Profile | undefined) => void;
43+
onCustomUserAccountPermissionsChange?: (
44+
data: null | PermissionType[] | undefined
45+
) => void;
46+
onCustomUserEntityPermissionsChange?: (
47+
data: null | PermissionType[] | undefined
48+
) => void;
3849
onPresetCountChange: (e: React.ChangeEvent, presetId: string) => void;
3950
onSelectChange: (e: React.ChangeEvent, presetId: string) => void;
4051
onTogglePreset: (e: React.ChangeEvent, presetId: string) => void;
@@ -50,12 +61,16 @@ export const ExtraPresetOptions = ({
5061
customEventsData,
5162
customMaintenanceData,
5263
customNotificationsData,
64+
customUserAccountPermissionsData,
65+
customUserEntityPermissionsData,
5366
handlers,
5467
onCustomAccountChange,
5568
onCustomProfileChange,
5669
onCustomEventsChange,
5770
onCustomMaintenanceChange,
5871
onCustomNotificationsChange,
72+
onCustomUserAccountPermissionsChange,
73+
onCustomUserEntityPermissionsChange,
5974
onPresetCountChange,
6075
onSelectChange,
6176
onTogglePreset,
@@ -113,6 +128,26 @@ export const ExtraPresetOptions = ({
113128
onTogglePreset={onTogglePreset}
114129
/>
115130
)}
131+
{currentGroupType === 'userPermissions' && (
132+
<>
133+
<ExtraPresetUserAccountPermissions
134+
customUserAccountPermissionsData={
135+
customUserAccountPermissionsData
136+
}
137+
handlers={handlers}
138+
onFormChange={onCustomUserAccountPermissionsChange}
139+
onTogglePreset={onTogglePreset}
140+
/>
141+
<ExtraPresetUserEntityPermissions
142+
customUserEntityPermissionsData={
143+
customUserEntityPermissionsData
144+
}
145+
handlers={handlers}
146+
onFormChange={onCustomUserEntityPermissionsChange}
147+
onTogglePreset={onTogglePreset}
148+
/>
149+
</>
150+
)}
116151
{currentGroupType === 'events' && (
117152
<ExtraPresetEvents
118153
customEventsData={customEventsData}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import { Dialog } from '@linode/ui';
2+
import * as React from 'react';
3+
4+
import { userAccountPermissionsFactory } from 'src/factories/userAccountPermissions';
5+
import { extraMockPresets } from 'src/mocks/presets';
6+
import { setCustomUserAccountPermissionsData } from 'src/mocks/presets/extra/userPermissions/customUserAccountPermissions';
7+
8+
import { saveCustomUserAccountPermissionsData } from '../utils';
9+
import { JsonTextArea } from './JsonTextArea';
10+
11+
import type { PermissionType } from '@linode/api-v4';
12+
13+
const customUserAccountPermissionsPreset = extraMockPresets.find(
14+
(p) => p.id === 'userAccountPermissions:custom'
15+
);
16+
17+
interface ExtraPresetUserAccountPermissionsProps {
18+
customUserAccountPermissionsData: null | PermissionType[] | undefined;
19+
handlers: string[];
20+
onFormChange?: (data: null | PermissionType[] | undefined) => void;
21+
onTogglePreset: (
22+
e: React.ChangeEvent<HTMLInputElement>,
23+
presetId: string
24+
) => void;
25+
}
26+
27+
export const ExtraPresetUserAccountPermissions = ({
28+
customUserAccountPermissionsData,
29+
handlers,
30+
onFormChange,
31+
onTogglePreset,
32+
}: ExtraPresetUserAccountPermissionsProps) => {
33+
const isEnabled = handlers.includes('userAccountPermissions:custom');
34+
const [formData, setFormData] = React.useState<PermissionType[]>(() => {
35+
const permissions = [...userAccountPermissionsFactory];
36+
const customPermissions = customUserAccountPermissionsData || [];
37+
return [...permissions, ...customPermissions];
38+
});
39+
const [isEditingCustomUserPermissions, setIsEditingCustomUserPermissions] =
40+
React.useState(false);
41+
42+
const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
43+
const { value } = e.target;
44+
45+
try {
46+
const newFormData = value;
47+
48+
if (!Array.isArray(newFormData)) {
49+
throw new Error('Expected an array');
50+
}
51+
52+
setFormData([...newFormData]);
53+
54+
if (isEnabled) {
55+
onFormChange?.(newFormData);
56+
}
57+
} catch (err) {
58+
// eslint-disable-next-line no-console
59+
console.error('Failed to parse JSON from input value:', value, err);
60+
}
61+
};
62+
63+
const handleTogglePreset = (e: React.ChangeEvent<HTMLInputElement>) => {
64+
if (!e.target.checked) {
65+
saveCustomUserAccountPermissionsData(null);
66+
} else {
67+
saveCustomUserAccountPermissionsData(formData);
68+
}
69+
onTogglePreset(e, 'userAccountPermissions:custom');
70+
};
71+
72+
React.useEffect(() => {
73+
if (!isEnabled) {
74+
setFormData([...userAccountPermissionsFactory]);
75+
setCustomUserAccountPermissionsData(null);
76+
} else if (isEnabled && customUserAccountPermissionsData) {
77+
setFormData([...customUserAccountPermissionsData]);
78+
setCustomUserAccountPermissionsData([
79+
...customUserAccountPermissionsData,
80+
]);
81+
}
82+
}, [isEnabled, customUserAccountPermissionsData]);
83+
84+
if (!customUserAccountPermissionsPreset) {
85+
return null;
86+
}
87+
88+
return (
89+
<li className="dev-tools__list-box__separator has-button">
90+
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
91+
<div>
92+
<label
93+
title={
94+
customUserAccountPermissionsPreset.desc ||
95+
customUserAccountPermissionsPreset.label
96+
}
97+
>
98+
<input
99+
checked={isEnabled}
100+
onChange={handleTogglePreset}
101+
type="checkbox"
102+
/>
103+
{customUserAccountPermissionsPreset.label}
104+
</label>
105+
</div>
106+
{isEnabled && (
107+
<div>
108+
<button
109+
className="dev-tools-button small"
110+
onClick={() => setIsEditingCustomUserPermissions(true)}
111+
>
112+
Edit
113+
</button>
114+
</div>
115+
)}
116+
</div>
117+
{isEnabled && isEditingCustomUserPermissions && (
118+
<Dialog
119+
onClose={() => setIsEditingCustomUserPermissions(false)}
120+
open={isEditingCustomUserPermissions}
121+
title="Edit Custom User Account Permissions"
122+
>
123+
<form
124+
className="dev-tools__modal-form"
125+
onSubmit={(e) => {
126+
e.preventDefault();
127+
setIsEditingCustomUserPermissions(false);
128+
}}
129+
>
130+
<FieldWrapper>
131+
<JsonTextArea
132+
label="User Permissions"
133+
name="user_permissions"
134+
onChange={handleInputChange}
135+
value={formData}
136+
/>
137+
</FieldWrapper>
138+
<button className="dev-tools-button" type="submit">
139+
Save
140+
</button>
141+
</form>
142+
</Dialog>
143+
)}
144+
</li>
145+
);
146+
};
147+
148+
const FieldWrapper = ({ children }: { children: React.ReactNode }) => {
149+
return <div className="dev-tools__modal-form__field">{children}</div>;
150+
};

0 commit comments

Comments
 (0)