Skip to content

Commit 4246a39

Browse files
committed
chore: fix sizing and perf optimization
1 parent 142f17d commit 4246a39

File tree

1 file changed

+199
-81
lines changed

1 file changed

+199
-81
lines changed
Lines changed: 199 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,181 @@
11
import { useFormikContext } from "formik";
2-
import { useCallback, useEffect, useState, useMemo } from "react";
2+
import { useCallback, useState, useEffect } from "react";
33
import { Switch } from "../../../ui/FormControls/Switch";
4-
import FormikCheckbox from "../../Forms/Formik/FormikCheckbox";
54
import { OBJECTS, getActionsForObject } from "../tokenUtils";
65
import { TokenFormValues } from "./CreateTokenForm";
76

87
const ScopeOptions = ["Read", "Write", "Admin", "Custom"] as const;
98
type ScopeType = (typeof ScopeOptions)[number];
109

10+
const ObjectPermissionOptions = ["None", "Read", "Write", "Admin"] as const;
11+
const McpPermissionOptions = ["None", "Admin"] as const;
12+
type ObjectPermissionType = (typeof ObjectPermissionOptions)[number];
13+
type McpPermissionType = (typeof McpPermissionOptions)[number];
14+
15+
// Component for individual object permission selection
16+
type ObjectPermissionSwitchProps = {
17+
object: string;
18+
isMcpSetup?: boolean;
19+
};
20+
21+
function ObjectPermissionSwitch({
22+
object,
23+
isMcpSetup
24+
}: ObjectPermissionSwitchProps) {
25+
const { setFieldValue, values } = useFormikContext<TokenFormValues>();
26+
27+
// Determine current permission level for this object
28+
const getCurrentPermission = (): ObjectPermissionType | McpPermissionType => {
29+
const objectActions = values.objectActions;
30+
31+
// Special handling for MCP setup mode
32+
if (isMcpSetup && object === "mcp") {
33+
return "Admin"; // Pre-select Admin (which maps to mcp:*) for MCP setup
34+
}
35+
36+
if (object === "mcp") {
37+
return objectActions["mcp:*"] ? "Admin" : "None";
38+
}
39+
40+
if (object === "playbooks") {
41+
const playbookActions = [
42+
"playbook:run",
43+
"playbook:approve",
44+
"playbook:cancel"
45+
];
46+
const hasAllPlaybookActions = playbookActions.every(
47+
(action) => objectActions[`${object}:${action}`]
48+
);
49+
const hasCrud = ["read", "create", "update", "delete"].every(
50+
(action) => objectActions[`${object}:${action}`]
51+
);
52+
53+
if (hasCrud && hasAllPlaybookActions) return "Admin";
54+
if (objectActions[`${object}:read`] && objectActions[`${object}:create`])
55+
return "Write";
56+
if (objectActions[`${object}:read`]) return "Read";
57+
return "None";
58+
}
59+
60+
// For other objects (non-mcp, non-playbooks)
61+
const hasCrud = ["read", "create", "update", "delete"].every(
62+
(action) => objectActions[`${object}:${action}`]
63+
);
64+
if (hasCrud) return "Admin";
65+
if (objectActions[`${object}:read`] && objectActions[`${object}:create`])
66+
return "Write";
67+
if (objectActions[`${object}:read`]) return "Read";
68+
return "None";
69+
};
70+
71+
const [selectedPermission, setSelectedPermission] = useState(() =>
72+
getCurrentPermission()
73+
);
74+
75+
// Handle initial MCP setup - apply the mcp:* permission when component mounts
76+
useEffect(() => {
77+
if (isMcpSetup && object === "mcp" && selectedPermission === "Admin") {
78+
setFieldValue(
79+
"objectActions",
80+
(currentObjectActions: Record<string, boolean>) => {
81+
const newObjectActions = { ...currentObjectActions };
82+
newObjectActions["mcp:*"] = true;
83+
return newObjectActions;
84+
}
85+
);
86+
}
87+
}, [isMcpSetup, object, selectedPermission, setFieldValue]);
88+
89+
const handlePermissionChange = useCallback(
90+
(permission: string) => {
91+
const newPermission = permission as
92+
| ObjectPermissionType
93+
| McpPermissionType;
94+
setSelectedPermission(newPermission);
95+
96+
// Update the objectActions in form state
97+
setFieldValue(
98+
"objectActions",
99+
(currentObjectActions: Record<string, boolean>) => {
100+
const newObjectActions = { ...currentObjectActions };
101+
const actions = getActionsForObject(object);
102+
103+
// Reset all actions for this object first
104+
actions.forEach((action) => {
105+
newObjectActions[`${object}:${action}`] = false;
106+
});
107+
108+
// Apply the selected permission level
109+
if (newPermission !== "None") {
110+
if (object === "mcp") {
111+
// MCP only has Admin option (maps to *)
112+
if (newPermission === "Admin") {
113+
newObjectActions["mcp:*"] = true;
114+
}
115+
} else if (object === "playbooks") {
116+
if (newPermission === "Read") {
117+
newObjectActions[`${object}:read`] = true;
118+
} else if (newPermission === "Write") {
119+
newObjectActions[`${object}:read`] = true;
120+
newObjectActions[`${object}:create`] = true;
121+
} else if (newPermission === "Admin") {
122+
// For playbooks Admin: CRUD + all 3 specific playbook actions
123+
["read", "create", "update", "delete"].forEach((action) => {
124+
newObjectActions[`${object}:${action}`] = true;
125+
});
126+
["playbook:run", "playbook:approve", "playbook:cancel"].forEach(
127+
(action) => {
128+
newObjectActions[`${object}:${action}`] = true;
129+
}
130+
);
131+
}
132+
} else {
133+
// For other objects
134+
if (newPermission === "Read") {
135+
newObjectActions[`${object}:read`] = true;
136+
} else if (newPermission === "Write") {
137+
newObjectActions[`${object}:read`] = true;
138+
newObjectActions[`${object}:create`] = true;
139+
} else if (newPermission === "Admin") {
140+
["read", "create", "update", "delete"].forEach((action) => {
141+
newObjectActions[`${object}:${action}`] = true;
142+
});
143+
}
144+
}
145+
}
146+
147+
return newObjectActions;
148+
}
149+
);
150+
},
151+
[object, setFieldValue]
152+
);
153+
154+
// Get the appropriate options based on object type
155+
const getOptionsForObject = () => {
156+
if (object === "mcp") {
157+
return McpPermissionOptions as unknown as string[];
158+
}
159+
return ObjectPermissionOptions as unknown as string[];
160+
};
161+
162+
return (
163+
<div className="flex flex-row items-center space-x-4">
164+
<label className="w-20 flex-shrink-0 text-sm font-medium text-gray-800">
165+
{object}
166+
</label>
167+
<div className="flex flex-row">
168+
<Switch
169+
options={getOptionsForObject()}
170+
defaultValue="None"
171+
value={selectedPermission as string}
172+
onChange={handlePermissionChange}
173+
/>
174+
</div>
175+
</div>
176+
);
177+
}
178+
11179
// Pre-calculate scope mappings outside component to avoid recalculation
12180
const SCOPE_MAPPINGS = {
13181
Read: (() => {
@@ -55,23 +223,14 @@ const SCOPE_MAPPINGS = {
55223
})()
56224
};
57225

58-
// Pre-calculate object actions to avoid function calls in render
59-
const OBJECT_ACTIONS = OBJECTS.reduce(
60-
(acc, object) => {
61-
acc[object] = getActionsForObject(object);
62-
return acc;
63-
},
64-
{} as Record<string, string[]>
65-
);
66-
67226
type TokenScopeFieldsGroupProps = {
68227
isMcpSetup?: boolean;
69228
};
70229

71230
export default function TokenScopeFieldsGroup({
72231
isMcpSetup = false
73232
}: TokenScopeFieldsGroupProps) {
74-
const { setFieldValue, values } = useFormikContext<TokenFormValues>();
233+
const { setFieldValue } = useFormikContext<TokenFormValues>();
75234

76235
const [selectedScope, setSelectedScope] = useState<ScopeType>(() => {
77236
if (isMcpSetup) {
@@ -80,41 +239,36 @@ export default function TokenScopeFieldsGroup({
80239
return "Read";
81240
});
82241

83-
// Memoize the keys from objectActions to avoid dependency on the whole object
84-
const objectActionKeys = useMemo(() => {
85-
return Object.keys(values.objectActions);
86-
}, [values.objectActions]);
242+
const handleScopeChange = useCallback(
243+
(scope: string) => {
244+
const newScope = scope as ScopeType;
245+
setSelectedScope(newScope);
87246

88-
const applyScopePreset = useCallback(
89-
(scope: ScopeType) => {
90-
let newObjectActions: Record<string, boolean> = {};
247+
if (newScope !== "Custom") {
248+
// Use setFieldValue with a function to get current values
249+
setFieldValue(
250+
"objectActions",
251+
(currentObjectActions: Record<string, boolean>) => {
252+
const newObjectActions: Record<string, boolean> = {};
91253

92-
// Reset all scopes first
93-
objectActionKeys.forEach((key) => {
94-
newObjectActions[key] = false;
95-
});
254+
// Reset all scopes first
255+
Object.keys(currentObjectActions).forEach((key) => {
256+
newObjectActions[key] = false;
257+
});
96258

97-
if (scope !== "Custom") {
98-
// Use pre-calculated scope mappings
99-
newObjectActions = { ...newObjectActions, ...SCOPE_MAPPINGS[scope] };
100-
} else if (isMcpSetup) {
101-
// Pre-select MCP * action for MCP setup
102-
newObjectActions["mcp:*"] = true;
103-
}
259+
// Apply the selected preset
260+
Object.assign(newObjectActions, SCOPE_MAPPINGS[newScope]);
104261

105-
setFieldValue("objectActions", newObjectActions);
262+
return newObjectActions;
263+
}
264+
);
265+
}
266+
// Note: For Custom mode, individual ObjectPermissionSwitch components handle their own state
267+
// For MCP setup, the individual ObjectPermissionSwitch for mcp will handle the pre-selection
106268
},
107-
[objectActionKeys, setFieldValue, isMcpSetup]
269+
[setFieldValue]
108270
);
109271

110-
useEffect(() => {
111-
applyScopePreset(selectedScope);
112-
}, [selectedScope, applyScopePreset]);
113-
114-
const handleScopeChange = useCallback((scope: string) => {
115-
setSelectedScope(scope as ScopeType);
116-
}, []);
117-
118272
return (
119273
<div className="space-y-3">
120274
<div className="text-sm font-medium text-gray-700">
@@ -125,7 +279,6 @@ export default function TokenScopeFieldsGroup({
125279
</div>
126280

127281
<div className="flex flex-col space-y-2">
128-
<label className="text-sm font-semibold">Permission Level</label>
129282
<div className="flex w-full flex-row">
130283
<Switch
131284
options={ScopeOptions as unknown as string[]}
@@ -139,49 +292,14 @@ export default function TokenScopeFieldsGroup({
139292
{selectedScope === "Custom" && (
140293
<div className="max-h-64 space-y-4 overflow-y-auto rounded-md border bg-gray-50 p-4">
141294
{OBJECTS.map((object) => (
142-
<div key={object} className="space-y-2">
143-
<div className="text-sm font-medium text-gray-800">{object}</div>
144-
<div className="grid grid-cols-4 gap-2 pl-4">
145-
{OBJECT_ACTIONS[object].map((action) => {
146-
const scopeKey = `${object}:${action}`;
147-
return (
148-
<FormikCheckbox
149-
key={scopeKey}
150-
name={`objectActions.${scopeKey}`}
151-
label={action}
152-
labelClassName="text-sm font-normal text-gray-600"
153-
inline={true}
154-
/>
155-
);
156-
})}
157-
</div>
158-
</div>
295+
<ObjectPermissionSwitch
296+
key={object}
297+
object={object}
298+
isMcpSetup={isMcpSetup}
299+
/>
159300
))}
160301
</div>
161302
)}
162-
163-
{selectedScope !== "Custom" && (
164-
<div className="rounded-md border bg-blue-50 p-3">
165-
<div className="text-sm text-blue-800">
166-
<strong>{selectedScope}</strong> permissions selected:
167-
<ul className="mt-1 list-inside list-disc text-xs">
168-
{selectedScope === "Read" && <li>Read access to all objects</li>}
169-
{selectedScope === "Write" && (
170-
<>
171-
<li>Read access to all objects</li>
172-
<li>Create access to all objects</li>
173-
</>
174-
)}
175-
{selectedScope === "Admin" && (
176-
<>
177-
<li>Full CRUD access to all objects</li>
178-
<li>Playbook execution permissions</li>
179-
</>
180-
)}
181-
</ul>
182-
</div>
183-
</div>
184-
)}
185303
</div>
186304
);
187305
}

0 commit comments

Comments
 (0)