Skip to content

Commit e0ef0f4

Browse files
committed
fix: show MCP token creation error inside the modal
1 parent 3e2fd4b commit e0ef0f4

File tree

9 files changed

+351
-285
lines changed

9 files changed

+351
-285
lines changed

src/components/MCP/UserList.tsx

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import {
2+
MCP_SETTINGS_PERMISSION_SOURCE,
3+
PermissionSubject
4+
} from "@flanksource-ui/api/services/permissions";
5+
import { PermissionsSummary } from "@flanksource-ui/api/types/permissions";
6+
import SubjectAccessCard from "@flanksource-ui/components/Permissions/SubjectAccessCard";
7+
8+
const MCP_OBJECT = "mcp";
9+
const MCP_ACTION = "mcp:use";
10+
11+
const TYPE_LABELS: Record<PermissionSubject["type"], string> = {
12+
person: "person",
13+
team: "team",
14+
role: "role",
15+
permission_subject_group: "group"
16+
};
17+
18+
type GroupedSubject = {
19+
type: PermissionSubject["type"];
20+
subjects: PermissionSubject[];
21+
};
22+
23+
type Props = {
24+
groupedSubjects: GroupedSubject[];
25+
permissionsByUser: Map<string, PermissionsSummary[]>;
26+
mutatingSubjectId: string | null;
27+
onChangeAccess: (
28+
subject: PermissionSubject,
29+
access: "allow" | "deny" | "default"
30+
) => void;
31+
};
32+
33+
export default function UserList({
34+
groupedSubjects,
35+
permissionsByUser,
36+
mutatingSubjectId,
37+
onChangeAccess
38+
}: Props) {
39+
return (
40+
<div className="flex h-full w-full flex-1 flex-col gap-4 p-6 pb-6">
41+
{groupedSubjects.map((group) => (
42+
<div key={group.type} className="space-y-1">
43+
<div className="pt-2 text-xs font-semibold uppercase tracking-wide text-gray-500 first:pt-0">
44+
{TYPE_LABELS[group.type] ?? group.type}
45+
</div>
46+
47+
<div className="[&>*+*]:border-t [&>*+*]:border-gray-200 [&>*+*]:pt-2 [&>*]:pb-2">
48+
{group.subjects.map((subject) => {
49+
const permissions = permissionsByUser.get(subject.id) ?? [];
50+
51+
const activePermission = permissions.find(
52+
(permission) =>
53+
permission.source === MCP_SETTINGS_PERMISSION_SOURCE
54+
);
55+
56+
const access = !activePermission
57+
? "default"
58+
: activePermission.deny === true
59+
? "deny"
60+
: "allow";
61+
62+
return (
63+
<SubjectAccessCard
64+
key={subject.id}
65+
user={{
66+
id: subject.id,
67+
name: subject.name,
68+
type: subject.type
69+
}}
70+
action={MCP_ACTION}
71+
object={MCP_OBJECT}
72+
access={access}
73+
isMutating={mutatingSubjectId === subject.id}
74+
onChangeAccess={(access) => onChangeAccess(subject, access)}
75+
/>
76+
);
77+
})}
78+
</div>
79+
</div>
80+
))}
81+
</div>
82+
);
83+
}

src/components/Permissions/PermissionAccessCard.tsx

Lines changed: 0 additions & 97 deletions
This file was deleted.
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import { Icon } from "@flanksource-ui/ui/Icons/Icon";
2+
import { Switch } from "@flanksource-ui/ui/FormControls/Switch";
3+
4+
type GlobalOverride = "allow" | "none" | "deny";
5+
6+
type Entity = {
7+
id: string;
8+
name: string;
9+
namespace?: string;
10+
icon?: string;
11+
};
12+
13+
type PermissionAccessCardProps = {
14+
entity: Entity;
15+
globalOverride?: GlobalOverride;
16+
onGlobalOverrideChange: (value: GlobalOverride) => void;
17+
onViewSubjects?: () => void;
18+
isMutating?: boolean;
19+
};
20+
21+
const SWITCH_OPTIONS = ["Deny all", "Custom", "Allow all"];
22+
type SwitchOption = "Deny all" | "Custom" | "Allow all";
23+
24+
function toSwitchOption(value: GlobalOverride): SwitchOption {
25+
switch (value) {
26+
case "deny":
27+
return "Deny all";
28+
case "allow":
29+
return "Allow all";
30+
default:
31+
return "Custom";
32+
}
33+
}
34+
35+
function toGlobalOverride(value: string): GlobalOverride {
36+
switch (value) {
37+
case "Deny all":
38+
return "deny";
39+
case "Allow all":
40+
return "allow";
41+
default:
42+
return "none";
43+
}
44+
}
45+
46+
export default function ResourceAccessCard({
47+
entity,
48+
globalOverride = "none",
49+
onGlobalOverrideChange,
50+
onViewSubjects,
51+
isMutating = false
52+
}: PermissionAccessCardProps) {
53+
const canOpenSubjects = globalOverride === "none" && Boolean(onViewSubjects);
54+
55+
const handleCardClick = () => {
56+
if (!canOpenSubjects) {
57+
return;
58+
}
59+
60+
onViewSubjects?.();
61+
};
62+
63+
return (
64+
<div
65+
className={`w-full max-w-3xl ${canOpenSubjects ? "cursor-pointer" : ""}`}
66+
onClick={handleCardClick}
67+
>
68+
<div className="flex items-start gap-3">
69+
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-gray-100 text-xs font-semibold text-gray-700">
70+
<Icon name={entity.icon ?? "playbook"} className="h-4 w-4" />
71+
</div>
72+
73+
<div className="flex min-w-0 flex-1 items-start justify-between gap-2">
74+
<div className="min-w-0">
75+
<div
76+
className="truncate text-sm font-semibold text-gray-900"
77+
title={entity.name}
78+
>
79+
{entity.name}
80+
</div>
81+
{entity.namespace && (
82+
<div
83+
className="mt-0.5 truncate text-xs text-gray-500"
84+
title={entity.namespace}
85+
>
86+
{entity.namespace}
87+
</div>
88+
)}
89+
</div>
90+
91+
<div
92+
className="shrink-0"
93+
onClick={(event) => event.stopPropagation()}
94+
>
95+
<div
96+
className={
97+
isMutating ? "pointer-events-none opacity-60" : undefined
98+
}
99+
aria-disabled={isMutating || undefined}
100+
>
101+
<Switch
102+
size="sm"
103+
options={SWITCH_OPTIONS}
104+
value={toSwitchOption(globalOverride)}
105+
onChange={(value) =>
106+
onGlobalOverrideChange(toGlobalOverride(value))
107+
}
108+
className="h-auto"
109+
itemsClassName=""
110+
getActiveItemClassName={(option) => {
111+
if (option === "Allow all") {
112+
return "bg-blue-50 text-blue-700 ring-blue-200";
113+
}
114+
115+
if (option === "Deny all") {
116+
return "bg-red-50 text-red-700 ring-red-200";
117+
}
118+
119+
return undefined;
120+
}}
121+
/>
122+
</div>
123+
</div>
124+
</div>
125+
</div>
126+
</div>
127+
);
128+
}

0 commit comments

Comments
 (0)