@elevasis/ui 2.74.0 → 2.76.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/index.js +3 -3
- package/dist/app/index.d.ts +277 -249
- package/dist/app/index.js +12 -11
- package/dist/auth/index.js +6 -7
- package/dist/charts/index.js +6 -7
- package/dist/{chunk-GO4XJ5D4.js → chunk-2GIHWU5O.js} +4 -3
- package/dist/chunk-2Y6AKWGU.js +42 -0
- package/dist/chunk-3QYRZSHP.js +854 -0
- package/dist/{chunk-CRA7WQZH.js → chunk-3RK2NNJX.js} +1 -1
- package/dist/{chunk-R6NMRXNO.js → chunk-4WLAVUPS.js} +1 -1
- package/dist/{chunk-GPQQGQGT.js → chunk-5R4I2PAN.js} +9 -43
- package/dist/{chunk-R5UGIFYU.js → chunk-5ZK7K7YP.js} +25 -1
- package/dist/chunk-6B4ZPBQJ.js +7 -0
- package/dist/{chunk-ARAFQBNI.js → chunk-73G3I5HJ.js} +160 -160
- package/dist/{chunk-ZWI62BQ6.js → chunk-BFLYDVEN.js} +32 -42
- package/dist/{chunk-W2QRBQQA.js → chunk-BG6PWDJ3.js} +381 -381
- package/dist/{chunk-7ARBBELS.js → chunk-BV2RNLPP.js} +1 -1
- package/dist/{chunk-FA7LMNQO.js → chunk-CTT4RDXE.js} +150 -151
- package/dist/chunk-E2LJ3FRA.js +237 -0
- package/dist/{chunk-E6YGA5WH.js → chunk-EFQLXRYT.js} +1 -2
- package/dist/{chunk-FZSG6M6W.js → chunk-EVNLJHDD.js} +3 -3
- package/dist/{chunk-ZYNVCWDZ.js → chunk-GDQQX5SL.js} +4 -42
- package/dist/{chunk-DCQKTDIG.js → chunk-GFSOQH3N.js} +1 -1
- package/dist/{chunk-3LOKYZJC.js → chunk-H5AEYSVX.js} +164 -3
- package/dist/{chunk-ZFISGKYW.js → chunk-IIEZXXDD.js} +1 -1
- package/dist/{chunk-3QHX4ZKV.js → chunk-INCEZAM3.js} +1 -1
- package/dist/{chunk-O32UQ26A.js → chunk-JZQNUQE4.js} +1 -1
- package/dist/{chunk-6JN2TLV2.js → chunk-K55TU6CC.js} +2023 -1700
- package/dist/chunk-KB5CAN5D.js +33 -0
- package/dist/{chunk-2MRRDF32.js → chunk-KMA4FOSV.js} +5682 -5675
- package/dist/{chunk-HYQOGRWG.js → chunk-KXNRTJAE.js} +1 -1
- package/dist/{chunk-7RJ7P6S6.js → chunk-MGAELVTZ.js} +12 -12
- package/dist/chunk-MKH2KOAO.js +39 -0
- package/dist/{chunk-4MXNHJTA.js → chunk-NAPXMYFW.js} +939 -1570
- package/dist/{chunk-CQPULKKL.js → chunk-OGG3QJYW.js} +14 -5
- package/dist/{chunk-4M2BSHVT.js → chunk-P6HHXMLJ.js} +1 -1
- package/dist/{chunk-WZU3MCKI.js → chunk-QAILYRC2.js} +1 -1
- package/dist/{chunk-RBGVNPA6.js → chunk-RXZ3XDBM.js} +1 -1
- package/dist/{chunk-BA3AZJUR.js → chunk-SPW6UA6B.js} +1 -1
- package/dist/{chunk-RFNJ2PTO.js → chunk-SVZR4GYA.js} +1 -1
- package/dist/{chunk-5A6KCFS5.js → chunk-UEY4AB3R.js} +789 -66
- package/dist/{chunk-LN4UW6S4.js → chunk-UL6MPMKY.js} +1 -1
- package/dist/{chunk-ZFA56VSR.js → chunk-WELN7DDV.js} +2 -2
- package/dist/{chunk-43LC6XAX.js → chunk-WJN2VVVJ.js} +1 -1
- package/dist/{chunk-BRTGCFBH.js → chunk-XODQUPFW.js} +11 -11
- package/dist/components/index.d.ts +181 -34
- package/dist/components/index.js +2249 -1804
- package/dist/components/navigation/index.js +7 -8
- package/dist/features/auth/index.d.ts +35 -3716
- package/dist/features/auth/index.js +9 -10
- package/dist/features/clients/index.js +18 -12
- package/dist/features/content/index.js +34 -31
- package/dist/features/crm/index.css +59 -59
- package/dist/features/crm/index.d.ts +18 -1
- package/dist/features/crm/index.js +13 -11
- package/dist/features/dashboard/index.css +59 -59
- package/dist/features/dashboard/index.js +8 -9
- package/dist/features/delivery/index.d.ts +3 -0
- package/dist/features/delivery/index.js +21 -20
- package/dist/features/knowledge/index.js +6 -7
- package/dist/features/lead-gen/index.css +59 -59
- package/dist/features/lead-gen/index.d.ts +3903 -2
- package/dist/features/lead-gen/index.js +15 -13
- package/dist/features/monitoring/index.js +10 -11
- package/dist/features/monitoring/requests/index.css +59 -59
- package/dist/features/monitoring/requests/index.js +8 -9
- package/dist/features/notes/index.js +4 -4
- package/dist/features/operations/index.css +59 -59
- package/dist/features/operations/index.js +13 -15
- package/dist/features/seo/index.css +659 -0
- package/dist/features/seo/index.d.ts +3962 -1
- package/dist/features/seo/index.js +31 -2
- package/dist/features/settings/index.css +59 -59
- package/dist/features/settings/index.d.ts +87 -3745
- package/dist/features/settings/index.js +10 -12
- package/dist/hooks/access/index.js +6 -7
- package/dist/hooks/delivery/index.css +59 -59
- package/dist/hooks/delivery/index.d.ts +13 -4
- package/dist/hooks/delivery/index.js +3 -3
- package/dist/hooks/published.d.ts +69 -27
- package/dist/hooks/published.js +19 -19
- package/dist/initialization/index.d.ts +35 -3716
- package/dist/knowledge/index.js +11 -11
- package/dist/layout/index.d.ts +55 -2
- package/dist/layout/index.js +10 -10
- package/dist/organization/index.css +59 -59
- package/dist/profile/index.d.ts +64 -3716
- package/dist/provider/index.d.ts +3 -0
- package/dist/provider/index.js +6 -7
- package/dist/provider/published.d.ts +3 -0
- package/dist/provider/published.js +6 -7
- package/dist/test-utils/index.js +3 -3
- package/dist/theme/index.js +3 -3
- package/dist/theme/presets/index.js +1 -1
- package/dist/types/index.d.ts +312 -251
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- package/package.json +9 -9
- package/src/hooks/README.md +22 -23
- package/dist/chunk-6ZAP3FOA.js +0 -93
- package/dist/chunk-AH52XQB7.js +0 -1
- package/dist/chunk-GDOR57D7.js +0 -165
- package/dist/chunk-MISHPEJX.js +0 -244
- package/dist/hooks/operations/command-view/utils/transformCommandViewData.d.ts +0 -879
- package/dist/hooks/operations/command-view/utils/transformCommandViewData.js +0 -2
|
@@ -1,58 +1,25 @@
|
|
|
1
|
-
import { useAvailablePresets } from './chunk-
|
|
2
|
-
import { MEMBERSHIP_STATUS_COLORS, PROVISIONING_STATE_DISPLAY, transformMembershipToTableRow, useUserMemberships, useUpdateWebhookEndpoint, useDeleteWebhookEndpoint, useCreateWebhookEndpoint, useListWebhookEndpoints, useOrgRoles, useAssignRole, useRevokeRole, useOrganizationMembers, usePermissionCatalog } from './chunk-
|
|
3
|
-
import {
|
|
4
|
-
import { AppShellLoader } from './chunk-E6YGA5WH.js';
|
|
1
|
+
import { useAvailablePresets } from './chunk-IIEZXXDD.js';
|
|
2
|
+
import { MEMBERSHIP_STATUS_COLORS, PROVISIONING_STATE_DISPLAY, transformMembershipToTableRow, useUserMemberships, useUpdateWebhookEndpoint, useDeleteWebhookEndpoint, useCreateWebhookEndpoint, useListWebhookEndpoints, useOrgRoles, useAssignRole, useRevokeRole, useOrganizationMembers, usePermissionCatalog } from './chunk-73G3I5HJ.js';
|
|
3
|
+
import { AppShellLoader } from './chunk-EFQLXRYT.js';
|
|
5
4
|
import { FilterBar } from './chunk-PDHTXPSF.js';
|
|
6
|
-
import {
|
|
7
|
-
import { showApiErrorNotification, showErrorNotification, showSuccessNotification } from './chunk-
|
|
5
|
+
import { CustomModal, APIErrorAlert, EmptyState, PageTitleCaption, useResources, ListSkeleton, StatCard, useAccess, AccessKeys, AccessGuard } from './chunk-K55TU6CC.js';
|
|
6
|
+
import { showApiErrorNotification, showErrorNotification, showSuccessNotification } from './chunk-KXNRTJAE.js';
|
|
8
7
|
import { CardHeader } from './chunk-62GVNH5U.js';
|
|
9
8
|
import { useInitialization } from './chunk-P45GQ3ZW.js';
|
|
10
9
|
import { RoleBadge } from './chunk-UD657ICS.js';
|
|
11
10
|
import { useOrganization } from './chunk-DD3CCMCZ.js';
|
|
12
|
-
import { isAPIClientError, formatDateTime, OAUTH_FLOW_TIMEOUT } from './chunk-
|
|
11
|
+
import { isAPIClientError, formatDateTime, OAUTH_FLOW_TIMEOUT } from './chunk-5ZK7K7YP.js';
|
|
13
12
|
import { useElevasisServices } from './chunk-BI5VDO2K.js';
|
|
14
|
-
import { brandSupabaseOrgId } from './chunk-MQZE7SUI.js';
|
|
15
13
|
import { useAuthContext } from './chunk-BRJ3QZ4E.js';
|
|
16
|
-
import {
|
|
17
|
-
import { IconShieldOff,
|
|
18
|
-
import {
|
|
14
|
+
import { Text, Stack, Title, Alert, TextInput, Group, Button, Paper, SimpleGrid, Card, ThemeIcon, Badge, Tooltip, Select, Table, Center, Loader, Box, Code, CopyButton, ActionIcon, UnstyledButton, Switch, Divider, Textarea, MultiSelect } from '@mantine/core';
|
|
15
|
+
import { IconShieldOff, IconAlertCircle, IconBrandDropbox, IconBrandGoogleDrive, IconPlug, IconUsers, IconSearch, IconBuilding, IconWebhook, IconCheck, IconCopy, IconUser, IconMail, IconPalette, IconEye, IconSparkles, IconTrendingUp, IconExclamationMark, IconPencil, IconClock, IconPlayerPause, IconPlayerPlay, IconTrash, IconAlertTriangle, IconPlus, IconActivity, IconShieldLock, IconStar, IconUserCog, IconUsersGroup, IconSettings, IconEdit, IconUserX, IconUserCheck } from '@tabler/icons-react';
|
|
16
|
+
import { modals } from '@mantine/modals';
|
|
19
17
|
import { notifications } from '@mantine/notifications';
|
|
20
|
-
import { z } from 'zod';
|
|
21
18
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
22
19
|
import { useState, useEffect, useRef, useMemo, useCallback } from 'react';
|
|
23
|
-
import { useForm } from '@mantine/form';
|
|
24
|
-
import { create } from 'zustand';
|
|
25
20
|
import { useQueryClient } from '@tanstack/react-query';
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
var openModal = (payload) => {
|
|
29
|
-
const id = payload.modalId || randomId();
|
|
30
|
-
createEvent("openModal")({ ...payload, modalId: id });
|
|
31
|
-
return id;
|
|
32
|
-
};
|
|
33
|
-
var openConfirmModal = (payload) => {
|
|
34
|
-
const id = payload.modalId || randomId();
|
|
35
|
-
createEvent("openConfirmModal")({ ...payload, modalId: id });
|
|
36
|
-
return id;
|
|
37
|
-
};
|
|
38
|
-
var openContextModal = (payload) => {
|
|
39
|
-
const id = payload.modalId || randomId();
|
|
40
|
-
createEvent("openContextModal")({ ...payload, modalId: id });
|
|
41
|
-
return id;
|
|
42
|
-
};
|
|
43
|
-
var closeModal = createEvent("closeModal");
|
|
44
|
-
var closeAllModals = createEvent("closeAllModals");
|
|
45
|
-
var updateModal = (payload) => createEvent("updateModal")(payload);
|
|
46
|
-
var updateContextModal = (payload) => createEvent("updateContextModal")(payload);
|
|
47
|
-
var modals = {
|
|
48
|
-
open: openModal,
|
|
49
|
-
close: closeModal,
|
|
50
|
-
closeAll: closeAllModals,
|
|
51
|
-
openConfirmModal,
|
|
52
|
-
openContextModal,
|
|
53
|
-
updateModal,
|
|
54
|
-
updateContextModal
|
|
55
|
-
};
|
|
21
|
+
import { create } from 'zustand';
|
|
22
|
+
import { useForm } from '@mantine/form';
|
|
56
23
|
|
|
57
24
|
// ../core/src/integrations/oauth/provider-registry.ts
|
|
58
25
|
var OAUTH_PROVIDERS = {
|
|
@@ -115,178 +82,6 @@ var OAUTH_PROVIDERS = {
|
|
|
115
82
|
]
|
|
116
83
|
}
|
|
117
84
|
};
|
|
118
|
-
var CredentialFieldSchema = z.object({
|
|
119
|
-
key: z.string(),
|
|
120
|
-
label: z.string(),
|
|
121
|
-
type: z.enum(["password", "text"]),
|
|
122
|
-
required: z.boolean(),
|
|
123
|
-
placeholder: z.string().optional(),
|
|
124
|
-
description: z.string().optional()
|
|
125
|
-
});
|
|
126
|
-
var CredentialSchemaZod = z.object({
|
|
127
|
-
type: z.string(),
|
|
128
|
-
label: z.string(),
|
|
129
|
-
description: z.string(),
|
|
130
|
-
fields: z.array(CredentialFieldSchema).optional(),
|
|
131
|
-
docsUrl: z.string().url().optional(),
|
|
132
|
-
nameSuggestions: z.array(z.string()),
|
|
133
|
-
oauthProvider: z.string().optional()
|
|
134
|
-
});
|
|
135
|
-
var CREDENTIAL_SCHEMAS = {
|
|
136
|
-
"google-sheets": {
|
|
137
|
-
type: "oauth",
|
|
138
|
-
label: "Google Sheets",
|
|
139
|
-
description: "Google Sheets OAuth integration",
|
|
140
|
-
oauthProvider: "google-sheets",
|
|
141
|
-
nameSuggestions: ["google-sheets-prod", "google-sheets-dev", "sheets-workspace"]
|
|
142
|
-
},
|
|
143
|
-
dropbox: {
|
|
144
|
-
type: "oauth",
|
|
145
|
-
label: "Dropbox",
|
|
146
|
-
description: "Dropbox OAuth integration",
|
|
147
|
-
oauthProvider: "dropbox",
|
|
148
|
-
nameSuggestions: ["dropbox-prod", "dropbox-dev", "elevasis-dropbox"]
|
|
149
|
-
},
|
|
150
|
-
oauth: {
|
|
151
|
-
type: "oauth",
|
|
152
|
-
label: "OAuth",
|
|
153
|
-
description: "Generic OAuth credential",
|
|
154
|
-
nameSuggestions: []
|
|
155
|
-
},
|
|
156
|
-
"api-key": {
|
|
157
|
-
type: "single_field",
|
|
158
|
-
label: "API Key",
|
|
159
|
-
description: "Single-field API key credential",
|
|
160
|
-
fields: [
|
|
161
|
-
{
|
|
162
|
-
key: "apiKey",
|
|
163
|
-
label: "API Key",
|
|
164
|
-
type: "password",
|
|
165
|
-
required: true,
|
|
166
|
-
placeholder: "Enter your API key",
|
|
167
|
-
description: "API key for the service"
|
|
168
|
-
}
|
|
169
|
-
],
|
|
170
|
-
nameSuggestions: ["service-prod", "service-dev", "api-key"]
|
|
171
|
-
},
|
|
172
|
-
"webhook-secret": {
|
|
173
|
-
type: "single_field",
|
|
174
|
-
label: "Webhook Secret",
|
|
175
|
-
description: "Webhook signing secret for signature validation",
|
|
176
|
-
fields: [
|
|
177
|
-
{
|
|
178
|
-
key: "signingSecret",
|
|
179
|
-
label: "Signing Secret",
|
|
180
|
-
type: "password",
|
|
181
|
-
required: true,
|
|
182
|
-
placeholder: "whsec_...",
|
|
183
|
-
description: "Webhook signing secret from provider dashboard"
|
|
184
|
-
}
|
|
185
|
-
],
|
|
186
|
-
nameSuggestions: ["my-org-cal-com-webhook", "my-org-stripe-webhook", "my-org-signature-api-webhook"]
|
|
187
|
-
},
|
|
188
|
-
"api-key-secret": {
|
|
189
|
-
type: "api-key-secret",
|
|
190
|
-
label: "API Key + Secret",
|
|
191
|
-
description: "API key and secret pair authentication",
|
|
192
|
-
fields: [
|
|
193
|
-
{
|
|
194
|
-
key: "apiKey",
|
|
195
|
-
label: "API Key",
|
|
196
|
-
type: "password",
|
|
197
|
-
required: true
|
|
198
|
-
},
|
|
199
|
-
{
|
|
200
|
-
key: "apiSecret",
|
|
201
|
-
label: "API Secret",
|
|
202
|
-
type: "password",
|
|
203
|
-
required: true
|
|
204
|
-
}
|
|
205
|
-
],
|
|
206
|
-
nameSuggestions: []
|
|
207
|
-
},
|
|
208
|
-
apify: {
|
|
209
|
-
type: "single_field",
|
|
210
|
-
label: "Apify",
|
|
211
|
-
description: "Apify API token for running web-scraping actors (e.g. website crawl, Google Places scrape).",
|
|
212
|
-
fields: [
|
|
213
|
-
{
|
|
214
|
-
key: "apiKey",
|
|
215
|
-
label: "API Token",
|
|
216
|
-
type: "password",
|
|
217
|
-
required: true,
|
|
218
|
-
placeholder: "apify_api_...",
|
|
219
|
-
description: "Personal API token from https://console.apify.com/account/integrations"
|
|
220
|
-
}
|
|
221
|
-
],
|
|
222
|
-
docsUrl: "https://docs.apify.com/platform/integrations/api",
|
|
223
|
-
nameSuggestions: ["elevasis-apify", "apify-prod", "apify-dev"]
|
|
224
|
-
},
|
|
225
|
-
clickup: {
|
|
226
|
-
type: "single_field",
|
|
227
|
-
label: "ClickUp",
|
|
228
|
-
description: "ClickUp personal API token for creating tasks in tenant-managed ClickUp workspaces.",
|
|
229
|
-
fields: [
|
|
230
|
-
{
|
|
231
|
-
key: "apiToken",
|
|
232
|
-
label: "API Token",
|
|
233
|
-
type: "password",
|
|
234
|
-
required: true,
|
|
235
|
-
placeholder: "pk_...",
|
|
236
|
-
description: "Personal API token from ClickUp settings. Personal tokens start with pk_."
|
|
237
|
-
}
|
|
238
|
-
],
|
|
239
|
-
docsUrl: "https://developer.clickup.com/docs/authentication",
|
|
240
|
-
nameSuggestions: ["clickup-demo", "clickup-prod", "clickup-dev"]
|
|
241
|
-
},
|
|
242
|
-
instagram: {
|
|
243
|
-
type: "single_field",
|
|
244
|
-
label: "Instagram",
|
|
245
|
-
description: "Long-lived Instagram access token for publishing through Meta\u2019s Content Publishing API.",
|
|
246
|
-
fields: [
|
|
247
|
-
{
|
|
248
|
-
key: "accessToken",
|
|
249
|
-
label: "Access Token",
|
|
250
|
-
type: "password",
|
|
251
|
-
required: true,
|
|
252
|
-
placeholder: "IGAA...",
|
|
253
|
-
description: "Long-lived token from the Meta app dashboard. Valid 60 days from issue or last refresh."
|
|
254
|
-
},
|
|
255
|
-
{
|
|
256
|
-
key: "igUserId",
|
|
257
|
-
label: "Instagram User ID",
|
|
258
|
-
type: "text",
|
|
259
|
-
required: true,
|
|
260
|
-
placeholder: "17841400000000000",
|
|
261
|
-
description: "The Instagram professional account id shown beside the token on the same Meta screen."
|
|
262
|
-
}
|
|
263
|
-
],
|
|
264
|
-
docsUrl: "https://developers.facebook.com/docs/instagram-platform/instagram-api-with-instagram-login/content-publishing",
|
|
265
|
-
nameSuggestions: ["elevasis-instagram", "instagram-prod", "instagram-dev"]
|
|
266
|
-
}
|
|
267
|
-
};
|
|
268
|
-
function getCredentialSchema(type) {
|
|
269
|
-
const schema = CREDENTIAL_SCHEMAS[type] || CREDENTIAL_SCHEMAS["api-key"];
|
|
270
|
-
return CredentialSchemaZod.parse(schema);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// ../core/src/integrations/credentials/utils.ts
|
|
274
|
-
function buildCredentialValue(type, formValues) {
|
|
275
|
-
const schema = getCredentialSchema(type);
|
|
276
|
-
if (schema.type === "oauth") {
|
|
277
|
-
throw new Error(`buildCredentialValue should not be used for OAuth type: ${type}`);
|
|
278
|
-
}
|
|
279
|
-
const value = {};
|
|
280
|
-
if (schema.fields) {
|
|
281
|
-
for (const field of schema.fields) {
|
|
282
|
-
const fieldValue = formValues[field.key];
|
|
283
|
-
if (fieldValue !== void 0 && fieldValue !== null) {
|
|
284
|
-
value[field.key] = fieldValue.trim();
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
return value;
|
|
289
|
-
}
|
|
290
85
|
function showAuthError(error) {
|
|
291
86
|
if (isAPIClientError(error)) {
|
|
292
87
|
if (error.code === "LAST_OWNER_PROTECTED") {
|
|
@@ -315,282 +110,6 @@ function showAuthError(error) {
|
|
|
315
110
|
}
|
|
316
111
|
showApiErrorNotification(error);
|
|
317
112
|
}
|
|
318
|
-
function CredentialList({ credentials, isLoading, onEdit }) {
|
|
319
|
-
const [deleteCredential, setDeleteCredential] = useState(null);
|
|
320
|
-
const deleteMutation = useDeleteCredential();
|
|
321
|
-
const handleDelete = () => {
|
|
322
|
-
if (deleteCredential) {
|
|
323
|
-
deleteMutation.mutate(deleteCredential.id);
|
|
324
|
-
setDeleteCredential(null);
|
|
325
|
-
}
|
|
326
|
-
};
|
|
327
|
-
const handleCloseDelete = () => {
|
|
328
|
-
if (!deleteMutation.isPending) {
|
|
329
|
-
setDeleteCredential(null);
|
|
330
|
-
}
|
|
331
|
-
};
|
|
332
|
-
if (isLoading) {
|
|
333
|
-
return /* @__PURE__ */ jsx(ListSkeleton, { rows: 3, rowHeight: 60 });
|
|
334
|
-
}
|
|
335
|
-
if (credentials.length === 0) {
|
|
336
|
-
return /* @__PURE__ */ jsx(
|
|
337
|
-
EmptyState,
|
|
338
|
-
{
|
|
339
|
-
icon: IconKey,
|
|
340
|
-
title: "No credentials yet",
|
|
341
|
-
description: "Create your first credential to enable external service integrations"
|
|
342
|
-
}
|
|
343
|
-
);
|
|
344
|
-
}
|
|
345
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
346
|
-
/* @__PURE__ */ jsxs(Table, { children: [
|
|
347
|
-
/* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
|
|
348
|
-
/* @__PURE__ */ jsx(Table.Th, { children: "Name" }),
|
|
349
|
-
/* @__PURE__ */ jsx(Table.Th, { children: "Created" }),
|
|
350
|
-
/* @__PURE__ */ jsx(Table.Th, { w: 100 })
|
|
351
|
-
] }) }),
|
|
352
|
-
/* @__PURE__ */ jsx(Table.Tbody, { children: credentials.map((credential) => /* @__PURE__ */ jsxs(Table.Tr, { children: [
|
|
353
|
-
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
|
|
354
|
-
/* @__PURE__ */ jsx(IconKey, { size: 16, style: { opacity: 0.6 } }),
|
|
355
|
-
/* @__PURE__ */ jsx(Text, { fw: 500, children: credential.name })
|
|
356
|
-
] }) }),
|
|
357
|
-
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: 4, children: [
|
|
358
|
-
/* @__PURE__ */ jsx(IconCalendar, { size: 14, style: { opacity: 0.5 } }),
|
|
359
|
-
/* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: formatDateTime(credential.createdAt) })
|
|
360
|
-
] }) }),
|
|
361
|
-
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: "xs", justify: "flex-end", children: [
|
|
362
|
-
onEdit && /* @__PURE__ */ jsx(Tooltip, { label: "Edit credential", children: /* @__PURE__ */ jsx(
|
|
363
|
-
ActionIcon,
|
|
364
|
-
{
|
|
365
|
-
variant: "subtle",
|
|
366
|
-
color: "gray",
|
|
367
|
-
onClick: () => onEdit(credential),
|
|
368
|
-
disabled: deleteMutation.isPending,
|
|
369
|
-
children: /* @__PURE__ */ jsx(IconPencil, { size: 16 })
|
|
370
|
-
}
|
|
371
|
-
) }),
|
|
372
|
-
/* @__PURE__ */ jsx(Tooltip, { label: "Delete credential", position: "left", children: /* @__PURE__ */ jsx(
|
|
373
|
-
ActionIcon,
|
|
374
|
-
{
|
|
375
|
-
variant: "subtle",
|
|
376
|
-
color: "red",
|
|
377
|
-
onClick: () => setDeleteCredential(credential),
|
|
378
|
-
disabled: deleteMutation.isPending,
|
|
379
|
-
children: /* @__PURE__ */ jsx(IconTrash, { size: 16 })
|
|
380
|
-
}
|
|
381
|
-
) })
|
|
382
|
-
] }) })
|
|
383
|
-
] }, credential.id)) })
|
|
384
|
-
] }),
|
|
385
|
-
/* @__PURE__ */ jsx(CustomModal, { opened: !!deleteCredential, onClose: handleCloseDelete, size: "md", loading: deleteMutation.isPending, children: /* @__PURE__ */ jsxs(Stack, { children: [
|
|
386
|
-
/* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
|
|
387
|
-
/* @__PURE__ */ jsx(IconAlertTriangle, { size: 24, color: "var(--color-error)" }),
|
|
388
|
-
/* @__PURE__ */ jsx(Title, { order: 3, children: "Delete Credential" })
|
|
389
|
-
] }),
|
|
390
|
-
/* @__PURE__ */ jsxs(Text, { size: "sm", children: [
|
|
391
|
-
"Are you sure you want to delete",
|
|
392
|
-
" ",
|
|
393
|
-
/* @__PURE__ */ jsxs(Text, { span: true, fw: 600, children: [
|
|
394
|
-
'"',
|
|
395
|
-
deleteCredential?.name,
|
|
396
|
-
'"'
|
|
397
|
-
] }),
|
|
398
|
-
"?"
|
|
399
|
-
] }),
|
|
400
|
-
/* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "This action cannot be undone. Any agents or tools using this credential will stop working immediately." }),
|
|
401
|
-
/* @__PURE__ */ jsxs(Group, { justify: "space-between", gap: "sm", children: [
|
|
402
|
-
/* @__PURE__ */ jsx(Button, { variant: "light", onClick: handleCloseDelete, disabled: deleteMutation.isPending, children: "Cancel" }),
|
|
403
|
-
/* @__PURE__ */ jsx(
|
|
404
|
-
Button,
|
|
405
|
-
{
|
|
406
|
-
color: "red",
|
|
407
|
-
onClick: handleDelete,
|
|
408
|
-
loading: deleteMutation.isPending,
|
|
409
|
-
leftSection: /* @__PURE__ */ jsx(IconTrash, { size: 16 }),
|
|
410
|
-
children: "Delete Credential"
|
|
411
|
-
}
|
|
412
|
-
)
|
|
413
|
-
] })
|
|
414
|
-
] }) })
|
|
415
|
-
] });
|
|
416
|
-
}
|
|
417
|
-
function CreateCredentialModal({ opened, onClose, onSuccess }) {
|
|
418
|
-
const createCredential = useCreateCredential();
|
|
419
|
-
const [selectedType, setSelectedType] = useState("api-key");
|
|
420
|
-
const [schema, setSchema] = useState(getCredentialSchema("api-key"));
|
|
421
|
-
useEffect(() => {
|
|
422
|
-
setSchema(getCredentialSchema(selectedType));
|
|
423
|
-
}, [selectedType]);
|
|
424
|
-
const getInitialValues = (currentSchema) => {
|
|
425
|
-
const values = { name: "" };
|
|
426
|
-
if (currentSchema.fields) {
|
|
427
|
-
currentSchema.fields.forEach((field) => {
|
|
428
|
-
values[field.key] = "";
|
|
429
|
-
});
|
|
430
|
-
}
|
|
431
|
-
return values;
|
|
432
|
-
};
|
|
433
|
-
const getValidation = (currentSchema) => {
|
|
434
|
-
const validation = {
|
|
435
|
-
name: (value) => {
|
|
436
|
-
const result = CredentialSchemas.CreateRequest.shape.name.safeParse(value);
|
|
437
|
-
return result.success ? null : result.error.issues[0]?.message || "Invalid credential name";
|
|
438
|
-
}
|
|
439
|
-
};
|
|
440
|
-
if (currentSchema.fields) {
|
|
441
|
-
currentSchema.fields.forEach((field) => {
|
|
442
|
-
if (field.required) {
|
|
443
|
-
validation[field.key] = (value) => !value.trim() ? `${field.label} is required` : null;
|
|
444
|
-
}
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
return validation;
|
|
448
|
-
};
|
|
449
|
-
const form = useForm({
|
|
450
|
-
initialValues: getInitialValues(schema),
|
|
451
|
-
validate: getValidation(schema)
|
|
452
|
-
});
|
|
453
|
-
useEffect(() => {
|
|
454
|
-
const newValues = getInitialValues(schema);
|
|
455
|
-
form.setValues(newValues);
|
|
456
|
-
form.setFieldValue("name", form.values.name || "");
|
|
457
|
-
form.resetDirty();
|
|
458
|
-
}, [selectedType]);
|
|
459
|
-
const handleSubmit = async (values) => {
|
|
460
|
-
if (schema.type === "oauth") {
|
|
461
|
-
console.error("OAuth credentials cannot be created via this modal");
|
|
462
|
-
return;
|
|
463
|
-
}
|
|
464
|
-
const data = {
|
|
465
|
-
name: values.name.trim(),
|
|
466
|
-
type: selectedType,
|
|
467
|
-
value: buildCredentialValue(selectedType, values)
|
|
468
|
-
};
|
|
469
|
-
const validationResult = CredentialSchemas.CreateRequest.safeParse(data);
|
|
470
|
-
if (!validationResult.success) {
|
|
471
|
-
console.error("Validation failed:", validationResult.error);
|
|
472
|
-
return;
|
|
473
|
-
}
|
|
474
|
-
try {
|
|
475
|
-
const result = await createCredential.mutateAsync(data);
|
|
476
|
-
form.reset();
|
|
477
|
-
setSelectedType("api-key");
|
|
478
|
-
onSuccess(result);
|
|
479
|
-
} catch (error) {
|
|
480
|
-
console.error("Failed to create credential:", error);
|
|
481
|
-
}
|
|
482
|
-
};
|
|
483
|
-
const handleClose = () => {
|
|
484
|
-
if (!createCredential.isPending) {
|
|
485
|
-
form.reset();
|
|
486
|
-
setSelectedType("api-key");
|
|
487
|
-
onClose();
|
|
488
|
-
}
|
|
489
|
-
};
|
|
490
|
-
return /* @__PURE__ */ jsx(CustomModal, { opened, onClose: handleClose, size: "lg", loading: createCredential.isPending, children: /* @__PURE__ */ jsx("form", { onSubmit: form.onSubmit(handleSubmit), children: /* @__PURE__ */ jsxs(Stack, { children: [
|
|
491
|
-
/* @__PURE__ */ jsx(Title, { order: 3, children: "Create New Credential" }),
|
|
492
|
-
/* @__PURE__ */ jsx(
|
|
493
|
-
Select,
|
|
494
|
-
{
|
|
495
|
-
label: "Credential Type",
|
|
496
|
-
description: "Select the integration type",
|
|
497
|
-
placeholder: "Select credential type",
|
|
498
|
-
data: Object.entries(CREDENTIAL_SCHEMAS).filter(([_, s]) => s.type !== "oauth").map(([key, s]) => ({ value: key, label: s.label })),
|
|
499
|
-
value: selectedType,
|
|
500
|
-
onChange: (value) => setSelectedType(value || "api-key"),
|
|
501
|
-
required: true,
|
|
502
|
-
disabled: createCredential.isPending
|
|
503
|
-
}
|
|
504
|
-
),
|
|
505
|
-
/* @__PURE__ */ jsx(
|
|
506
|
-
TextInput,
|
|
507
|
-
{
|
|
508
|
-
label: "Credential Name",
|
|
509
|
-
description: schema.nameSuggestions.length > 0 ? `Suggested: ${schema.nameSuggestions.join(", ")}` : "Lowercase letters, numbers, and hyphens only (e.g., service-dev, service-prod)",
|
|
510
|
-
placeholder: schema.nameSuggestions[0] || "e.g., service-prod",
|
|
511
|
-
leftSection: /* @__PURE__ */ jsx(IconKey, { size: 16 }),
|
|
512
|
-
maxLength: 100,
|
|
513
|
-
required: true,
|
|
514
|
-
...form.getInputProps("name"),
|
|
515
|
-
disabled: createCredential.isPending
|
|
516
|
-
}
|
|
517
|
-
),
|
|
518
|
-
schema.type === "oauth" ? /* @__PURE__ */ jsxs(Alert, { icon: /* @__PURE__ */ jsx(IconInfoCircle, { size: 16 }), color: "blue", children: [
|
|
519
|
-
schema.label,
|
|
520
|
-
" uses OAuth authentication. Please go to the OAuth Integrations page to connect your",
|
|
521
|
-
" ",
|
|
522
|
-
schema.label,
|
|
523
|
-
" account."
|
|
524
|
-
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
525
|
-
schema.fields?.map((field) => {
|
|
526
|
-
const Input = field.type === "text" ? TextInput : PasswordInput;
|
|
527
|
-
return /* @__PURE__ */ jsx(
|
|
528
|
-
Input,
|
|
529
|
-
{
|
|
530
|
-
label: field.label,
|
|
531
|
-
description: field.description,
|
|
532
|
-
placeholder: field.placeholder,
|
|
533
|
-
required: field.required,
|
|
534
|
-
value: form.values[field.key] || "",
|
|
535
|
-
onChange: (e) => form.setFieldValue(field.key, e.target.value),
|
|
536
|
-
error: form.errors[field.key],
|
|
537
|
-
disabled: createCredential.isPending
|
|
538
|
-
},
|
|
539
|
-
field.key
|
|
540
|
-
);
|
|
541
|
-
}),
|
|
542
|
-
schema.docsUrl && /* @__PURE__ */ jsxs(Text, { size: "sm", c: "dimmed", children: [
|
|
543
|
-
"Get credentials from",
|
|
544
|
-
" ",
|
|
545
|
-
/* @__PURE__ */ jsx(Anchor, { href: schema.docsUrl, target: "_blank", rel: "noopener noreferrer", children: schema.docsUrl })
|
|
546
|
-
] })
|
|
547
|
-
] }),
|
|
548
|
-
createCredential.error && /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconExclamationMark, { size: 16 }), color: "red", children: createCredential.error instanceof Error ? createCredential.error.message : "An error occurred while creating the credential" }),
|
|
549
|
-
/* @__PURE__ */ jsxs(Group, { justify: "flex-end", gap: "sm", children: [
|
|
550
|
-
/* @__PURE__ */ jsx(Button, { variant: "subtle", onClick: handleClose, disabled: createCredential.isPending, children: "Cancel" }),
|
|
551
|
-
/* @__PURE__ */ jsx(Button, { type: "submit", loading: createCredential.isPending, leftSection: /* @__PURE__ */ jsx(IconKey, { size: 16 }), children: "Create Credential" })
|
|
552
|
-
] })
|
|
553
|
-
] }) }) });
|
|
554
|
-
}
|
|
555
|
-
function CredentialSettings({ oauthSection }) {
|
|
556
|
-
const { organizationReady } = useInitialization();
|
|
557
|
-
const [showCreateModal, setShowCreateModal] = useState(false);
|
|
558
|
-
const { data: credentials = [], isLoading } = useCredentials();
|
|
559
|
-
if (!organizationReady) return /* @__PURE__ */ jsx(AppShellLoader, {});
|
|
560
|
-
const handleCredentialCreated = () => {
|
|
561
|
-
setShowCreateModal(false);
|
|
562
|
-
};
|
|
563
|
-
return /* @__PURE__ */ jsxs(Stack, { children: [
|
|
564
|
-
/* @__PURE__ */ jsx(
|
|
565
|
-
PageTitleCaption,
|
|
566
|
-
{
|
|
567
|
-
title: "Organization Credentials",
|
|
568
|
-
caption: "Manage encrypted credentials for third-party service integrations"
|
|
569
|
-
}
|
|
570
|
-
),
|
|
571
|
-
oauthSection,
|
|
572
|
-
/* @__PURE__ */ jsxs(Paper, { withBorder: true, children: [
|
|
573
|
-
/* @__PURE__ */ jsx(
|
|
574
|
-
CardHeader,
|
|
575
|
-
{
|
|
576
|
-
icon: /* @__PURE__ */ jsx(IconKey, { size: 18 }),
|
|
577
|
-
title: "Credentials",
|
|
578
|
-
subtitle: "Configured API keys and credentials",
|
|
579
|
-
rightSection: /* @__PURE__ */ jsx(Button, { leftSection: /* @__PURE__ */ jsx(IconPlus, { size: 16 }), onClick: () => setShowCreateModal(true), variant: "light", children: "Create Credential" })
|
|
580
|
-
}
|
|
581
|
-
),
|
|
582
|
-
/* @__PURE__ */ jsx(CredentialList, { credentials, isLoading })
|
|
583
|
-
] }),
|
|
584
|
-
/* @__PURE__ */ jsx(
|
|
585
|
-
CreateCredentialModal,
|
|
586
|
-
{
|
|
587
|
-
opened: showCreateModal,
|
|
588
|
-
onClose: () => setShowCreateModal(false),
|
|
589
|
-
onSuccess: handleCredentialCreated
|
|
590
|
-
}
|
|
591
|
-
)
|
|
592
|
-
] });
|
|
593
|
-
}
|
|
594
113
|
function OAuthConnectModal({ opened, onClose, provider, onConnect, error, isLoading }) {
|
|
595
114
|
const [credentialName, setCredentialName] = useState("");
|
|
596
115
|
useEffect(() => {
|
|
@@ -648,80 +167,291 @@ function OAuthConnectModal({ opened, onClose, provider, onConnect, error, isLoad
|
|
|
648
167
|
] })
|
|
649
168
|
] }) });
|
|
650
169
|
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
170
|
+
var OAUTH_COMPLETION_CHANNEL = "elevasis-oauth";
|
|
171
|
+
var OAUTH_COMPLETION_STORAGE_KEY = "elevasis:oauth-complete";
|
|
172
|
+
function useOAuthFlow({ apiUrl: apiUrlOverride } = {}) {
|
|
173
|
+
const [isAuthorizing, setIsAuthorizing] = useState(false);
|
|
174
|
+
const [error, setError] = useState(null);
|
|
175
|
+
const { isReady, apiUrl: contextApiUrl } = useElevasisServices();
|
|
176
|
+
const { getAccessToken, organizationId } = useAuthContext();
|
|
177
|
+
const apiUrl = apiUrlOverride ?? contextApiUrl;
|
|
178
|
+
const authorize = useCallback(
|
|
179
|
+
async (provider, credentialName) => {
|
|
180
|
+
if (!isReady || !organizationId) {
|
|
181
|
+
setError("Organization context not ready");
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
setIsAuthorizing(true);
|
|
185
|
+
setError(null);
|
|
186
|
+
const width = 600;
|
|
187
|
+
const height = 700;
|
|
188
|
+
const left = Math.round(window.screen.width / 2 - width / 2);
|
|
189
|
+
const top = Math.round(window.screen.height / 2 - height / 2);
|
|
190
|
+
const popup = window.open(
|
|
191
|
+
"about:blank",
|
|
192
|
+
`oauth-${provider}`,
|
|
193
|
+
`width=${width},height=${height},left=${left},top=${top}`
|
|
194
|
+
);
|
|
195
|
+
if (!popup) {
|
|
196
|
+
setIsAuthorizing(false);
|
|
197
|
+
setError("Please enable popups and try again");
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
let token;
|
|
201
|
+
try {
|
|
202
|
+
token = await getAccessToken();
|
|
203
|
+
} catch {
|
|
204
|
+
popup.close();
|
|
205
|
+
setIsAuthorizing(false);
|
|
206
|
+
setError("Authorization failed. Please sign in again.");
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
const authorizeUrl = new URL(`${apiUrl}/api/oauth/authorize/${provider}`);
|
|
211
|
+
authorizeUrl.searchParams.set("credentialName", credentialName);
|
|
212
|
+
authorizeUrl.searchParams.set("token", token);
|
|
213
|
+
authorizeUrl.searchParams.set("orgId", organizationId);
|
|
214
|
+
authorizeUrl.searchParams.set("appOrigin", window.location.origin);
|
|
215
|
+
popup.location.href = authorizeUrl.toString();
|
|
216
|
+
await waitForOAuthCallback(popup, Date.now());
|
|
217
|
+
} catch (err) {
|
|
218
|
+
const rawError = err instanceof Error ? err.message : "unknown";
|
|
219
|
+
if (rawError.includes("popup_blocked")) {
|
|
220
|
+
setError("Please enable popups and try again");
|
|
221
|
+
} else if (rawError.includes("timeout")) {
|
|
222
|
+
setError("Authorization timed out. Please try again.");
|
|
223
|
+
} else if (rawError.includes("popup_closed")) {
|
|
224
|
+
setError("Authorization was cancelled.");
|
|
225
|
+
} else if (rawError.includes("token_exchange_failed")) {
|
|
226
|
+
setError("Failed to obtain access token. Please try again.");
|
|
227
|
+
} else if (rawError.includes("invalid_state")) {
|
|
228
|
+
setError("Security validation failed. Please try again.");
|
|
229
|
+
} else if (rawError.includes("state_expired")) {
|
|
230
|
+
setError("Authorization expired. Please try again.");
|
|
231
|
+
} else {
|
|
232
|
+
setError(`Failed to connect: ${rawError}`);
|
|
233
|
+
}
|
|
234
|
+
throw err;
|
|
235
|
+
} finally {
|
|
236
|
+
setIsAuthorizing(false);
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
[isReady, organizationId, apiUrl, getAccessToken]
|
|
240
|
+
);
|
|
241
|
+
const waitForOAuthCallback = (popup, startedAt) => {
|
|
242
|
+
return new Promise((resolve, reject) => {
|
|
243
|
+
let broadcastChannel = null;
|
|
244
|
+
const complete = (data) => {
|
|
245
|
+
const payload = parseOAuthCompletionPayload(data, startedAt);
|
|
246
|
+
if (!payload) return;
|
|
247
|
+
cleanup();
|
|
248
|
+
if (payload.status === "success") {
|
|
249
|
+
resolve();
|
|
250
|
+
} else {
|
|
251
|
+
reject(new Error(payload.error || "oauth_failed"));
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
const messageHandler = (event) => {
|
|
255
|
+
if (!isTrustedCallbackOrigin(event.origin)) return;
|
|
256
|
+
complete(event.data);
|
|
257
|
+
};
|
|
258
|
+
const storageHandler = (event) => {
|
|
259
|
+
if (event.key !== OAUTH_COMPLETION_STORAGE_KEY || !event.newValue) return;
|
|
260
|
+
try {
|
|
261
|
+
complete(JSON.parse(event.newValue));
|
|
262
|
+
} catch {
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
const rejectIfPopupClosed = () => {
|
|
266
|
+
if (popup.closed) {
|
|
267
|
+
cleanup();
|
|
268
|
+
reject(new Error("popup_closed"));
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
window.addEventListener("message", messageHandler);
|
|
272
|
+
window.addEventListener("storage", storageHandler);
|
|
273
|
+
window.addEventListener("focus", rejectIfPopupClosed);
|
|
274
|
+
if ("BroadcastChannel" in window) {
|
|
275
|
+
broadcastChannel = new BroadcastChannel(OAUTH_COMPLETION_CHANNEL);
|
|
276
|
+
broadcastChannel.onmessage = (event) => complete(event.data);
|
|
277
|
+
}
|
|
278
|
+
const timeout = setTimeout(() => {
|
|
279
|
+
cleanup();
|
|
280
|
+
reject(new Error("timeout"));
|
|
281
|
+
}, OAUTH_FLOW_TIMEOUT);
|
|
282
|
+
const cleanup = () => {
|
|
283
|
+
clearTimeout(timeout);
|
|
284
|
+
broadcastChannel?.close();
|
|
285
|
+
window.removeEventListener("focus", rejectIfPopupClosed);
|
|
286
|
+
window.removeEventListener("storage", storageHandler);
|
|
287
|
+
window.removeEventListener("message", messageHandler);
|
|
288
|
+
};
|
|
289
|
+
});
|
|
290
|
+
};
|
|
291
|
+
return { authorize, isAuthorizing, error };
|
|
292
|
+
}
|
|
293
|
+
function parseOAuthCompletionPayload(data, startedAt) {
|
|
294
|
+
if (!data || typeof data !== "object") return null;
|
|
295
|
+
const payload = data;
|
|
296
|
+
if (payload.type !== "oauth-complete") return null;
|
|
297
|
+
if (payload.completedAt && payload.completedAt < startedAt) return null;
|
|
298
|
+
return payload;
|
|
299
|
+
}
|
|
300
|
+
function isTrustedCallbackOrigin(origin) {
|
|
301
|
+
if (origin === window.location.origin || origin === "https://app.elevasis.io") {
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
try {
|
|
305
|
+
const url = new URL(origin);
|
|
306
|
+
return url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
|
|
307
|
+
} catch {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
var PROVIDER_ICONS = {
|
|
312
|
+
"google-sheets": IconBrandGoogleDrive,
|
|
313
|
+
dropbox: IconBrandDropbox
|
|
314
|
+
};
|
|
315
|
+
function OAuthIntegrationsCard({ apiUrl } = {}) {
|
|
316
|
+
const [selectedProviderId, setSelectedProviderId] = useState(null);
|
|
317
|
+
const [modalOpened, setModalOpened] = useState(false);
|
|
318
|
+
const { authorize, isAuthorizing, error } = useOAuthFlow({ apiUrl });
|
|
319
|
+
const queryClient = useQueryClient();
|
|
320
|
+
const handleConnect = async (credentialName) => {
|
|
321
|
+
if (!selectedProviderId) return;
|
|
322
|
+
try {
|
|
323
|
+
await authorize(selectedProviderId, credentialName);
|
|
324
|
+
setModalOpened(false);
|
|
325
|
+
setSelectedProviderId(null);
|
|
326
|
+
queryClient.invalidateQueries({ queryKey: ["credentials"] });
|
|
327
|
+
} catch (err) {
|
|
328
|
+
console.error("OAuth flow failed:", err);
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
const handleOpenModal = (providerId) => {
|
|
332
|
+
setSelectedProviderId(providerId);
|
|
333
|
+
setModalOpened(true);
|
|
334
|
+
};
|
|
335
|
+
useEffect(() => {
|
|
336
|
+
const params = new URLSearchParams(window.location.search);
|
|
337
|
+
const requested = params.get("connect");
|
|
338
|
+
if (requested && OAUTH_PROVIDERS[requested]) {
|
|
339
|
+
handleOpenModal(requested);
|
|
340
|
+
params.delete("connect");
|
|
341
|
+
const remaining = params.toString();
|
|
342
|
+
const newUrl = window.location.pathname + (remaining ? `?${remaining}` : "") + window.location.hash;
|
|
343
|
+
window.history.replaceState({}, "", newUrl);
|
|
344
|
+
}
|
|
345
|
+
}, []);
|
|
346
|
+
const providers = Object.values(OAUTH_PROVIDERS);
|
|
347
|
+
const selectedProvider = selectedProviderId ? OAUTH_PROVIDERS[selectedProviderId] : null;
|
|
348
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
349
|
+
/* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { children: [
|
|
350
|
+
/* @__PURE__ */ jsx(Group, { justify: "space-between", children: /* @__PURE__ */ jsxs("div", { children: [
|
|
351
|
+
/* @__PURE__ */ jsx(Title, { order: 3, children: "OAuth Integrations" }),
|
|
352
|
+
/* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", mt: 4, children: "Connect third-party services using OAuth 2.0" })
|
|
353
|
+
] }) }),
|
|
354
|
+
/* @__PURE__ */ jsx(SimpleGrid, { cols: { base: 1, sm: 2, md: 3 }, children: providers.map((provider) => {
|
|
355
|
+
const Icon = PROVIDER_ICONS[provider.id] || IconPlug;
|
|
356
|
+
return /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { children: [
|
|
357
|
+
/* @__PURE__ */ jsx(Group, { justify: "space-between", align: "flex-start", children: /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
|
|
358
|
+
/* @__PURE__ */ jsx(ThemeIcon, { size: "lg", variant: "light", children: /* @__PURE__ */ jsx(Icon, { size: 20 }) }),
|
|
359
|
+
/* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Text, { fw: 600, style: { fontFamily: "var(--mantine-font-family-headings)" }, children: provider.name }) })
|
|
360
|
+
] }) }),
|
|
361
|
+
/* @__PURE__ */ jsx(Button, { variant: "filled", size: "sm", onClick: () => handleOpenModal(provider.id), fullWidth: true, children: "Connect" })
|
|
362
|
+
] }) }, provider.id);
|
|
363
|
+
}) })
|
|
364
|
+
] }) }),
|
|
365
|
+
/* @__PURE__ */ jsx(
|
|
366
|
+
OAuthConnectModal,
|
|
367
|
+
{
|
|
368
|
+
opened: modalOpened,
|
|
369
|
+
onClose: () => {
|
|
370
|
+
setModalOpened(false);
|
|
371
|
+
setSelectedProviderId(null);
|
|
372
|
+
},
|
|
373
|
+
provider: selectedProvider,
|
|
374
|
+
onConnect: handleConnect,
|
|
375
|
+
error,
|
|
376
|
+
isLoading: isAuthorizing
|
|
377
|
+
}
|
|
378
|
+
)
|
|
379
|
+
] });
|
|
380
|
+
}
|
|
381
|
+
function MembershipStatusBadge({ status, size = "sm", variant = "light" }) {
|
|
382
|
+
const getStatusLabel = (status2) => {
|
|
383
|
+
switch (status2) {
|
|
384
|
+
case "active":
|
|
385
|
+
return "Active";
|
|
386
|
+
case "inactive":
|
|
387
|
+
return "Inactive";
|
|
388
|
+
default:
|
|
389
|
+
return status2;
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
return /* @__PURE__ */ jsx(Badge, { color: MEMBERSHIP_STATUS_COLORS[status], size, variant, children: getStatusLabel(status) });
|
|
393
|
+
}
|
|
394
|
+
function ProvisioningStateBadge({ state, size = "sm", variant = "light" }) {
|
|
395
|
+
if (!state || state === "linked") {
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
const display = PROVISIONING_STATE_DISPLAY[state];
|
|
399
|
+
return /* @__PURE__ */ jsx(Tooltip, { label: display.description, multiline: true, w: 280, children: /* @__PURE__ */ jsx(Badge, { color: display.color, size, variant, children: display.label }) });
|
|
400
|
+
}
|
|
401
|
+
function OrganizationMembershipsList({
|
|
402
|
+
memberships,
|
|
403
|
+
loading,
|
|
404
|
+
error,
|
|
405
|
+
onEditRole,
|
|
406
|
+
onLeaveOrganization
|
|
407
|
+
}) {
|
|
408
|
+
const [searchTerm, setSearchTerm] = useState("");
|
|
409
|
+
const [statusFilter, setStatusFilter] = useState("");
|
|
410
|
+
const tableRows = memberships?.map(transformMembershipToTableRow) || [];
|
|
411
|
+
const filteredRows = tableRows.filter((row) => {
|
|
412
|
+
const matchesSearch = row.organizationName.toLowerCase().includes(searchTerm.toLowerCase()) || row.role.toLowerCase().includes(searchTerm.toLowerCase());
|
|
413
|
+
const matchesStatus = !statusFilter || row.status === statusFilter;
|
|
414
|
+
return matchesSearch && matchesStatus;
|
|
415
|
+
});
|
|
416
|
+
const renderActions = (row) => {
|
|
417
|
+
if (!onEditRole && !onLeaveOrganization) {
|
|
418
|
+
return /* @__PURE__ */ jsx(ActionIcon, { variant: "light", size: "sm", color: "gray", title: "Configure member access", children: /* @__PURE__ */ jsx(IconSettings, { size: 14 }) });
|
|
419
|
+
}
|
|
420
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
421
|
+
row.canEdit && onEditRole && /* @__PURE__ */ jsx(
|
|
422
|
+
ActionIcon,
|
|
423
|
+
{
|
|
424
|
+
variant: "subtle",
|
|
425
|
+
size: "sm",
|
|
426
|
+
color: "blue",
|
|
427
|
+
onClick: () => onEditRole(row.id),
|
|
428
|
+
disabled: row.status !== "active",
|
|
429
|
+
children: /* @__PURE__ */ jsx(IconEdit, { size: 16 })
|
|
430
|
+
}
|
|
431
|
+
),
|
|
432
|
+
row.canRemove && onLeaveOrganization && row.status === "active" && /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", size: "sm", color: "red", onClick: () => onLeaveOrganization(row.id), children: /* @__PURE__ */ jsx(IconUserX, { size: 16 }) }),
|
|
433
|
+
row.status === "inactive" && /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", size: "sm", color: "green", disabled: true, title: "Contact admin to reactivate", children: /* @__PURE__ */ jsx(IconUserCheck, { size: 16 }) })
|
|
434
|
+
] });
|
|
435
|
+
};
|
|
436
|
+
if (error) {
|
|
437
|
+
return /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(APIErrorAlert, { error, title: "Failed to load memberships" }) });
|
|
438
|
+
}
|
|
439
|
+
return /* @__PURE__ */ jsxs(Paper, { withBorder: true, children: [
|
|
440
|
+
/* @__PURE__ */ jsx(
|
|
441
|
+
CardHeader,
|
|
442
|
+
{
|
|
443
|
+
icon: /* @__PURE__ */ jsx(IconUsers, { size: 18 }),
|
|
444
|
+
title: `Your Organizations (${loading ? "..." : filteredRows.length})`,
|
|
445
|
+
subtitle: loading ? "Loading memberships..." : `Showing ${filteredRows.length} ${filteredRows.length === 1 ? "membership" : "memberships"}`
|
|
446
|
+
}
|
|
447
|
+
),
|
|
448
|
+
/* @__PURE__ */ jsxs(Stack, { children: [
|
|
449
|
+
/* @__PURE__ */ jsxs(FilterBar, { children: [
|
|
450
|
+
/* @__PURE__ */ jsx(
|
|
451
|
+
TextInput,
|
|
452
|
+
{
|
|
453
|
+
placeholder: "Search organizations or roles...",
|
|
454
|
+
leftSection: /* @__PURE__ */ jsx(IconSearch, { size: 16 }),
|
|
725
455
|
value: searchTerm,
|
|
726
456
|
onChange: (event) => setSearchTerm(event.currentTarget.value),
|
|
727
457
|
style: { flex: 1 },
|
|
@@ -1109,264 +839,34 @@ function AppearanceSettings({ initialTheme, onThemeChange }) {
|
|
|
1109
839
|
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: row.last }) })
|
|
1110
840
|
] }, row.name)) })
|
|
1111
841
|
] }) })
|
|
1112
|
-
] }) }),
|
|
1113
|
-
/* @__PURE__ */ jsx(Divider, {}),
|
|
1114
|
-
/* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
|
|
1115
|
-
/* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 600, children: "Alerts" }),
|
|
1116
|
-
/* @__PURE__ */ jsx(Alert, { variant: "light", color: "blue", icon: /* @__PURE__ */ jsx(IconAlertCircle, { size: 16 }), title: "Informational", children: "This is how informational alerts look with your current theme." }),
|
|
1117
|
-
/* @__PURE__ */ jsx(Alert, { variant: "light", color: "green", icon: /* @__PURE__ */ jsx(IconCheck, { size: 16 }), title: "Success", children: "Operation completed successfully." })
|
|
1118
|
-
] })
|
|
1119
|
-
] })
|
|
1120
|
-
] }),
|
|
1121
|
-
/* @__PURE__ */ jsx(CustomModal, { opened: modalOpen, onClose: () => setModalOpen(false), size: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", p: "md", children: [
|
|
1122
|
-
/* @__PURE__ */ jsx(Title, { order: 3, children: "Modal Preview" }),
|
|
1123
|
-
/* @__PURE__ */ jsxs(Text, { size: "sm", children: [
|
|
1124
|
-
"Modals should sit visibly above the page background. Elevation comes from the",
|
|
1125
|
-
" ",
|
|
1126
|
-
/* @__PURE__ */ jsx(Text, { span: true, ff: "monospace", size: "sm", children: "--color-elevated" }),
|
|
1127
|
-
" ",
|
|
1128
|
-
"token, which every preset defines as an opaque surface."
|
|
1129
|
-
] }),
|
|
1130
|
-
/* @__PURE__ */ jsx(TextInput, { label: "Example field", placeholder: "Type something...", size: "xs" }),
|
|
1131
|
-
/* @__PURE__ */ jsxs(Group, { justify: "flex-end", children: [
|
|
1132
|
-
/* @__PURE__ */ jsx(Button, { variant: "default", size: "xs", onClick: () => setModalOpen(false), children: "Cancel" }),
|
|
1133
|
-
/* @__PURE__ */ jsx(Button, { size: "xs", onClick: () => setModalOpen(false), children: "Confirm" })
|
|
1134
|
-
] })
|
|
1135
|
-
] }) })
|
|
1136
|
-
] });
|
|
1137
|
-
}
|
|
1138
|
-
function EditWebhookEndpointModal({ opened, endpoint, onClose }) {
|
|
1139
|
-
const updateEndpoint = useUpdateWebhookEndpoint();
|
|
1140
|
-
const { data: resourcesData } = useResources();
|
|
1141
|
-
const resourceOptions = useMemo(() => {
|
|
1142
|
-
if (!resourcesData) return [];
|
|
1143
|
-
const workflowOptions = (resourcesData.workflows || []).filter((w) => w?.resourceId).map((w) => ({
|
|
1144
|
-
value: w.resourceId,
|
|
1145
|
-
label: `Workflow: ${w.resourceId}`
|
|
1146
|
-
}));
|
|
1147
|
-
const agentOptions = (resourcesData.agents || []).filter((a) => a?.resourceId).map((a) => ({
|
|
1148
|
-
value: a.resourceId,
|
|
1149
|
-
label: `Agent: ${a.resourceId}`
|
|
1150
|
-
}));
|
|
1151
|
-
return [...workflowOptions, ...agentOptions];
|
|
1152
|
-
}, [resourcesData]);
|
|
1153
|
-
const form = useForm({
|
|
1154
|
-
initialValues: {
|
|
1155
|
-
name: "",
|
|
1156
|
-
resourceId: ""
|
|
1157
|
-
},
|
|
1158
|
-
validate: {
|
|
1159
|
-
name: (value) => {
|
|
1160
|
-
if (!value.trim()) return "Name is required";
|
|
1161
|
-
if (value.trim().length < 2) return "Name must be at least 2 characters";
|
|
1162
|
-
if (value.trim().length > 100) return "Name must be less than 100 characters";
|
|
1163
|
-
return null;
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
});
|
|
1167
|
-
useEffect(() => {
|
|
1168
|
-
if (endpoint && opened) {
|
|
1169
|
-
form.setValues({
|
|
1170
|
-
name: endpoint.name,
|
|
1171
|
-
resourceId: endpoint.resourceId ?? ""
|
|
1172
|
-
});
|
|
1173
|
-
}
|
|
1174
|
-
}, [endpoint, opened]);
|
|
1175
|
-
const handleSubmit = async (values) => {
|
|
1176
|
-
if (!endpoint) return;
|
|
1177
|
-
try {
|
|
1178
|
-
await updateEndpoint.mutateAsync({
|
|
1179
|
-
endpointId: endpoint.id,
|
|
1180
|
-
data: {
|
|
1181
|
-
name: values.name.trim(),
|
|
1182
|
-
resourceId: values.resourceId || void 0
|
|
1183
|
-
}
|
|
1184
|
-
});
|
|
1185
|
-
onClose();
|
|
1186
|
-
} catch (error) {
|
|
1187
|
-
console.error("Failed to update webhook endpoint:", error);
|
|
1188
|
-
}
|
|
1189
|
-
};
|
|
1190
|
-
const handleClose = () => {
|
|
1191
|
-
if (!updateEndpoint.isPending) {
|
|
1192
|
-
onClose();
|
|
1193
|
-
}
|
|
1194
|
-
};
|
|
1195
|
-
return /* @__PURE__ */ jsx(CustomModal, { opened, onClose: handleClose, size: "md", loading: updateEndpoint.isPending, children: /* @__PURE__ */ jsx("form", { onSubmit: form.onSubmit(handleSubmit), children: /* @__PURE__ */ jsxs(Stack, { children: [
|
|
1196
|
-
/* @__PURE__ */ jsx(Title, { order: 3, children: "Edit Webhook Endpoint" }),
|
|
1197
|
-
/* @__PURE__ */ jsx(
|
|
1198
|
-
TextInput,
|
|
1199
|
-
{
|
|
1200
|
-
label: "Name",
|
|
1201
|
-
description: "A descriptive label for this endpoint",
|
|
1202
|
-
leftSection: /* @__PURE__ */ jsx(IconWebhook, { size: 16 }),
|
|
1203
|
-
required: true,
|
|
1204
|
-
...form.getInputProps("name"),
|
|
1205
|
-
disabled: updateEndpoint.isPending
|
|
1206
|
-
}
|
|
1207
|
-
),
|
|
1208
|
-
/* @__PURE__ */ jsx(
|
|
1209
|
-
Select,
|
|
1210
|
-
{
|
|
1211
|
-
label: "Target Resource",
|
|
1212
|
-
description: "The workflow or agent to trigger on each inbound request",
|
|
1213
|
-
placeholder: resourcesData ? "Select a workflow or agent" : "Loading resources...",
|
|
1214
|
-
data: resourceOptions,
|
|
1215
|
-
searchable: true,
|
|
1216
|
-
clearable: true,
|
|
1217
|
-
...form.getInputProps("resourceId"),
|
|
1218
|
-
disabled: !resourcesData || updateEndpoint.isPending
|
|
1219
|
-
}
|
|
1220
|
-
),
|
|
1221
|
-
updateEndpoint.error && /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconExclamationMark, { size: 16 }), color: "red", children: updateEndpoint.error instanceof Error ? updateEndpoint.error.message : "An error occurred while updating the webhook endpoint" }),
|
|
1222
|
-
/* @__PURE__ */ jsxs(Group, { justify: "flex-end", gap: "sm", children: [
|
|
1223
|
-
/* @__PURE__ */ jsx(Button, { variant: "subtle", onClick: handleClose, disabled: updateEndpoint.isPending, children: "Cancel" }),
|
|
1224
|
-
/* @__PURE__ */ jsx(Button, { type: "submit", loading: updateEndpoint.isPending, leftSection: /* @__PURE__ */ jsx(IconPencil, { size: 16 }), children: "Save Changes" })
|
|
1225
|
-
] })
|
|
1226
|
-
] }) }) });
|
|
1227
|
-
}
|
|
1228
|
-
function formatKeyDisplay(keyPrefix) {
|
|
1229
|
-
if (!keyPrefix) return "Unavailable";
|
|
1230
|
-
return `${keyPrefix}...`;
|
|
1231
|
-
}
|
|
1232
|
-
function WebhookEndpointList({ endpoints, isLoading }) {
|
|
1233
|
-
const [deleteTarget, setDeleteTarget] = useState(null);
|
|
1234
|
-
const [editTarget, setEditTarget] = useState(null);
|
|
1235
|
-
const deleteMutation = useDeleteWebhookEndpoint();
|
|
1236
|
-
const updateMutation = useUpdateWebhookEndpoint();
|
|
1237
|
-
const handleDelete = () => {
|
|
1238
|
-
if (deleteTarget) {
|
|
1239
|
-
deleteMutation.mutate(deleteTarget.id);
|
|
1240
|
-
setDeleteTarget(null);
|
|
1241
|
-
}
|
|
1242
|
-
};
|
|
1243
|
-
const handleCloseDelete = () => {
|
|
1244
|
-
if (!deleteMutation.isPending) {
|
|
1245
|
-
setDeleteTarget(null);
|
|
1246
|
-
}
|
|
1247
|
-
};
|
|
1248
|
-
const handleToggleStatus = (endpoint) => {
|
|
1249
|
-
const newStatus = endpoint.status === "active" ? "paused" : "active";
|
|
1250
|
-
updateMutation.mutate({ endpointId: endpoint.id, data: { status: newStatus } });
|
|
1251
|
-
};
|
|
1252
|
-
if (isLoading) {
|
|
1253
|
-
return /* @__PURE__ */ jsx(ListSkeleton, { rows: 3, rowHeight: 50 });
|
|
1254
|
-
}
|
|
1255
|
-
if (endpoints.length === 0) {
|
|
1256
|
-
return /* @__PURE__ */ jsx(
|
|
1257
|
-
EmptyState,
|
|
1258
|
-
{
|
|
1259
|
-
icon: IconWebhook,
|
|
1260
|
-
title: "No webhook endpoints yet",
|
|
1261
|
-
description: "Create your first webhook endpoint to let third-party apps trigger your resources"
|
|
1262
|
-
}
|
|
1263
|
-
);
|
|
1264
|
-
}
|
|
1265
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1266
|
-
/* @__PURE__ */ jsxs(Table, { children: [
|
|
1267
|
-
/* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
|
|
1268
|
-
/* @__PURE__ */ jsx(Table.Th, { children: "Name" }),
|
|
1269
|
-
/* @__PURE__ */ jsx(Table.Th, { children: "Status" }),
|
|
1270
|
-
/* @__PURE__ */ jsx(Table.Th, { children: "Target Resource" }),
|
|
1271
|
-
/* @__PURE__ */ jsx(Table.Th, { children: "Key" }),
|
|
1272
|
-
/* @__PURE__ */ jsx(Table.Th, { children: "Last Triggered" }),
|
|
1273
|
-
/* @__PURE__ */ jsx(Table.Th, { children: "Requests" }),
|
|
1274
|
-
/* @__PURE__ */ jsx(Table.Th, { w: 120 })
|
|
1275
|
-
] }) }),
|
|
1276
|
-
/* @__PURE__ */ jsx(Table.Tbody, { children: endpoints.map((endpoint) => {
|
|
1277
|
-
const keyDisplay = formatKeyDisplay(endpoint.keyPrefix);
|
|
1278
|
-
const isToggling = updateMutation.isPending;
|
|
1279
|
-
return /* @__PURE__ */ jsxs(Table.Tr, { children: [
|
|
1280
|
-
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
|
|
1281
|
-
/* @__PURE__ */ jsx(IconWebhook, { size: 16, style: { opacity: 0.6 } }),
|
|
1282
|
-
/* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
|
|
1283
|
-
/* @__PURE__ */ jsx(Text, { fw: 500, children: endpoint.name }),
|
|
1284
|
-
endpoint.description && /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: endpoint.description })
|
|
1285
|
-
] })
|
|
1286
|
-
] }) }),
|
|
1287
|
-
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { color: endpoint.status === "active" ? "teal" : "gray", variant: "light", children: endpoint.status }) }),
|
|
1288
|
-
/* @__PURE__ */ jsx(Table.Td, { children: endpoint.resourceId ? /* @__PURE__ */ jsx(Code, { children: endpoint.resourceId }) : /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", fs: "italic", children: "Not configured" }) }),
|
|
1289
|
-
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(
|
|
1290
|
-
Tooltip,
|
|
1291
|
-
{
|
|
1292
|
-
label: "Full URL is shown only at creation. Use the prefix to identify which endpoint this is.",
|
|
1293
|
-
position: "top",
|
|
1294
|
-
children: /* @__PURE__ */ jsx(Paper, { withBorder: true, style: { display: "inline-flex", alignItems: "center", padding: "2px 8px" }, children: /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", ff: "monospace", style: { userSelect: "all" }, children: keyDisplay }) })
|
|
1295
|
-
}
|
|
1296
|
-
) }),
|
|
1297
|
-
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: 4, children: [
|
|
1298
|
-
/* @__PURE__ */ jsx(IconClock, { size: 14, style: { opacity: 0.5 } }),
|
|
1299
|
-
/* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: formatDateTime(endpoint.lastTriggeredAt) })
|
|
1300
|
-
] }) }),
|
|
1301
|
-
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", children: endpoint.requestCount.toLocaleString() }) }),
|
|
1302
|
-
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: "xs", justify: "flex-end", children: [
|
|
1303
|
-
/* @__PURE__ */ jsx(Tooltip, { label: "Edit endpoint", position: "left", children: /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", color: "blue", onClick: () => setEditTarget(endpoint), children: /* @__PURE__ */ jsx(IconPencil, { size: 16 }) }) }),
|
|
1304
|
-
/* @__PURE__ */ jsx(
|
|
1305
|
-
Tooltip,
|
|
1306
|
-
{
|
|
1307
|
-
label: endpoint.status === "active" ? "Pause endpoint" : "Resume endpoint",
|
|
1308
|
-
position: "left",
|
|
1309
|
-
children: /* @__PURE__ */ jsx(
|
|
1310
|
-
ActionIcon,
|
|
1311
|
-
{
|
|
1312
|
-
variant: "subtle",
|
|
1313
|
-
color: endpoint.status === "active" ? "yellow" : "teal",
|
|
1314
|
-
onClick: () => handleToggleStatus(endpoint),
|
|
1315
|
-
disabled: isToggling,
|
|
1316
|
-
children: endpoint.status === "active" ? /* @__PURE__ */ jsx(IconPlayerPause, { size: 16 }) : /* @__PURE__ */ jsx(IconPlayerPlay, { size: 16 })
|
|
1317
|
-
}
|
|
1318
|
-
)
|
|
1319
|
-
}
|
|
1320
|
-
),
|
|
1321
|
-
/* @__PURE__ */ jsx(Tooltip, { label: "Delete endpoint", position: "left", children: /* @__PURE__ */ jsx(
|
|
1322
|
-
ActionIcon,
|
|
1323
|
-
{
|
|
1324
|
-
variant: "subtle",
|
|
1325
|
-
color: "red",
|
|
1326
|
-
onClick: () => setDeleteTarget(endpoint),
|
|
1327
|
-
disabled: deleteMutation.isPending,
|
|
1328
|
-
children: /* @__PURE__ */ jsx(IconTrash, { size: 16 })
|
|
1329
|
-
}
|
|
1330
|
-
) })
|
|
1331
|
-
] }) })
|
|
1332
|
-
] }, endpoint.id);
|
|
1333
|
-
}) })
|
|
842
|
+
] }) }),
|
|
843
|
+
/* @__PURE__ */ jsx(Divider, {}),
|
|
844
|
+
/* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
|
|
845
|
+
/* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 600, children: "Alerts" }),
|
|
846
|
+
/* @__PURE__ */ jsx(Alert, { variant: "light", color: "blue", icon: /* @__PURE__ */ jsx(IconAlertCircle, { size: 16 }), title: "Informational", children: "This is how informational alerts look with your current theme." }),
|
|
847
|
+
/* @__PURE__ */ jsx(Alert, { variant: "light", color: "green", icon: /* @__PURE__ */ jsx(IconCheck, { size: 16 }), title: "Success", children: "Operation completed successfully." })
|
|
848
|
+
] })
|
|
849
|
+
] })
|
|
1334
850
|
] }),
|
|
1335
|
-
/* @__PURE__ */ jsx(
|
|
1336
|
-
|
|
1337
|
-
/* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
|
|
1338
|
-
/* @__PURE__ */ jsx(IconAlertTriangle, { size: 24, color: "var(--color-error)" }),
|
|
1339
|
-
/* @__PURE__ */ jsx(Title, { order: 3, children: "Delete Webhook Endpoint" })
|
|
1340
|
-
] }),
|
|
851
|
+
/* @__PURE__ */ jsx(CustomModal, { opened: modalOpen, onClose: () => setModalOpen(false), size: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", p: "md", children: [
|
|
852
|
+
/* @__PURE__ */ jsx(Title, { order: 3, children: "Modal Preview" }),
|
|
1341
853
|
/* @__PURE__ */ jsxs(Text, { size: "sm", children: [
|
|
1342
|
-
"
|
|
854
|
+
"Modals should sit visibly above the page background. Elevation comes from the",
|
|
1343
855
|
" ",
|
|
1344
|
-
/* @__PURE__ */
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
'"'
|
|
1348
|
-
] }),
|
|
1349
|
-
"?"
|
|
856
|
+
/* @__PURE__ */ jsx(Text, { span: true, ff: "monospace", size: "sm", children: "--color-elevated" }),
|
|
857
|
+
" ",
|
|
858
|
+
"token, which every preset defines as an opaque surface."
|
|
1350
859
|
] }),
|
|
1351
|
-
/* @__PURE__ */ jsx(
|
|
1352
|
-
/* @__PURE__ */ jsxs(Group, { justify: "
|
|
1353
|
-
/* @__PURE__ */ jsx(Button, { variant: "
|
|
1354
|
-
/* @__PURE__ */ jsx(
|
|
1355
|
-
Button,
|
|
1356
|
-
{
|
|
1357
|
-
color: "red",
|
|
1358
|
-
onClick: handleDelete,
|
|
1359
|
-
loading: deleteMutation.isPending,
|
|
1360
|
-
leftSection: /* @__PURE__ */ jsx(IconTrash, { size: 16 }),
|
|
1361
|
-
children: "Delete Endpoint"
|
|
1362
|
-
}
|
|
1363
|
-
)
|
|
860
|
+
/* @__PURE__ */ jsx(TextInput, { label: "Example field", placeholder: "Type something...", size: "xs" }),
|
|
861
|
+
/* @__PURE__ */ jsxs(Group, { justify: "flex-end", children: [
|
|
862
|
+
/* @__PURE__ */ jsx(Button, { variant: "default", size: "xs", onClick: () => setModalOpen(false), children: "Cancel" }),
|
|
863
|
+
/* @__PURE__ */ jsx(Button, { size: "xs", onClick: () => setModalOpen(false), children: "Confirm" })
|
|
1364
864
|
] })
|
|
1365
865
|
] }) })
|
|
1366
866
|
] });
|
|
1367
867
|
}
|
|
1368
|
-
function
|
|
1369
|
-
const
|
|
868
|
+
function EditWebhookEndpointModal({ opened, endpoint, onClose }) {
|
|
869
|
+
const updateEndpoint = useUpdateWebhookEndpoint();
|
|
1370
870
|
const { data: resourcesData } = useResources();
|
|
1371
871
|
const resourceOptions = useMemo(() => {
|
|
1372
872
|
if (!resourcesData) return [];
|
|
@@ -1383,8 +883,7 @@ function CreateWebhookEndpointModal({ opened, onClose, onSuccess }) {
|
|
|
1383
883
|
const form = useForm({
|
|
1384
884
|
initialValues: {
|
|
1385
885
|
name: "",
|
|
1386
|
-
resourceId: ""
|
|
1387
|
-
description: ""
|
|
886
|
+
resourceId: ""
|
|
1388
887
|
},
|
|
1389
888
|
validate: {
|
|
1390
889
|
name: (value) => {
|
|
@@ -1395,833 +894,703 @@ function CreateWebhookEndpointModal({ opened, onClose, onSuccess }) {
|
|
|
1395
894
|
}
|
|
1396
895
|
}
|
|
1397
896
|
});
|
|
897
|
+
useEffect(() => {
|
|
898
|
+
if (endpoint && opened) {
|
|
899
|
+
form.setValues({
|
|
900
|
+
name: endpoint.name,
|
|
901
|
+
resourceId: endpoint.resourceId ?? ""
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
}, [endpoint, opened]);
|
|
1398
905
|
const handleSubmit = async (values) => {
|
|
906
|
+
if (!endpoint) return;
|
|
1399
907
|
try {
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
908
|
+
await updateEndpoint.mutateAsync({
|
|
909
|
+
endpointId: endpoint.id,
|
|
910
|
+
data: {
|
|
911
|
+
name: values.name.trim(),
|
|
912
|
+
resourceId: values.resourceId || void 0
|
|
913
|
+
}
|
|
1404
914
|
});
|
|
1405
|
-
|
|
1406
|
-
onSuccess(result);
|
|
915
|
+
onClose();
|
|
1407
916
|
} catch (error) {
|
|
1408
|
-
console.error("Failed to
|
|
917
|
+
console.error("Failed to update webhook endpoint:", error);
|
|
1409
918
|
}
|
|
1410
919
|
};
|
|
1411
920
|
const handleClose = () => {
|
|
1412
|
-
if (!
|
|
1413
|
-
form.reset();
|
|
921
|
+
if (!updateEndpoint.isPending) {
|
|
1414
922
|
onClose();
|
|
1415
923
|
}
|
|
1416
924
|
};
|
|
1417
|
-
return /* @__PURE__ */ jsx(CustomModal, { opened, onClose: handleClose, size: "md", loading:
|
|
1418
|
-
/* @__PURE__ */ jsx(Title, { order: 3, children: "
|
|
1419
|
-
/* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconExclamationMark, { size: 16 }), color: "blue", children: "A unique URL will be generated that any third-party app can POST to, triggering your target resource. The URL contains a secret key \u2014 treat it like a credential." }),
|
|
925
|
+
return /* @__PURE__ */ jsx(CustomModal, { opened, onClose: handleClose, size: "md", loading: updateEndpoint.isPending, children: /* @__PURE__ */ jsx("form", { onSubmit: form.onSubmit(handleSubmit), children: /* @__PURE__ */ jsxs(Stack, { children: [
|
|
926
|
+
/* @__PURE__ */ jsx(Title, { order: 3, children: "Edit Webhook Endpoint" }),
|
|
1420
927
|
/* @__PURE__ */ jsx(
|
|
1421
928
|
TextInput,
|
|
1422
|
-
{
|
|
1423
|
-
label: "Name",
|
|
1424
|
-
description: "A descriptive label for this endpoint",
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
),
|
|
1432
|
-
/* @__PURE__ */ jsx(
|
|
1433
|
-
Select,
|
|
1434
|
-
{
|
|
1435
|
-
label: "Target Resource",
|
|
1436
|
-
description: "The workflow or agent to trigger on each inbound request (can be set later)",
|
|
1437
|
-
placeholder: resourcesData ? "Select a workflow or agent" : "Loading resources...",
|
|
1438
|
-
data: resourceOptions,
|
|
1439
|
-
searchable: true,
|
|
1440
|
-
clearable: true,
|
|
1441
|
-
...form.getInputProps("resourceId"),
|
|
1442
|
-
disabled: !resourcesData || createEndpoint.isPending
|
|
1443
|
-
}
|
|
1444
|
-
),
|
|
1445
|
-
/* @__PURE__ */ jsx(
|
|
1446
|
-
Textarea,
|
|
1447
|
-
{
|
|
1448
|
-
label: "Description",
|
|
1449
|
-
description: "Optional notes about what this endpoint is for",
|
|
1450
|
-
placeholder: "e.g., Receives new lead submissions from Zapier",
|
|
1451
|
-
rows: 3,
|
|
1452
|
-
...form.getInputProps("description"),
|
|
1453
|
-
disabled: createEndpoint.isPending
|
|
1454
|
-
}
|
|
1455
|
-
),
|
|
1456
|
-
createEndpoint.error && /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconExclamationMark, { size: 16 }), color: "red", children: createEndpoint.error instanceof Error ? createEndpoint.error.message : "An error occurred while creating the webhook endpoint" }),
|
|
1457
|
-
/* @__PURE__ */ jsxs(Group, { justify: "flex-end", gap: "sm", children: [
|
|
1458
|
-
/* @__PURE__ */ jsx(Button, { variant: "subtle", onClick: handleClose, disabled: createEndpoint.isPending, children: "Cancel" }),
|
|
1459
|
-
/* @__PURE__ */ jsx(Button, { type: "submit", loading: createEndpoint.isPending, leftSection: /* @__PURE__ */ jsx(IconWebhook, { size: 16 }), children: "Create Endpoint" })
|
|
1460
|
-
] })
|
|
1461
|
-
] }) }) });
|
|
1462
|
-
}
|
|
1463
|
-
function getWebhookUrl(apiUrl, key) {
|
|
1464
|
-
return `${apiUrl}/api/webhooks/inbound/${key}`;
|
|
1465
|
-
}
|
|
1466
|
-
function WebhookEndpointSettings({ apiUrl }) {
|
|
1467
|
-
const { organizationReady } = useInitialization();
|
|
1468
|
-
const [showCreateModal, setShowCreateModal] = useState(false);
|
|
1469
|
-
const [newEndpoint, setNewEndpoint] = useState(null);
|
|
1470
|
-
const { data: endpoints = [], isLoading } = useListWebhookEndpoints();
|
|
1471
|
-
if (!organizationReady) return /* @__PURE__ */ jsx(AppShellLoader, {});
|
|
1472
|
-
const handleEndpointCreated = (endpoint) => {
|
|
1473
|
-
setNewEndpoint(endpoint);
|
|
1474
|
-
setShowCreateModal(false);
|
|
1475
|
-
};
|
|
1476
|
-
const activeCount = endpoints.filter((e) => e.status === "active").length;
|
|
1477
|
-
const totalRequests = endpoints.reduce((sum, e) => sum + e.requestCount, 0);
|
|
1478
|
-
return /* @__PURE__ */ jsxs(Stack, { children: [
|
|
1479
|
-
/* @__PURE__ */ jsx(
|
|
1480
|
-
PageTitleCaption,
|
|
1481
|
-
{
|
|
1482
|
-
title: "Webhooks",
|
|
1483
|
-
caption: "Create webhook URLs that third-party apps can send data to, triggering your resources.",
|
|
1484
|
-
rightSection: /* @__PURE__ */ jsx(Button, { leftSection: /* @__PURE__ */ jsx(IconPlus, { size: 16 }), onClick: () => setShowCreateModal(true), variant: "light", children: "Create Endpoint" })
|
|
1485
|
-
}
|
|
1486
|
-
),
|
|
1487
|
-
/* @__PURE__ */ jsxs(SimpleGrid, { cols: 2, children: [
|
|
1488
|
-
/* @__PURE__ */ jsx(
|
|
1489
|
-
StatCard,
|
|
1490
|
-
{
|
|
1491
|
-
variant: "hero",
|
|
1492
|
-
icon: IconWebhook,
|
|
1493
|
-
value: activeCount,
|
|
1494
|
-
label: "Active Endpoints",
|
|
1495
|
-
isLoading
|
|
1496
|
-
}
|
|
1497
|
-
),
|
|
1498
|
-
/* @__PURE__ */ jsx(
|
|
1499
|
-
StatCard,
|
|
1500
|
-
{
|
|
1501
|
-
variant: "hero",
|
|
1502
|
-
icon: IconActivity,
|
|
1503
|
-
value: totalRequests,
|
|
1504
|
-
label: "Total Requests",
|
|
1505
|
-
isLoading
|
|
1506
|
-
}
|
|
1507
|
-
)
|
|
1508
|
-
] }),
|
|
1509
|
-
/* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(WebhookEndpointList, { endpoints, isLoading }) }),
|
|
1510
|
-
/* @__PURE__ */ jsx(
|
|
1511
|
-
CreateWebhookEndpointModal,
|
|
1512
|
-
{
|
|
1513
|
-
opened: showCreateModal,
|
|
1514
|
-
onClose: () => setShowCreateModal(false),
|
|
1515
|
-
onSuccess: handleEndpointCreated
|
|
1516
|
-
}
|
|
1517
|
-
),
|
|
1518
|
-
newEndpoint && newEndpoint.key && /* @__PURE__ */ jsx(
|
|
1519
|
-
WebhookUrlDisplayModal,
|
|
1520
|
-
{
|
|
1521
|
-
opened: true,
|
|
1522
|
-
endpoint: newEndpoint,
|
|
1523
|
-
webhookUrl: getWebhookUrl(apiUrl, newEndpoint.key),
|
|
1524
|
-
onClose: () => setNewEndpoint(null)
|
|
1525
|
-
}
|
|
1526
|
-
)
|
|
1527
|
-
] });
|
|
1528
|
-
}
|
|
1529
|
-
function diffRoles(current, next) {
|
|
1530
|
-
const adds = next.filter((id) => !current.includes(id));
|
|
1531
|
-
const removes = current.filter((id) => !next.includes(id));
|
|
1532
|
-
return { adds, removes };
|
|
1533
|
-
}
|
|
1534
|
-
function MemberAccessModal({ opened, onClose, orgId, member, defaultTab = "roles" }) {
|
|
1535
|
-
const [activeTab, setActiveTab] = useState(defaultTab);
|
|
1536
|
-
useEffect(() => {
|
|
1537
|
-
if (opened) setActiveTab(defaultTab);
|
|
1538
|
-
}, [opened, defaultTab]);
|
|
1539
|
-
const handleClose = () => {
|
|
1540
|
-
onClose();
|
|
1541
|
-
};
|
|
1542
|
-
if (!member) return null;
|
|
1543
|
-
return /* @__PURE__ */ jsx(CustomModal, { opened, onClose: handleClose, size: "xl", children: /* @__PURE__ */ jsxs(Stack, { p: "md", gap: "md", children: [
|
|
1544
|
-
/* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
|
|
1545
|
-
/* @__PURE__ */ jsx(Title, { order: 3, children: "Member access" }),
|
|
1546
|
-
/* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
|
|
1547
|
-
/* @__PURE__ */ jsx(Text, { fw: 500, children: member.name }),
|
|
1548
|
-
/* @__PURE__ */ jsx(Badge, { variant: "light", size: "sm", children: member.role })
|
|
1549
|
-
] }),
|
|
1550
|
-
/* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: member.email })
|
|
1551
|
-
] }),
|
|
1552
|
-
activeTab === "roles" && /* @__PURE__ */ jsx(RolesTabPanel, { orgId, member, onSaved: onClose })
|
|
1553
|
-
] }) });
|
|
1554
|
-
}
|
|
1555
|
-
function RolesTabPanel({ orgId, member, onSaved }) {
|
|
1556
|
-
const { data: rolesData, isLoading: rolesLoading } = useOrgRoles(orgId ? brandSupabaseOrgId(orgId) : void 0);
|
|
1557
|
-
const assignMutation = useAssignRole();
|
|
1558
|
-
const revokeMutation = useRevokeRole();
|
|
1559
|
-
const canManagePrivileged = useAccess(AccessKeys.rolesManage).allowed;
|
|
1560
|
-
const allRoles = rolesData?.roles ?? [];
|
|
1561
|
-
const currentRoleIds = useMemo(() => {
|
|
1562
|
-
const matched = allRoles.find((r) => r.slug === member.role);
|
|
1563
|
-
return matched ? [matched.id] : [];
|
|
1564
|
-
}, [allRoles, member.role]);
|
|
1565
|
-
const [selectedIds, setSelectedIds] = useState(currentRoleIds);
|
|
1566
|
-
const [saving, setSaving] = useState(false);
|
|
1567
|
-
useEffect(() => {
|
|
1568
|
-
setSelectedIds(currentRoleIds);
|
|
1569
|
-
}, [currentRoleIds, member.id]);
|
|
1570
|
-
const options = allRoles.map((r) => ({
|
|
1571
|
-
value: r.id,
|
|
1572
|
-
label: r.name + (r.is_system ? " (built-in)" : ""),
|
|
1573
|
-
disabled: !canManagePrivileged && (r.slug === "owner" || r.slug === "admin")
|
|
1574
|
-
}));
|
|
1575
|
-
const hasZeroRoles = selectedIds.length === 0;
|
|
1576
|
-
const isUnchanged = selectedIds.length === currentRoleIds.length && selectedIds.every((id) => currentRoleIds.includes(id));
|
|
1577
|
-
async function handleSave() {
|
|
1578
|
-
if (hasZeroRoles || isUnchanged) return;
|
|
1579
|
-
setSaving(true);
|
|
1580
|
-
try {
|
|
1581
|
-
const { adds, removes } = diffRoles(currentRoleIds, selectedIds);
|
|
1582
|
-
await Promise.all([
|
|
1583
|
-
...adds.map((roleId) => assignMutation.mutateAsync({ membershipId: member.id, roleId })),
|
|
1584
|
-
...removes.map((roleId) => revokeMutation.mutateAsync({ membershipId: member.id, roleId }))
|
|
1585
|
-
]);
|
|
1586
|
-
showSuccessNotification("Roles updated");
|
|
1587
|
-
onSaved();
|
|
1588
|
-
} catch (err) {
|
|
1589
|
-
showAuthError(err);
|
|
1590
|
-
} finally {
|
|
1591
|
-
setSaving(false);
|
|
1592
|
-
}
|
|
1593
|
-
}
|
|
1594
|
-
return /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
|
|
1595
|
-
/* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "Select the roles assigned to this member. Each role grants a bundle of permissions; the member's effective permissions are the union of all assigned roles." }),
|
|
929
|
+
{
|
|
930
|
+
label: "Name",
|
|
931
|
+
description: "A descriptive label for this endpoint",
|
|
932
|
+
leftSection: /* @__PURE__ */ jsx(IconWebhook, { size: 16 }),
|
|
933
|
+
required: true,
|
|
934
|
+
...form.getInputProps("name"),
|
|
935
|
+
disabled: updateEndpoint.isPending
|
|
936
|
+
}
|
|
937
|
+
),
|
|
1596
938
|
/* @__PURE__ */ jsx(
|
|
1597
|
-
|
|
939
|
+
Select,
|
|
1598
940
|
{
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
disabled: saving || rolesLoading,
|
|
941
|
+
label: "Target Resource",
|
|
942
|
+
description: "The workflow or agent to trigger on each inbound request",
|
|
943
|
+
placeholder: resourcesData ? "Select a workflow or agent" : "Loading resources...",
|
|
944
|
+
data: resourceOptions,
|
|
1604
945
|
searchable: true,
|
|
1605
|
-
clearable:
|
|
946
|
+
clearable: true,
|
|
947
|
+
...form.getInputProps("resourceId"),
|
|
948
|
+
disabled: !resourcesData || updateEndpoint.isPending
|
|
1606
949
|
}
|
|
1607
950
|
),
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
/* @__PURE__ */ jsx("
|
|
1611
|
-
"
|
|
1612
|
-
] }),
|
|
1613
|
-
!rolesLoading && hasZeroRoles && currentRoleIds.length > 0 && /* @__PURE__ */ jsx(Text, { size: "xs", c: "red", children: "At least one role is required for an active membership." }),
|
|
1614
|
-
/* @__PURE__ */ jsxs(Group, { justify: "flex-end", mt: "md", children: [
|
|
1615
|
-
/* @__PURE__ */ jsx(Button, { variant: "default", onClick: onSaved, disabled: saving, children: "Cancel" }),
|
|
1616
|
-
/* @__PURE__ */ jsx(
|
|
1617
|
-
Button,
|
|
1618
|
-
{
|
|
1619
|
-
leftSection: /* @__PURE__ */ jsx(IconCheck, { size: 14 }),
|
|
1620
|
-
loading: saving,
|
|
1621
|
-
disabled: hasZeroRoles || isUnchanged,
|
|
1622
|
-
onClick: handleSave,
|
|
1623
|
-
children: "Save roles"
|
|
1624
|
-
}
|
|
1625
|
-
)
|
|
951
|
+
updateEndpoint.error && /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconExclamationMark, { size: 16 }), color: "red", children: updateEndpoint.error instanceof Error ? updateEndpoint.error.message : "An error occurred while updating the webhook endpoint" }),
|
|
952
|
+
/* @__PURE__ */ jsxs(Group, { justify: "flex-end", gap: "sm", children: [
|
|
953
|
+
/* @__PURE__ */ jsx(Button, { variant: "subtle", onClick: handleClose, disabled: updateEndpoint.isPending, children: "Cancel" }),
|
|
954
|
+
/* @__PURE__ */ jsx(Button, { type: "submit", loading: updateEndpoint.isPending, leftSection: /* @__PURE__ */ jsx(IconPencil, { size: 16 }), children: "Save Changes" })
|
|
1626
955
|
] })
|
|
1627
|
-
] });
|
|
956
|
+
] }) }) });
|
|
1628
957
|
}
|
|
1629
|
-
function
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
const name = firstName && lastName ? `${firstName} ${lastName}` : member.user?.email || "Unknown User";
|
|
1633
|
-
return {
|
|
1634
|
-
id: member.id,
|
|
1635
|
-
name,
|
|
1636
|
-
email: member.user?.email || "Unknown",
|
|
1637
|
-
role: member.role.slug
|
|
1638
|
-
};
|
|
958
|
+
function formatKeyDisplay(keyPrefix) {
|
|
959
|
+
if (!keyPrefix) return "Unavailable";
|
|
960
|
+
return `${keyPrefix}...`;
|
|
1639
961
|
}
|
|
1640
|
-
function
|
|
1641
|
-
const
|
|
1642
|
-
const [
|
|
1643
|
-
const
|
|
1644
|
-
const
|
|
1645
|
-
const
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
962
|
+
function WebhookEndpointList({ endpoints, isLoading }) {
|
|
963
|
+
const [deleteTarget, setDeleteTarget] = useState(null);
|
|
964
|
+
const [editTarget, setEditTarget] = useState(null);
|
|
965
|
+
const deleteMutation = useDeleteWebhookEndpoint();
|
|
966
|
+
const updateMutation = useUpdateWebhookEndpoint();
|
|
967
|
+
const handleDelete = () => {
|
|
968
|
+
if (deleteTarget) {
|
|
969
|
+
deleteMutation.mutate(deleteTarget.id);
|
|
970
|
+
setDeleteTarget(null);
|
|
971
|
+
}
|
|
1649
972
|
};
|
|
1650
|
-
const
|
|
1651
|
-
|
|
1652
|
-
|
|
973
|
+
const handleCloseDelete = () => {
|
|
974
|
+
if (!deleteMutation.isPending) {
|
|
975
|
+
setDeleteTarget(null);
|
|
976
|
+
}
|
|
977
|
+
};
|
|
978
|
+
const handleToggleStatus = (endpoint) => {
|
|
979
|
+
const newStatus = endpoint.status === "active" ? "paused" : "active";
|
|
980
|
+
updateMutation.mutate({ endpointId: endpoint.id, data: { status: newStatus } });
|
|
1653
981
|
};
|
|
1654
982
|
if (isLoading) {
|
|
1655
|
-
return /* @__PURE__ */ jsx(
|
|
1656
|
-
/* @__PURE__ */ jsx(Loader, { size: "md" }),
|
|
1657
|
-
/* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "Loading members..." })
|
|
1658
|
-
] }) });
|
|
1659
|
-
}
|
|
1660
|
-
if (error) {
|
|
1661
|
-
return /* @__PURE__ */ jsx(APIErrorAlert, { error, title: "Error loading members" });
|
|
983
|
+
return /* @__PURE__ */ jsx(ListSkeleton, { rows: 3, rowHeight: 50 });
|
|
1662
984
|
}
|
|
1663
|
-
if (
|
|
1664
|
-
return /* @__PURE__ */ jsx(
|
|
985
|
+
if (endpoints.length === 0) {
|
|
986
|
+
return /* @__PURE__ */ jsx(
|
|
987
|
+
EmptyState,
|
|
988
|
+
{
|
|
989
|
+
icon: IconWebhook,
|
|
990
|
+
title: "No webhook endpoints yet",
|
|
991
|
+
description: "Create your first webhook endpoint to let third-party apps trigger your resources"
|
|
992
|
+
}
|
|
993
|
+
);
|
|
1665
994
|
}
|
|
1666
995
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1667
|
-
/* @__PURE__ */
|
|
996
|
+
/* @__PURE__ */ jsxs(Table, { children: [
|
|
1668
997
|
/* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
|
|
1669
998
|
/* @__PURE__ */ jsx(Table.Th, { children: "Name" }),
|
|
1670
|
-
/* @__PURE__ */ jsx(Table.Th, { children: "Email" }),
|
|
1671
|
-
/* @__PURE__ */ jsx(Table.Th, { children: "Role" }),
|
|
1672
999
|
/* @__PURE__ */ jsx(Table.Th, { children: "Status" }),
|
|
1673
|
-
/* @__PURE__ */ jsx(Table.Th, { children: "
|
|
1000
|
+
/* @__PURE__ */ jsx(Table.Th, { children: "Target Resource" }),
|
|
1001
|
+
/* @__PURE__ */ jsx(Table.Th, { children: "Key" }),
|
|
1002
|
+
/* @__PURE__ */ jsx(Table.Th, { children: "Last Triggered" }),
|
|
1003
|
+
/* @__PURE__ */ jsx(Table.Th, { children: "Requests" }),
|
|
1004
|
+
/* @__PURE__ */ jsx(Table.Th, { w: 120 })
|
|
1674
1005
|
] }) }),
|
|
1675
|
-
/* @__PURE__ */ jsx(Table.Tbody, { children:
|
|
1676
|
-
const
|
|
1677
|
-
const
|
|
1678
|
-
const displayName = firstName && lastName ? `${firstName} ${lastName}` : member.user?.email || "Unknown User";
|
|
1006
|
+
/* @__PURE__ */ jsx(Table.Tbody, { children: endpoints.map((endpoint) => {
|
|
1007
|
+
const keyDisplay = formatKeyDisplay(endpoint.keyPrefix);
|
|
1008
|
+
const isToggling = updateMutation.isPending;
|
|
1679
1009
|
return /* @__PURE__ */ jsxs(Table.Tr, { children: [
|
|
1680
|
-
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { fw: 500, size: "sm", children: displayName }) }),
|
|
1681
|
-
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: member.user?.email || "Unknown" }) }),
|
|
1682
|
-
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(
|
|
1683
|
-
RoleBadge,
|
|
1684
|
-
{
|
|
1685
|
-
slug: member.role.slug,
|
|
1686
|
-
name: member.role.slug.charAt(0).toUpperCase() + member.role.slug.slice(1),
|
|
1687
|
-
isSystem: true
|
|
1688
|
-
}
|
|
1689
|
-
) }),
|
|
1690
1010
|
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
|
|
1691
|
-
/* @__PURE__ */ jsx(
|
|
1692
|
-
/* @__PURE__ */
|
|
1011
|
+
/* @__PURE__ */ jsx(IconWebhook, { size: 16, style: { opacity: 0.6 } }),
|
|
1012
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
|
|
1013
|
+
/* @__PURE__ */ jsx(Text, { fw: 500, children: endpoint.name }),
|
|
1014
|
+
endpoint.description && /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: endpoint.description })
|
|
1015
|
+
] })
|
|
1693
1016
|
] }) }),
|
|
1694
|
-
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
/* @__PURE__ */ jsx(
|
|
1699
|
-
MemberAccessModal,
|
|
1700
|
-
{
|
|
1701
|
-
opened: modalOpened,
|
|
1702
|
-
onClose: handleModalClose,
|
|
1703
|
-
orgId,
|
|
1704
|
-
member: selectedMember ? transformMemberForModal(selectedMember) : null,
|
|
1705
|
-
defaultTab
|
|
1706
|
-
}
|
|
1707
|
-
)
|
|
1708
|
-
] });
|
|
1709
|
-
}
|
|
1710
|
-
function OrganizationSettings({ user, currentMembership, isOrgAdmin }) {
|
|
1711
|
-
return /* @__PURE__ */ jsxs(Stack, { children: [
|
|
1712
|
-
/* @__PURE__ */ jsx(PageTitleCaption, { title: "Organization Settings", caption: "Manage your current organization" }),
|
|
1713
|
-
currentMembership?.organization && /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
|
|
1714
|
-
/* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
|
|
1715
|
-
/* @__PURE__ */ jsx(ThemeIcon, { size: "lg", variant: "light", color: "green", children: /* @__PURE__ */ jsx(IconBuilding, { size: 18 }) }),
|
|
1716
|
-
/* @__PURE__ */ jsxs(Stack, { gap: 2, style: { flex: 1 }, children: [
|
|
1717
|
-
/* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Current Organization" }),
|
|
1718
|
-
/* @__PURE__ */ jsx(Text, { size: "lg", fw: 600, style: { fontFamily: "var(--elevasis-font-family-subtitle)" }, children: currentMembership.organization.name })
|
|
1719
|
-
] }),
|
|
1720
|
-
/* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
|
|
1721
|
-
/* @__PURE__ */ jsx(Code, { children: currentMembership.organization.id }),
|
|
1722
|
-
/* @__PURE__ */ jsx(CopyButton, { value: currentMembership.organization.id, timeout: 2e3, children: ({ copied, copy }) => /* @__PURE__ */ jsx(Tooltip, { label: copied ? "Copied!" : "Copy ID", withArrow: true, position: "top", children: /* @__PURE__ */ jsx(
|
|
1723
|
-
ActionIcon,
|
|
1724
|
-
{
|
|
1725
|
-
color: copied ? "teal" : "gray",
|
|
1726
|
-
variant: copied ? "filled" : "subtle",
|
|
1727
|
-
onClick: copy,
|
|
1728
|
-
size: "sm",
|
|
1729
|
-
children: copied ? /* @__PURE__ */ jsx(IconCheck, { size: 14 }) : /* @__PURE__ */ jsx(IconCopy, { size: 14 })
|
|
1730
|
-
}
|
|
1731
|
-
) }) })
|
|
1732
|
-
] })
|
|
1733
|
-
] }),
|
|
1734
|
-
user && /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
|
|
1735
|
-
/* @__PURE__ */ jsx(ThemeIcon, { size: "lg", variant: "light", color: "blue", children: /* @__PURE__ */ jsx(IconUsers, { size: 18 }) }),
|
|
1736
|
-
/* @__PURE__ */ jsxs(Stack, { gap: 2, style: { flex: 1 }, children: [
|
|
1737
|
-
/* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Signed in as" }),
|
|
1738
|
-
/* @__PURE__ */ jsxs(Text, { size: "lg", fw: 600, style: { fontFamily: "var(--elevasis-font-family-subtitle)" }, children: [
|
|
1739
|
-
user.firstName,
|
|
1740
|
-
" ",
|
|
1741
|
-
user.lastName,
|
|
1742
|
-
" \u2022 ",
|
|
1743
|
-
user.email
|
|
1744
|
-
] })
|
|
1745
|
-
] }),
|
|
1746
|
-
/* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
|
|
1747
|
-
/* @__PURE__ */ jsx(Code, { children: currentMembership.userId }),
|
|
1748
|
-
/* @__PURE__ */ jsx(CopyButton, { value: currentMembership.userId, timeout: 2e3, children: ({ copied, copy }) => /* @__PURE__ */ jsx(Tooltip, { label: copied ? "Copied!" : "Copy User ID", withArrow: true, position: "top", children: /* @__PURE__ */ jsx(
|
|
1749
|
-
ActionIcon,
|
|
1017
|
+
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { color: endpoint.status === "active" ? "teal" : "gray", variant: "light", children: endpoint.status }) }),
|
|
1018
|
+
/* @__PURE__ */ jsx(Table.Td, { children: endpoint.resourceId ? /* @__PURE__ */ jsx(Code, { children: endpoint.resourceId }) : /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", fs: "italic", children: "Not configured" }) }),
|
|
1019
|
+
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(
|
|
1020
|
+
Tooltip,
|
|
1750
1021
|
{
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
size: "sm",
|
|
1755
|
-
children: copied ? /* @__PURE__ */ jsx(IconCheck, { size: 14 }) : /* @__PURE__ */ jsx(IconCopy, { size: 14 })
|
|
1022
|
+
label: "Full URL is shown only at creation. Use the prefix to identify which endpoint this is.",
|
|
1023
|
+
position: "top",
|
|
1024
|
+
children: /* @__PURE__ */ jsx(Paper, { withBorder: true, style: { display: "inline-flex", alignItems: "center", padding: "2px 8px" }, children: /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", ff: "monospace", style: { userSelect: "all" }, children: keyDisplay }) })
|
|
1756
1025
|
}
|
|
1757
|
-
) })
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
/* @__PURE__ */ jsx(
|
|
1797
|
-
/* @__PURE__ */ jsx(
|
|
1798
|
-
/* @__PURE__ */ jsx(
|
|
1799
|
-
CardHeader,
|
|
1800
|
-
{
|
|
1801
|
-
icon: /* @__PURE__ */ jsx(IconShieldLock, { size: 18 }),
|
|
1802
|
-
title: "Your roles",
|
|
1803
|
-
subtitle: `Roles assigned to your membership in ${orgName}.`
|
|
1804
|
-
}
|
|
1805
|
-
),
|
|
1026
|
+
) }),
|
|
1027
|
+
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: 4, children: [
|
|
1028
|
+
/* @__PURE__ */ jsx(IconClock, { size: 14, style: { opacity: 0.5 } }),
|
|
1029
|
+
/* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: formatDateTime(endpoint.lastTriggeredAt) })
|
|
1030
|
+
] }) }),
|
|
1031
|
+
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", children: endpoint.requestCount.toLocaleString() }) }),
|
|
1032
|
+
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: "xs", justify: "flex-end", children: [
|
|
1033
|
+
/* @__PURE__ */ jsx(Tooltip, { label: "Edit endpoint", position: "left", children: /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", color: "blue", onClick: () => setEditTarget(endpoint), children: /* @__PURE__ */ jsx(IconPencil, { size: 16 }) }) }),
|
|
1034
|
+
/* @__PURE__ */ jsx(
|
|
1035
|
+
Tooltip,
|
|
1036
|
+
{
|
|
1037
|
+
label: endpoint.status === "active" ? "Pause endpoint" : "Resume endpoint",
|
|
1038
|
+
position: "left",
|
|
1039
|
+
children: /* @__PURE__ */ jsx(
|
|
1040
|
+
ActionIcon,
|
|
1041
|
+
{
|
|
1042
|
+
variant: "subtle",
|
|
1043
|
+
color: endpoint.status === "active" ? "yellow" : "teal",
|
|
1044
|
+
onClick: () => handleToggleStatus(endpoint),
|
|
1045
|
+
disabled: isToggling,
|
|
1046
|
+
children: endpoint.status === "active" ? /* @__PURE__ */ jsx(IconPlayerPause, { size: 16 }) : /* @__PURE__ */ jsx(IconPlayerPlay, { size: 16 })
|
|
1047
|
+
}
|
|
1048
|
+
)
|
|
1049
|
+
}
|
|
1050
|
+
),
|
|
1051
|
+
/* @__PURE__ */ jsx(Tooltip, { label: "Delete endpoint", position: "left", children: /* @__PURE__ */ jsx(
|
|
1052
|
+
ActionIcon,
|
|
1053
|
+
{
|
|
1054
|
+
variant: "subtle",
|
|
1055
|
+
color: "red",
|
|
1056
|
+
onClick: () => setDeleteTarget(endpoint),
|
|
1057
|
+
disabled: deleteMutation.isPending,
|
|
1058
|
+
children: /* @__PURE__ */ jsx(IconTrash, { size: 16 })
|
|
1059
|
+
}
|
|
1060
|
+
) })
|
|
1061
|
+
] }) })
|
|
1062
|
+
] }, endpoint.id);
|
|
1063
|
+
}) })
|
|
1064
|
+
] }),
|
|
1065
|
+
/* @__PURE__ */ jsx(EditWebhookEndpointModal, { opened: !!editTarget, endpoint: editTarget, onClose: () => setEditTarget(null) }),
|
|
1066
|
+
/* @__PURE__ */ jsx(CustomModal, { opened: !!deleteTarget, onClose: handleCloseDelete, size: "md", loading: deleteMutation.isPending, children: /* @__PURE__ */ jsxs(Stack, { children: [
|
|
1806
1067
|
/* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
|
|
1807
|
-
|
|
1808
|
-
|
|
1068
|
+
/* @__PURE__ */ jsx(IconAlertTriangle, { size: 24, color: "var(--color-error)" }),
|
|
1069
|
+
/* @__PURE__ */ jsx(Title, { order: 3, children: "Delete Webhook Endpoint" })
|
|
1809
1070
|
] }),
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
),
|
|
1821
|
-
|
|
1822
|
-
/* @__PURE__ */ jsx(
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
{
|
|
1834
|
-
icon: /* @__PURE__ */ jsx(IconUserCog, { size: 18 }),
|
|
1835
|
-
title: "Manage roles & members",
|
|
1836
|
-
subtitle: "Assign roles to members, create custom roles, and edit permission sets."
|
|
1837
|
-
}
|
|
1838
|
-
),
|
|
1839
|
-
/* @__PURE__ */ jsxs(Group, { children: [
|
|
1840
|
-
onManageMembers ? /* @__PURE__ */ jsx(Button, { variant: "light", leftSection: /* @__PURE__ */ jsx(IconUsersGroup, { size: 14 }), onClick: onManageMembers, children: "Members" }) : null,
|
|
1841
|
-
onManageOrganizationRoles ? /* @__PURE__ */ jsx(AccessGuard, { accessKey: AccessKeys.organizationManage, children: /* @__PURE__ */ jsx(Button, { variant: "light", leftSection: /* @__PURE__ */ jsx(IconShieldLock, { size: 14 }), onClick: onManageOrganizationRoles, children: "Organization roles" }) }) : null
|
|
1071
|
+
/* @__PURE__ */ jsxs(Text, { size: "sm", children: [
|
|
1072
|
+
"Are you sure you want to delete",
|
|
1073
|
+
" ",
|
|
1074
|
+
/* @__PURE__ */ jsxs(Text, { span: true, fw: 600, children: [
|
|
1075
|
+
'"',
|
|
1076
|
+
deleteTarget?.name,
|
|
1077
|
+
'"'
|
|
1078
|
+
] }),
|
|
1079
|
+
"?"
|
|
1080
|
+
] }),
|
|
1081
|
+
/* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "This action cannot be undone. Any third-party apps posting to this URL will receive a 404. The URL and its key will be permanently revoked." }),
|
|
1082
|
+
/* @__PURE__ */ jsxs(Group, { justify: "space-between", gap: "sm", children: [
|
|
1083
|
+
/* @__PURE__ */ jsx(Button, { variant: "light", onClick: handleCloseDelete, disabled: deleteMutation.isPending, children: "Cancel" }),
|
|
1084
|
+
/* @__PURE__ */ jsx(
|
|
1085
|
+
Button,
|
|
1086
|
+
{
|
|
1087
|
+
color: "red",
|
|
1088
|
+
onClick: handleDelete,
|
|
1089
|
+
loading: deleteMutation.isPending,
|
|
1090
|
+
leftSection: /* @__PURE__ */ jsx(IconTrash, { size: 16 }),
|
|
1091
|
+
children: "Delete Endpoint"
|
|
1092
|
+
}
|
|
1093
|
+
)
|
|
1842
1094
|
] })
|
|
1843
|
-
] }) })
|
|
1095
|
+
] }) })
|
|
1844
1096
|
] });
|
|
1845
1097
|
}
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
const
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1098
|
+
function CreateWebhookEndpointModal({ opened, onClose, onSuccess }) {
|
|
1099
|
+
const createEndpoint = useCreateWebhookEndpoint();
|
|
1100
|
+
const { data: resourcesData } = useResources();
|
|
1101
|
+
const resourceOptions = useMemo(() => {
|
|
1102
|
+
if (!resourcesData) return [];
|
|
1103
|
+
const workflowOptions = (resourcesData.workflows || []).filter((w) => w?.resourceId).map((w) => ({
|
|
1104
|
+
value: w.resourceId,
|
|
1105
|
+
label: `Workflow: ${w.resourceId}`
|
|
1106
|
+
}));
|
|
1107
|
+
const agentOptions = (resourcesData.agents || []).filter((a) => a?.resourceId).map((a) => ({
|
|
1108
|
+
value: a.resourceId,
|
|
1109
|
+
label: `Agent: ${a.resourceId}`
|
|
1110
|
+
}));
|
|
1111
|
+
return [...workflowOptions, ...agentOptions];
|
|
1112
|
+
}, [resourcesData]);
|
|
1113
|
+
const form = useForm({
|
|
1114
|
+
initialValues: {
|
|
1115
|
+
name: "",
|
|
1116
|
+
resourceId: "",
|
|
1117
|
+
description: ""
|
|
1118
|
+
},
|
|
1119
|
+
validate: {
|
|
1120
|
+
name: (value) => {
|
|
1121
|
+
if (!value.trim()) return "Name is required";
|
|
1122
|
+
if (value.trim().length < 2) return "Name must be at least 2 characters";
|
|
1123
|
+
if (value.trim().length > 100) return "Name must be less than 100 characters";
|
|
1124
|
+
return null;
|
|
1858
1125
|
}
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
const
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
);
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1126
|
+
}
|
|
1127
|
+
});
|
|
1128
|
+
const handleSubmit = async (values) => {
|
|
1129
|
+
try {
|
|
1130
|
+
const result = await createEndpoint.mutateAsync({
|
|
1131
|
+
name: values.name.trim(),
|
|
1132
|
+
...values.resourceId ? { resourceId: values.resourceId } : {},
|
|
1133
|
+
...values.description.trim() ? { description: values.description.trim() } : {}
|
|
1134
|
+
});
|
|
1135
|
+
form.reset();
|
|
1136
|
+
onSuccess(result);
|
|
1137
|
+
} catch (error) {
|
|
1138
|
+
console.error("Failed to create webhook endpoint:", error);
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
const handleClose = () => {
|
|
1142
|
+
if (!createEndpoint.isPending) {
|
|
1143
|
+
form.reset();
|
|
1144
|
+
onClose();
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1147
|
+
return /* @__PURE__ */ jsx(CustomModal, { opened, onClose: handleClose, size: "md", loading: createEndpoint.isPending, children: /* @__PURE__ */ jsx("form", { onSubmit: form.onSubmit(handleSubmit), children: /* @__PURE__ */ jsxs(Stack, { children: [
|
|
1148
|
+
/* @__PURE__ */ jsx(Title, { order: 3, children: "Create Webhook Endpoint" }),
|
|
1149
|
+
/* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconExclamationMark, { size: 16 }), color: "blue", children: "A unique URL will be generated that any third-party app can POST to, triggering your target resource. The URL contains a secret key \u2014 treat it like a credential." }),
|
|
1150
|
+
/* @__PURE__ */ jsx(
|
|
1151
|
+
TextInput,
|
|
1152
|
+
{
|
|
1153
|
+
label: "Name",
|
|
1154
|
+
description: "A descriptive label for this endpoint",
|
|
1155
|
+
placeholder: "e.g., Zapier Lead Intake, Typeform Responses",
|
|
1156
|
+
leftSection: /* @__PURE__ */ jsx(IconWebhook, { size: 16 }),
|
|
1157
|
+
required: true,
|
|
1158
|
+
...form.getInputProps("name"),
|
|
1159
|
+
disabled: createEndpoint.isPending
|
|
1874
1160
|
}
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1161
|
+
),
|
|
1162
|
+
/* @__PURE__ */ jsx(
|
|
1163
|
+
Select,
|
|
1164
|
+
{
|
|
1165
|
+
label: "Target Resource",
|
|
1166
|
+
description: "The workflow or agent to trigger on each inbound request (can be set later)",
|
|
1167
|
+
placeholder: resourcesData ? "Select a workflow or agent" : "Loading resources...",
|
|
1168
|
+
data: resourceOptions,
|
|
1169
|
+
searchable: true,
|
|
1170
|
+
clearable: true,
|
|
1171
|
+
...form.getInputProps("resourceId"),
|
|
1172
|
+
disabled: !resourcesData || createEndpoint.isPending
|
|
1883
1173
|
}
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1174
|
+
),
|
|
1175
|
+
/* @__PURE__ */ jsx(
|
|
1176
|
+
Textarea,
|
|
1177
|
+
{
|
|
1178
|
+
label: "Description",
|
|
1179
|
+
description: "Optional notes about what this endpoint is for",
|
|
1180
|
+
placeholder: "e.g., Receives new lead submissions from Zapier",
|
|
1181
|
+
rows: 3,
|
|
1182
|
+
...form.getInputProps("description"),
|
|
1183
|
+
disabled: createEndpoint.isPending
|
|
1184
|
+
}
|
|
1185
|
+
),
|
|
1186
|
+
createEndpoint.error && /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconExclamationMark, { size: 16 }), color: "red", children: createEndpoint.error instanceof Error ? createEndpoint.error.message : "An error occurred while creating the webhook endpoint" }),
|
|
1187
|
+
/* @__PURE__ */ jsxs(Group, { justify: "flex-end", gap: "sm", children: [
|
|
1188
|
+
/* @__PURE__ */ jsx(Button, { variant: "subtle", onClick: handleClose, disabled: createEndpoint.isPending, children: "Cancel" }),
|
|
1189
|
+
/* @__PURE__ */ jsx(Button, { type: "submit", loading: createEndpoint.isPending, leftSection: /* @__PURE__ */ jsx(IconWebhook, { size: 16 }), children: "Create Endpoint" })
|
|
1190
|
+
] })
|
|
1191
|
+
] }) }) });
|
|
1192
|
+
}
|
|
1193
|
+
function getWebhookUrl(apiUrl, key) {
|
|
1194
|
+
return `${apiUrl}/api/webhooks/inbound/${key}`;
|
|
1195
|
+
}
|
|
1196
|
+
function WebhookEndpointSettings({ apiUrl }) {
|
|
1197
|
+
const { organizationReady } = useInitialization();
|
|
1198
|
+
const [showCreateModal, setShowCreateModal] = useState(false);
|
|
1199
|
+
const [newEndpoint, setNewEndpoint] = useState(null);
|
|
1200
|
+
const { data: endpoints = [], isLoading } = useListWebhookEndpoints();
|
|
1201
|
+
if (!organizationReady) return /* @__PURE__ */ jsx(AppShellLoader, {});
|
|
1202
|
+
const handleEndpointCreated = (endpoint) => {
|
|
1203
|
+
setNewEndpoint(endpoint);
|
|
1204
|
+
setShowCreateModal(false);
|
|
1205
|
+
};
|
|
1206
|
+
const activeCount = endpoints.filter((e) => e.status === "active").length;
|
|
1207
|
+
const totalRequests = endpoints.reduce((sum, e) => sum + e.requestCount, 0);
|
|
1208
|
+
return /* @__PURE__ */ jsxs(Stack, { children: [
|
|
1209
|
+
/* @__PURE__ */ jsx(
|
|
1210
|
+
PageTitleCaption,
|
|
1211
|
+
{
|
|
1212
|
+
title: "Webhooks",
|
|
1213
|
+
caption: "Create webhook URLs that third-party apps can send data to, triggering your resources.",
|
|
1214
|
+
rightSection: /* @__PURE__ */ jsx(Button, { leftSection: /* @__PURE__ */ jsx(IconPlus, { size: 16 }), onClick: () => setShowCreateModal(true), variant: "light", children: "Create Endpoint" })
|
|
1911
1215
|
}
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
if (payload.status === "success") {
|
|
1923
|
-
resolve();
|
|
1924
|
-
} else {
|
|
1925
|
-
reject(new Error(payload.error || "oauth_failed"));
|
|
1926
|
-
}
|
|
1927
|
-
};
|
|
1928
|
-
const messageHandler = (event) => {
|
|
1929
|
-
if (!isTrustedCallbackOrigin(event.origin)) return;
|
|
1930
|
-
complete(event.data);
|
|
1931
|
-
};
|
|
1932
|
-
const storageHandler = (event) => {
|
|
1933
|
-
if (event.key !== OAUTH_COMPLETION_STORAGE_KEY || !event.newValue) return;
|
|
1934
|
-
try {
|
|
1935
|
-
complete(JSON.parse(event.newValue));
|
|
1936
|
-
} catch {
|
|
1216
|
+
),
|
|
1217
|
+
/* @__PURE__ */ jsxs(SimpleGrid, { cols: 2, children: [
|
|
1218
|
+
/* @__PURE__ */ jsx(
|
|
1219
|
+
StatCard,
|
|
1220
|
+
{
|
|
1221
|
+
variant: "hero",
|
|
1222
|
+
icon: IconWebhook,
|
|
1223
|
+
value: activeCount,
|
|
1224
|
+
label: "Active Endpoints",
|
|
1225
|
+
isLoading
|
|
1937
1226
|
}
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1227
|
+
),
|
|
1228
|
+
/* @__PURE__ */ jsx(
|
|
1229
|
+
StatCard,
|
|
1230
|
+
{
|
|
1231
|
+
variant: "hero",
|
|
1232
|
+
icon: IconActivity,
|
|
1233
|
+
value: totalRequests,
|
|
1234
|
+
label: "Total Requests",
|
|
1235
|
+
isLoading
|
|
1943
1236
|
}
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1237
|
+
)
|
|
1238
|
+
] }),
|
|
1239
|
+
/* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(WebhookEndpointList, { endpoints, isLoading }) }),
|
|
1240
|
+
/* @__PURE__ */ jsx(
|
|
1241
|
+
CreateWebhookEndpointModal,
|
|
1242
|
+
{
|
|
1243
|
+
opened: showCreateModal,
|
|
1244
|
+
onClose: () => setShowCreateModal(false),
|
|
1245
|
+
onSuccess: handleEndpointCreated
|
|
1951
1246
|
}
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
});
|
|
1964
|
-
};
|
|
1965
|
-
return { authorize, isAuthorizing, error };
|
|
1966
|
-
}
|
|
1967
|
-
function parseOAuthCompletionPayload(data, startedAt) {
|
|
1968
|
-
if (!data || typeof data !== "object") return null;
|
|
1969
|
-
const payload = data;
|
|
1970
|
-
if (payload.type !== "oauth-complete") return null;
|
|
1971
|
-
if (payload.completedAt && payload.completedAt < startedAt) return null;
|
|
1972
|
-
return payload;
|
|
1247
|
+
),
|
|
1248
|
+
newEndpoint && newEndpoint.key && /* @__PURE__ */ jsx(
|
|
1249
|
+
WebhookUrlDisplayModal,
|
|
1250
|
+
{
|
|
1251
|
+
opened: true,
|
|
1252
|
+
endpoint: newEndpoint,
|
|
1253
|
+
webhookUrl: getWebhookUrl(apiUrl, newEndpoint.key),
|
|
1254
|
+
onClose: () => setNewEndpoint(null)
|
|
1255
|
+
}
|
|
1256
|
+
)
|
|
1257
|
+
] });
|
|
1973
1258
|
}
|
|
1974
|
-
function
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
}
|
|
1978
|
-
try {
|
|
1979
|
-
const url = new URL(origin);
|
|
1980
|
-
return url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
|
|
1981
|
-
} catch {
|
|
1982
|
-
return false;
|
|
1983
|
-
}
|
|
1259
|
+
function diffRoles(current, next) {
|
|
1260
|
+
const adds = next.filter((id) => !current.includes(id));
|
|
1261
|
+
const removes = current.filter((id) => !next.includes(id));
|
|
1262
|
+
return { adds, removes };
|
|
1984
1263
|
}
|
|
1985
|
-
function
|
|
1986
|
-
const [
|
|
1987
|
-
const [nameError, setNameError] = useState(null);
|
|
1988
|
-
const [fieldValues, setFieldValues] = useState({});
|
|
1989
|
-
const { authorize, isAuthorizing, error: oauthError } = useOAuthFlow({ apiUrl });
|
|
1990
|
-
const updateMutation = useUpdateCredential();
|
|
1264
|
+
function MemberAccessModal({ opened, onClose, orgId, member, defaultTab = "roles" }) {
|
|
1265
|
+
const [activeTab, setActiveTab] = useState(defaultTab);
|
|
1991
1266
|
useEffect(() => {
|
|
1992
|
-
if (
|
|
1993
|
-
|
|
1994
|
-
}
|
|
1995
|
-
}, [credential]);
|
|
1996
|
-
if (!credential) return null;
|
|
1997
|
-
const credentialType = credential.type || "api-key";
|
|
1998
|
-
const schema = getCredentialSchema(credentialType);
|
|
1999
|
-
const provider = credential.provider;
|
|
2000
|
-
const isOAuth = !!provider || schema.type === "oauth";
|
|
2001
|
-
const hasMetadataChanged = credentialName !== credential.name;
|
|
2002
|
-
const isPending = isAuthorizing || updateMutation.isPending;
|
|
1267
|
+
if (opened) setActiveTab(defaultTab);
|
|
1268
|
+
}, [opened, defaultTab]);
|
|
2003
1269
|
const handleClose = () => {
|
|
2004
|
-
|
|
2005
|
-
onClose();
|
|
2006
|
-
}
|
|
2007
|
-
};
|
|
2008
|
-
const handleOAuthReconnect = async () => {
|
|
2009
|
-
if (!provider) return;
|
|
2010
|
-
try {
|
|
2011
|
-
await authorize(provider, credential.name);
|
|
2012
|
-
onClose();
|
|
2013
|
-
} catch (err) {
|
|
2014
|
-
console.error("Reconnect failed:", err);
|
|
2015
|
-
}
|
|
2016
|
-
};
|
|
2017
|
-
const handleSaveChanges = async () => {
|
|
2018
|
-
const nameValidation = CredentialSchemas.UpdateRequest.shape.name.safeParse(credentialName);
|
|
2019
|
-
if (!nameValidation.success) {
|
|
2020
|
-
setNameError(nameValidation.error.issues[0]?.message || "Invalid credential name");
|
|
2021
|
-
return;
|
|
2022
|
-
}
|
|
2023
|
-
setNameError(null);
|
|
2024
|
-
const updateData = { name: credentialName };
|
|
2025
|
-
const validationResult = CredentialSchemas.UpdateRequest.safeParse(updateData);
|
|
2026
|
-
if (!validationResult.success) {
|
|
2027
|
-
console.error("Validation failed:", validationResult.error);
|
|
2028
|
-
return;
|
|
2029
|
-
}
|
|
2030
|
-
try {
|
|
2031
|
-
await updateMutation.mutateAsync({
|
|
2032
|
-
credentialId: credential.id,
|
|
2033
|
-
updates: updateData
|
|
2034
|
-
});
|
|
2035
|
-
onClose();
|
|
2036
|
-
} catch (err) {
|
|
2037
|
-
console.error("Save changes failed:", err);
|
|
2038
|
-
}
|
|
1270
|
+
onClose();
|
|
2039
1271
|
};
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
1272
|
+
if (!member) return null;
|
|
1273
|
+
return /* @__PURE__ */ jsx(CustomModal, { opened, onClose: handleClose, size: "xl", children: /* @__PURE__ */ jsxs(Stack, { p: "md", gap: "md", children: [
|
|
1274
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
|
|
1275
|
+
/* @__PURE__ */ jsx(Title, { order: 3, children: "Member access" }),
|
|
1276
|
+
/* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
|
|
1277
|
+
/* @__PURE__ */ jsx(Text, { fw: 500, children: member.name }),
|
|
1278
|
+
/* @__PURE__ */ jsx(Badge, { variant: "light", size: "sm", children: member.role })
|
|
1279
|
+
] }),
|
|
1280
|
+
/* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: member.email })
|
|
1281
|
+
] }),
|
|
1282
|
+
activeTab === "roles" && /* @__PURE__ */ jsx(RolesTabPanel, { orgId, member, onSaved: onClose })
|
|
1283
|
+
] }) });
|
|
1284
|
+
}
|
|
1285
|
+
function RolesTabPanel({ orgId, member, onSaved }) {
|
|
1286
|
+
const { data: rolesData, isLoading: rolesLoading } = useOrgRoles(orgId ?? void 0);
|
|
1287
|
+
const assignMutation = useAssignRole();
|
|
1288
|
+
const revokeMutation = useRevokeRole();
|
|
1289
|
+
const canManagePrivileged = useAccess(AccessKeys.rolesManage).allowed;
|
|
1290
|
+
const allRoles = rolesData?.roles ?? [];
|
|
1291
|
+
const currentRoleIds = useMemo(() => {
|
|
1292
|
+
const matched = allRoles.find((r) => r.slug === member.role);
|
|
1293
|
+
return matched ? [matched.id] : [];
|
|
1294
|
+
}, [allRoles, member.role]);
|
|
1295
|
+
const [selectedIds, setSelectedIds] = useState(currentRoleIds);
|
|
1296
|
+
const [saving, setSaving] = useState(false);
|
|
1297
|
+
useEffect(() => {
|
|
1298
|
+
setSelectedIds(currentRoleIds);
|
|
1299
|
+
}, [currentRoleIds, member.id]);
|
|
1300
|
+
const options = allRoles.map((r) => ({
|
|
1301
|
+
value: r.id,
|
|
1302
|
+
label: r.name + (r.is_system ? " (built-in)" : ""),
|
|
1303
|
+
disabled: !canManagePrivileged && (r.slug === "owner" || r.slug === "admin")
|
|
1304
|
+
}));
|
|
1305
|
+
const hasZeroRoles = selectedIds.length === 0;
|
|
1306
|
+
const isUnchanged = selectedIds.length === currentRoleIds.length && selectedIds.every((id) => currentRoleIds.includes(id));
|
|
1307
|
+
async function handleSave() {
|
|
1308
|
+
if (hasZeroRoles || isUnchanged) return;
|
|
1309
|
+
setSaving(true);
|
|
2043
1310
|
try {
|
|
2044
|
-
const
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
await updateMutation.mutateAsync({
|
|
2052
|
-
credentialId: credential.id,
|
|
2053
|
-
updates: updateData
|
|
2054
|
-
});
|
|
2055
|
-
onClose();
|
|
1311
|
+
const { adds, removes } = diffRoles(currentRoleIds, selectedIds);
|
|
1312
|
+
await Promise.all([
|
|
1313
|
+
...adds.map((roleId) => assignMutation.mutateAsync({ membershipId: member.id, roleId })),
|
|
1314
|
+
...removes.map((roleId) => revokeMutation.mutateAsync({ membershipId: member.id, roleId }))
|
|
1315
|
+
]);
|
|
1316
|
+
showSuccessNotification("Roles updated");
|
|
1317
|
+
onSaved();
|
|
2056
1318
|
} catch (err) {
|
|
2057
|
-
|
|
1319
|
+
showAuthError(err);
|
|
1320
|
+
} finally {
|
|
1321
|
+
setSaving(false);
|
|
2058
1322
|
}
|
|
2059
|
-
}
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
};
|
|
2063
|
-
const handleNameChange = (value) => {
|
|
2064
|
-
setCredentialName(value);
|
|
2065
|
-
if (nameError) setNameError(null);
|
|
2066
|
-
};
|
|
2067
|
-
const hasFieldValues = Object.values(fieldValues).some((v) => v.trim());
|
|
2068
|
-
return /* @__PURE__ */ jsx(CustomModal, { opened: true, onClose: handleClose, loading: isPending, children: /* @__PURE__ */ jsxs(Stack, { children: [
|
|
2069
|
-
/* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
|
|
2070
|
-
/* @__PURE__ */ jsx(IconSettings, { size: 24 }),
|
|
2071
|
-
/* @__PURE__ */ jsx(Title, { order: 3, children: isOAuth ? "Manage OAuth Credential" : "Edit API Key" })
|
|
2072
|
-
] }),
|
|
2073
|
-
/* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
|
|
2074
|
-
/* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, children: "Type:" }),
|
|
2075
|
-
/* @__PURE__ */ jsx(Badge, { variant: "light", size: "sm", children: schema.label })
|
|
2076
|
-
] }),
|
|
1323
|
+
}
|
|
1324
|
+
return /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
|
|
1325
|
+
/* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "Select the roles assigned to this member. Each role grants a bundle of permissions; the member's effective permissions are the union of all assigned roles." }),
|
|
2077
1326
|
/* @__PURE__ */ jsx(
|
|
2078
|
-
|
|
1327
|
+
MultiSelect,
|
|
2079
1328
|
{
|
|
2080
|
-
|
|
2081
|
-
value:
|
|
2082
|
-
onChange:
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
1329
|
+
data: options,
|
|
1330
|
+
value: selectedIds,
|
|
1331
|
+
onChange: setSelectedIds,
|
|
1332
|
+
placeholder: rolesLoading ? "Loading roles..." : "Select roles",
|
|
1333
|
+
disabled: saving || rolesLoading,
|
|
1334
|
+
searchable: true,
|
|
1335
|
+
clearable: false
|
|
2087
1336
|
}
|
|
2088
1337
|
),
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
/* @__PURE__ */ jsx(Button, { variant: "light", onClick: handleClose, disabled: isPending, children: "Cancel" }),
|
|
2094
|
-
/* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
|
|
2095
|
-
/* @__PURE__ */ jsx(
|
|
2096
|
-
Button,
|
|
2097
|
-
{
|
|
2098
|
-
variant: "light",
|
|
2099
|
-
onClick: handleSaveChanges,
|
|
2100
|
-
loading: updateMutation.isPending,
|
|
2101
|
-
disabled: !hasMetadataChanged || isAuthorizing,
|
|
2102
|
-
children: "Save Changes"
|
|
2103
|
-
}
|
|
2104
|
-
),
|
|
2105
|
-
/* @__PURE__ */ jsx(
|
|
2106
|
-
Button,
|
|
2107
|
-
{
|
|
2108
|
-
color: "blue",
|
|
2109
|
-
onClick: handleOAuthReconnect,
|
|
2110
|
-
loading: isAuthorizing,
|
|
2111
|
-
leftSection: /* @__PURE__ */ jsx(IconRefresh, { size: 16 }),
|
|
2112
|
-
disabled: updateMutation.isPending,
|
|
2113
|
-
children: "Reconnect OAuth"
|
|
2114
|
-
}
|
|
2115
|
-
)
|
|
2116
|
-
] })
|
|
2117
|
-
] })
|
|
1338
|
+
!canManagePrivileged && /* @__PURE__ */ jsxs(Alert, { color: "blue", variant: "light", children: [
|
|
1339
|
+
"Only owners (with ",
|
|
1340
|
+
/* @__PURE__ */ jsx("code", { children: "roles.manage" }),
|
|
1341
|
+
") can grant or revoke the owner / admin system roles."
|
|
2118
1342
|
] }),
|
|
2119
|
-
!
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
{
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
},
|
|
2133
|
-
field.key
|
|
2134
|
-
);
|
|
2135
|
-
}),
|
|
2136
|
-
updateMutation.error && /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconAlertCircle, { size: 16 }), color: "red", title: "Update Failed", children: "Failed to update credential" }),
|
|
2137
|
-
/* @__PURE__ */ jsxs(Group, { justify: "space-between", gap: "sm", children: [
|
|
2138
|
-
/* @__PURE__ */ jsx(Button, { variant: "light", onClick: handleClose, disabled: isPending, children: "Cancel" }),
|
|
2139
|
-
/* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
|
|
2140
|
-
/* @__PURE__ */ jsx(
|
|
2141
|
-
Button,
|
|
2142
|
-
{
|
|
2143
|
-
variant: "light",
|
|
2144
|
-
onClick: handleSaveChanges,
|
|
2145
|
-
loading: updateMutation.isPending,
|
|
2146
|
-
disabled: !hasMetadataChanged,
|
|
2147
|
-
children: "Save Name"
|
|
2148
|
-
}
|
|
2149
|
-
),
|
|
2150
|
-
/* @__PURE__ */ jsx(Button, { onClick: handleCredentialUpdate, loading: updateMutation.isPending, disabled: !hasFieldValues, children: "Save Credential" })
|
|
2151
|
-
] })
|
|
2152
|
-
] })
|
|
1343
|
+
!rolesLoading && hasZeroRoles && currentRoleIds.length > 0 && /* @__PURE__ */ jsx(Text, { size: "xs", c: "red", children: "At least one role is required for an active membership." }),
|
|
1344
|
+
/* @__PURE__ */ jsxs(Group, { justify: "flex-end", mt: "md", children: [
|
|
1345
|
+
/* @__PURE__ */ jsx(Button, { variant: "default", onClick: onSaved, disabled: saving, children: "Cancel" }),
|
|
1346
|
+
/* @__PURE__ */ jsx(
|
|
1347
|
+
Button,
|
|
1348
|
+
{
|
|
1349
|
+
leftSection: /* @__PURE__ */ jsx(IconCheck, { size: 14 }),
|
|
1350
|
+
loading: saving,
|
|
1351
|
+
disabled: hasZeroRoles || isUnchanged,
|
|
1352
|
+
onClick: handleSave,
|
|
1353
|
+
children: "Save roles"
|
|
1354
|
+
}
|
|
1355
|
+
)
|
|
2153
1356
|
] })
|
|
2154
|
-
] })
|
|
1357
|
+
] });
|
|
2155
1358
|
}
|
|
2156
|
-
|
|
2157
|
-
"
|
|
2158
|
-
|
|
2159
|
-
};
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
const handleConnect = async (credentialName) => {
|
|
2166
|
-
if (!selectedProviderId) return;
|
|
2167
|
-
try {
|
|
2168
|
-
await authorize(selectedProviderId, credentialName);
|
|
2169
|
-
setModalOpened(false);
|
|
2170
|
-
setSelectedProviderId(null);
|
|
2171
|
-
queryClient.invalidateQueries({ queryKey: ["credentials"] });
|
|
2172
|
-
} catch (err) {
|
|
2173
|
-
console.error("OAuth flow failed:", err);
|
|
2174
|
-
}
|
|
1359
|
+
function transformMemberForModal(member) {
|
|
1360
|
+
const firstName = member.user?.firstName || "";
|
|
1361
|
+
const lastName = member.user?.lastName || "";
|
|
1362
|
+
const name = firstName && lastName ? `${firstName} ${lastName}` : member.user?.email || "Unknown User";
|
|
1363
|
+
return {
|
|
1364
|
+
id: member.id,
|
|
1365
|
+
name,
|
|
1366
|
+
email: member.user?.email || "Unknown",
|
|
1367
|
+
role: member.role.slug
|
|
2175
1368
|
};
|
|
2176
|
-
|
|
2177
|
-
|
|
1369
|
+
}
|
|
1370
|
+
function OrgMembersList({ orgId }) {
|
|
1371
|
+
const { data: members, isLoading, error } = useOrganizationMembers(orgId);
|
|
1372
|
+
const [selectedMember, setSelectedMember] = useState(null);
|
|
1373
|
+
const [modalOpened, setModalOpened] = useState(false);
|
|
1374
|
+
const [defaultTab, setDefaultTab] = useState("roles");
|
|
1375
|
+
const openModal = (member, tab) => {
|
|
1376
|
+
setSelectedMember(member);
|
|
1377
|
+
setDefaultTab(tab);
|
|
2178
1378
|
setModalOpened(true);
|
|
2179
1379
|
};
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
1380
|
+
const handleModalClose = () => {
|
|
1381
|
+
setModalOpened(false);
|
|
1382
|
+
setSelectedMember(null);
|
|
1383
|
+
};
|
|
1384
|
+
if (isLoading) {
|
|
1385
|
+
return /* @__PURE__ */ jsx(Center, { py: "xl", children: /* @__PURE__ */ jsxs(Stack, { align: "center", gap: "sm", children: [
|
|
1386
|
+
/* @__PURE__ */ jsx(Loader, { size: "md" }),
|
|
1387
|
+
/* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "Loading members..." })
|
|
1388
|
+
] }) });
|
|
1389
|
+
}
|
|
1390
|
+
if (error) {
|
|
1391
|
+
return /* @__PURE__ */ jsx(APIErrorAlert, { error, title: "Error loading members" });
|
|
1392
|
+
}
|
|
1393
|
+
if (!members || members.length === 0) {
|
|
1394
|
+
return /* @__PURE__ */ jsx(EmptyState, { icon: IconUsers, title: "No members found" });
|
|
1395
|
+
}
|
|
2193
1396
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2194
|
-
/* @__PURE__ */ jsx(
|
|
2195
|
-
/* @__PURE__ */ jsx(
|
|
2196
|
-
/* @__PURE__ */ jsx(
|
|
2197
|
-
/* @__PURE__ */ jsx(
|
|
1397
|
+
/* @__PURE__ */ jsx(Table.ScrollContainer, { minWidth: 500, children: /* @__PURE__ */ jsxs(Table, { children: [
|
|
1398
|
+
/* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
|
|
1399
|
+
/* @__PURE__ */ jsx(Table.Th, { children: "Name" }),
|
|
1400
|
+
/* @__PURE__ */ jsx(Table.Th, { children: "Email" }),
|
|
1401
|
+
/* @__PURE__ */ jsx(Table.Th, { children: "Role" }),
|
|
1402
|
+
/* @__PURE__ */ jsx(Table.Th, { children: "Status" }),
|
|
1403
|
+
/* @__PURE__ */ jsx(Table.Th, { children: "Actions" })
|
|
2198
1404
|
] }) }),
|
|
2199
|
-
/* @__PURE__ */ jsx(
|
|
2200
|
-
const
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
1405
|
+
/* @__PURE__ */ jsx(Table.Tbody, { children: members.map((member) => {
|
|
1406
|
+
const firstName = member.user?.firstName || "";
|
|
1407
|
+
const lastName = member.user?.lastName || "";
|
|
1408
|
+
const displayName = firstName && lastName ? `${firstName} ${lastName}` : member.user?.email || "Unknown User";
|
|
1409
|
+
return /* @__PURE__ */ jsxs(Table.Tr, { children: [
|
|
1410
|
+
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { fw: 500, size: "sm", children: displayName }) }),
|
|
1411
|
+
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: member.user?.email || "Unknown" }) }),
|
|
1412
|
+
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(
|
|
1413
|
+
RoleBadge,
|
|
1414
|
+
{
|
|
1415
|
+
slug: member.role.slug,
|
|
1416
|
+
name: member.role.slug.charAt(0).toUpperCase() + member.role.slug.slice(1),
|
|
1417
|
+
isSystem: true
|
|
1418
|
+
}
|
|
1419
|
+
) }),
|
|
1420
|
+
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
|
|
1421
|
+
/* @__PURE__ */ jsx(MembershipStatusBadge, { status: member.status }),
|
|
1422
|
+
/* @__PURE__ */ jsx(ProvisioningStateBadge, { state: member.provisioningState })
|
|
2205
1423
|
] }) }),
|
|
2206
|
-
/* @__PURE__ */ jsx(
|
|
2207
|
-
] }
|
|
1424
|
+
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Tooltip, { label: "Edit member access", withArrow: true, children: /* @__PURE__ */ jsx(ActionIcon, { variant: "light", size: "sm", color: "gray", onClick: () => openModal(member, "roles"), children: /* @__PURE__ */ jsx(IconPencil, { size: 14 }) }) }) })
|
|
1425
|
+
] }, member.id);
|
|
2208
1426
|
}) })
|
|
2209
1427
|
] }) }),
|
|
2210
1428
|
/* @__PURE__ */ jsx(
|
|
2211
|
-
|
|
1429
|
+
MemberAccessModal,
|
|
2212
1430
|
{
|
|
2213
1431
|
opened: modalOpened,
|
|
2214
|
-
onClose:
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
provider: selectedProvider,
|
|
2219
|
-
onConnect: handleConnect,
|
|
2220
|
-
error,
|
|
2221
|
-
isLoading: isAuthorizing
|
|
1432
|
+
onClose: handleModalClose,
|
|
1433
|
+
orgId,
|
|
1434
|
+
member: selectedMember ? transformMemberForModal(selectedMember) : null,
|
|
1435
|
+
defaultTab
|
|
2222
1436
|
}
|
|
2223
1437
|
)
|
|
2224
1438
|
] });
|
|
2225
1439
|
}
|
|
1440
|
+
function OrganizationSettings({
|
|
1441
|
+
user,
|
|
1442
|
+
currentMembership,
|
|
1443
|
+
organizationId,
|
|
1444
|
+
isOrgAdmin
|
|
1445
|
+
}) {
|
|
1446
|
+
return /* @__PURE__ */ jsxs(Stack, { children: [
|
|
1447
|
+
/* @__PURE__ */ jsx(PageTitleCaption, { title: "Organization Settings", caption: "Manage your current organization" }),
|
|
1448
|
+
currentMembership?.organization && /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
|
|
1449
|
+
/* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
|
|
1450
|
+
/* @__PURE__ */ jsx(ThemeIcon, { size: "lg", variant: "light", color: "green", children: /* @__PURE__ */ jsx(IconBuilding, { size: 18 }) }),
|
|
1451
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 2, style: { flex: 1 }, children: [
|
|
1452
|
+
/* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Current Organization" }),
|
|
1453
|
+
/* @__PURE__ */ jsx(Text, { size: "lg", fw: 600, style: { fontFamily: "var(--elevasis-font-family-subtitle)" }, children: currentMembership.organization.name })
|
|
1454
|
+
] }),
|
|
1455
|
+
/* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
|
|
1456
|
+
/* @__PURE__ */ jsx(Code, { children: currentMembership.organization.id }),
|
|
1457
|
+
/* @__PURE__ */ jsx(CopyButton, { value: currentMembership.organization.id, timeout: 2e3, children: ({ copied, copy }) => /* @__PURE__ */ jsx(Tooltip, { label: copied ? "Copied!" : "Copy ID", withArrow: true, position: "top", children: /* @__PURE__ */ jsx(
|
|
1458
|
+
ActionIcon,
|
|
1459
|
+
{
|
|
1460
|
+
color: copied ? "teal" : "gray",
|
|
1461
|
+
variant: copied ? "filled" : "subtle",
|
|
1462
|
+
onClick: copy,
|
|
1463
|
+
size: "sm",
|
|
1464
|
+
children: copied ? /* @__PURE__ */ jsx(IconCheck, { size: 14 }) : /* @__PURE__ */ jsx(IconCopy, { size: 14 })
|
|
1465
|
+
}
|
|
1466
|
+
) }) })
|
|
1467
|
+
] })
|
|
1468
|
+
] }),
|
|
1469
|
+
user && /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
|
|
1470
|
+
/* @__PURE__ */ jsx(ThemeIcon, { size: "lg", variant: "light", color: "blue", children: /* @__PURE__ */ jsx(IconUsers, { size: 18 }) }),
|
|
1471
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 2, style: { flex: 1 }, children: [
|
|
1472
|
+
/* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Signed in as" }),
|
|
1473
|
+
/* @__PURE__ */ jsxs(Text, { size: "lg", fw: 600, style: { fontFamily: "var(--elevasis-font-family-subtitle)" }, children: [
|
|
1474
|
+
user.firstName,
|
|
1475
|
+
" ",
|
|
1476
|
+
user.lastName,
|
|
1477
|
+
" \u2022 ",
|
|
1478
|
+
user.email
|
|
1479
|
+
] })
|
|
1480
|
+
] }),
|
|
1481
|
+
/* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
|
|
1482
|
+
/* @__PURE__ */ jsx(Code, { children: currentMembership.userId }),
|
|
1483
|
+
/* @__PURE__ */ jsx(CopyButton, { value: currentMembership.userId, timeout: 2e3, children: ({ copied, copy }) => /* @__PURE__ */ jsx(Tooltip, { label: copied ? "Copied!" : "Copy User ID", withArrow: true, position: "top", children: /* @__PURE__ */ jsx(
|
|
1484
|
+
ActionIcon,
|
|
1485
|
+
{
|
|
1486
|
+
color: copied ? "teal" : "gray",
|
|
1487
|
+
variant: copied ? "filled" : "subtle",
|
|
1488
|
+
onClick: copy,
|
|
1489
|
+
size: "sm",
|
|
1490
|
+
children: copied ? /* @__PURE__ */ jsx(IconCheck, { size: 14 }) : /* @__PURE__ */ jsx(IconCopy, { size: 14 })
|
|
1491
|
+
}
|
|
1492
|
+
) }) })
|
|
1493
|
+
] })
|
|
1494
|
+
] })
|
|
1495
|
+
] }) }),
|
|
1496
|
+
isOrgAdmin && organizationId && /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { children: [
|
|
1497
|
+
/* @__PURE__ */ jsx(
|
|
1498
|
+
CardHeader,
|
|
1499
|
+
{
|
|
1500
|
+
icon: /* @__PURE__ */ jsx(IconUsers, { size: 18 }),
|
|
1501
|
+
title: "Manage Members",
|
|
1502
|
+
subtitle: "Configure feature access for organization members."
|
|
1503
|
+
}
|
|
1504
|
+
),
|
|
1505
|
+
/* @__PURE__ */ jsx(OrgMembersList, { orgId: organizationId })
|
|
1506
|
+
] }) })
|
|
1507
|
+
] });
|
|
1508
|
+
}
|
|
1509
|
+
function capitalize(value) {
|
|
1510
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
1511
|
+
}
|
|
1512
|
+
function MyRolesPage({ onManageMembers, onManageOrganizationRoles }) {
|
|
1513
|
+
const { currentMembership } = useOrganization();
|
|
1514
|
+
const orgName = currentMembership?.organization?.name ?? "this organization";
|
|
1515
|
+
const access = useAccess("platform");
|
|
1516
|
+
const { permissions, isReady: permissionsReady, isPlatformAdmin } = access;
|
|
1517
|
+
const catalogQuery = usePermissionCatalog();
|
|
1518
|
+
const catalogMap = new Map(
|
|
1519
|
+
(catalogQuery.data?.permissions ?? []).map((permission) => [permission.key, permission.description])
|
|
1520
|
+
);
|
|
1521
|
+
const roleSlug = currentMembership?.role?.slug ?? null;
|
|
1522
|
+
const isLoading = !permissionsReady || catalogQuery.isLoading;
|
|
1523
|
+
const showManagePanel = Boolean(onManageMembers || onManageOrganizationRoles);
|
|
1524
|
+
const groupedPermissions = permissions.reduce((groups, key) => {
|
|
1525
|
+
const domain = key.split(".")[0] ?? "general";
|
|
1526
|
+
groups[domain] ??= [];
|
|
1527
|
+
groups[domain].push(key);
|
|
1528
|
+
return groups;
|
|
1529
|
+
}, {});
|
|
1530
|
+
return /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
|
|
1531
|
+
/* @__PURE__ */ jsx(
|
|
1532
|
+
PageTitleCaption,
|
|
1533
|
+
{
|
|
1534
|
+
title: "My Roles",
|
|
1535
|
+
caption: `Roles assigned to you in ${orgName} and what each one lets you do.`
|
|
1536
|
+
}
|
|
1537
|
+
),
|
|
1538
|
+
/* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { children: [
|
|
1539
|
+
/* @__PURE__ */ jsx(
|
|
1540
|
+
CardHeader,
|
|
1541
|
+
{
|
|
1542
|
+
icon: /* @__PURE__ */ jsx(IconShieldLock, { size: 18 }),
|
|
1543
|
+
title: "Your roles",
|
|
1544
|
+
subtitle: `Roles assigned to your membership in ${orgName}.`
|
|
1545
|
+
}
|
|
1546
|
+
),
|
|
1547
|
+
/* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
|
|
1548
|
+
roleSlug ? /* @__PURE__ */ jsx(RoleBadge, { slug: roleSlug, name: capitalize(roleSlug), isSystem: true }) : /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "No roles assigned yet." }),
|
|
1549
|
+
isPlatformAdmin && /* @__PURE__ */ jsx(Badge, { color: "grape", variant: "light", leftSection: /* @__PURE__ */ jsx(IconStar, { size: 12 }), children: "Platform admin" })
|
|
1550
|
+
] }),
|
|
1551
|
+
isPlatformAdmin && /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Platform admin bypasses every org-level permission check; you have full access regardless of role." })
|
|
1552
|
+
] }) }),
|
|
1553
|
+
/* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { children: [
|
|
1554
|
+
/* @__PURE__ */ jsx(
|
|
1555
|
+
CardHeader,
|
|
1556
|
+
{
|
|
1557
|
+
icon: /* @__PURE__ */ jsx(IconCheck, { size: 18 }),
|
|
1558
|
+
title: "What you can do",
|
|
1559
|
+
subtitle: isPlatformAdmin ? "Showing all platform permissions (platform admin bypass)." : "Permissions granted by your assigned roles."
|
|
1560
|
+
}
|
|
1561
|
+
),
|
|
1562
|
+
isLoading ? /* @__PURE__ */ jsx(Center, { children: /* @__PURE__ */ jsx(Loader, {}) }) : permissions.length === 0 ? /* @__PURE__ */ jsx(Alert, { color: "yellow", children: "You don't have any permissions in this organization yet. Ask an admin to assign you a role." }) : /* @__PURE__ */ jsx(Stack, { gap: "md", children: Object.entries(groupedPermissions).map(([domain, keys]) => /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
|
|
1563
|
+
/* @__PURE__ */ jsx(Text, { size: "sm", fw: 600, children: capitalize(domain) }),
|
|
1564
|
+
keys.map((key) => /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
|
|
1565
|
+
/* @__PURE__ */ jsx(IconCheck, { size: 14, color: "var(--color-success)" }),
|
|
1566
|
+
/* @__PURE__ */ jsx(Code, { children: key }),
|
|
1567
|
+
/* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: catalogMap.get(key) ?? "" })
|
|
1568
|
+
] }, key))
|
|
1569
|
+
] }, domain)) })
|
|
1570
|
+
] }) }),
|
|
1571
|
+
showManagePanel && /* @__PURE__ */ jsx(AccessGuard, { accessKey: AccessKeys.membersManage, children: /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { children: [
|
|
1572
|
+
/* @__PURE__ */ jsx(
|
|
1573
|
+
CardHeader,
|
|
1574
|
+
{
|
|
1575
|
+
icon: /* @__PURE__ */ jsx(IconUserCog, { size: 18 }),
|
|
1576
|
+
title: "Manage roles & members",
|
|
1577
|
+
subtitle: "Assign roles to members, create custom roles, and edit permission sets."
|
|
1578
|
+
}
|
|
1579
|
+
),
|
|
1580
|
+
/* @__PURE__ */ jsxs(Group, { children: [
|
|
1581
|
+
onManageMembers ? /* @__PURE__ */ jsx(Button, { variant: "light", leftSection: /* @__PURE__ */ jsx(IconUsersGroup, { size: 14 }), onClick: onManageMembers, children: "Members" }) : null,
|
|
1582
|
+
onManageOrganizationRoles ? /* @__PURE__ */ jsx(AccessGuard, { accessKey: AccessKeys.organizationManage, children: /* @__PURE__ */ jsx(
|
|
1583
|
+
Button,
|
|
1584
|
+
{
|
|
1585
|
+
variant: "light",
|
|
1586
|
+
leftSection: /* @__PURE__ */ jsx(IconShieldLock, { size: 14 }),
|
|
1587
|
+
onClick: onManageOrganizationRoles,
|
|
1588
|
+
children: "Organization roles"
|
|
1589
|
+
}
|
|
1590
|
+
) }) : null
|
|
1591
|
+
] })
|
|
1592
|
+
] }) }) })
|
|
1593
|
+
] });
|
|
1594
|
+
}
|
|
2226
1595
|
|
|
2227
|
-
export { AccountSettings, AppearanceSettings,
|
|
1596
|
+
export { AccountSettings, AppearanceSettings, CreateWebhookEndpointModal, EditWebhookEndpointModal, MemberAccessModal, MembershipStatusBadge, MyRolesPage, OAuthConnectModal, OAuthIntegrationsCard, OrgMembersList, OrganizationMembershipsList, OrganizationSettings, ProvisioningStateBadge, WebhookEndpointList, WebhookEndpointSettings, WebhookUrlDisplayModal, showAuthError };
|