@strapi-community/plugin-io 1.0.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.
@@ -0,0 +1,1409 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useState, useRef, useEffect } from "react";
3
+ import { Box, Typography, Flex, Button, Grid, Field, TextInput, Badge, Divider, NumberInput, Toggle, Accordion, Checkbox, Main } from "@strapi/design-system";
4
+ import { Download, Upload, Check } from "@strapi/icons";
5
+ import { useFetchClient, useNotification } from "@strapi/strapi/admin";
6
+ import { u as useIntl } from "./index-CEh8vkxY.mjs";
7
+ import styled from "styled-components";
8
+ import { P as PLUGIN_ID } from "./index-DLXtrAtk.mjs";
9
+ const ResponsiveMain = styled(Main)`
10
+ & > div {
11
+ padding: 16px !important;
12
+
13
+ @media (min-width: 768px) {
14
+ padding: 32px !important;
15
+ }
16
+ }
17
+ `;
18
+ const ResponsiveHeader = styled(Box)`
19
+ padding-bottom: 16px !important;
20
+
21
+ @media (min-width: 768px) {
22
+ padding-bottom: 32px !important;
23
+ }
24
+ `;
25
+ const ResponsiveTitle = styled(Typography)`
26
+ font-size: 1.25rem !important;
27
+
28
+ @media (min-width: 768px) {
29
+ font-size: 2rem !important;
30
+ }
31
+ `;
32
+ const ResponsiveSubtitle = styled(Typography)`
33
+ font-size: 0.875rem !important;
34
+
35
+ @media (min-width: 768px) {
36
+ font-size: 1rem !important;
37
+ }
38
+ `;
39
+ const ResponsiveCard = styled(Box)`
40
+ padding: 16px !important;
41
+
42
+ @media (min-width: 768px) {
43
+ padding: 24px !important;
44
+ }
45
+ `;
46
+ const ResponsiveSection = styled(Box)`
47
+ padding-bottom: 16px !important;
48
+
49
+ @media (min-width: 768px) {
50
+ padding-bottom: 24px !important;
51
+ }
52
+ `;
53
+ const ResponsiveSectionTitle = styled(Typography)`
54
+ font-size: 1.125rem !important;
55
+ margin-bottom: 8px !important;
56
+
57
+ @media (min-width: 768px) {
58
+ font-size: 1.5rem !important;
59
+ margin-bottom: 12px !important;
60
+ }
61
+ `;
62
+ const ResponsiveButtonGroup = styled(Flex)`
63
+ flex-direction: column;
64
+ gap: 8px;
65
+ width: 100%;
66
+
67
+ @media (min-width: 640px) {
68
+ flex-direction: row;
69
+ width: auto;
70
+ }
71
+
72
+ button {
73
+ width: 100%;
74
+ justify-content: center;
75
+
76
+ @media (min-width: 640px) {
77
+ width: auto;
78
+ }
79
+ }
80
+ `;
81
+ const InputWrapper = styled.div`
82
+ width: 100%;
83
+
84
+ input, textarea, select {
85
+ width: 100% !important;
86
+ min-height: 48px !important;
87
+ font-size: 16px !important;
88
+ padding: 14px 16px !important;
89
+ box-sizing: border-box !important;
90
+
91
+ @media (min-width: 768px) {
92
+ min-height: 44px !important;
93
+ font-size: 15px !important;
94
+ padding: 12px 16px !important;
95
+ }
96
+ }
97
+
98
+ /* Number Input specific - remove ALL spinners */
99
+ input[type="number"] {
100
+ width: 100% !important;
101
+ min-height: 48px !important;
102
+ font-size: 16px !important;
103
+
104
+ /* Remove native browser spinner arrows */
105
+ -moz-appearance: textfield;
106
+
107
+ &::-webkit-outer-spin-button,
108
+ &::-webkit-inner-spin-button {
109
+ -webkit-appearance: none;
110
+ margin: 0;
111
+ }
112
+
113
+ @media (min-width: 768px) {
114
+ min-height: 44px !important;
115
+ font-size: 15px !important;
116
+ }
117
+ }
118
+
119
+ /* Strapi Input Component Override */
120
+ > div {
121
+ width: 100% !important;
122
+
123
+ > div {
124
+ width: 100% !important;
125
+
126
+ input {
127
+ width: 100% !important;
128
+ }
129
+ }
130
+ }
131
+
132
+ /* Strapi NumberInput Component - ALWAYS hide increment/decrement buttons */
133
+ button[aria-label="Increment"],
134
+ button[aria-label="Decrement"],
135
+ div button[aria-label="Increment"],
136
+ div button[aria-label="Decrement"],
137
+ & button[aria-label="Increment"],
138
+ & button[aria-label="Decrement"] {
139
+ display: none !important;
140
+ visibility: hidden !important;
141
+ opacity: 0 !important;
142
+ width: 0 !important;
143
+ height: 0 !important;
144
+ overflow: hidden !important;
145
+ pointer-events: none !important;
146
+ }
147
+
148
+ /* NumberInput Container - proper alignment */
149
+ > div {
150
+ display: flex !important;
151
+ align-items: center !important;
152
+ width: 100% !important;
153
+
154
+ /* Input field container */
155
+ > div:first-child {
156
+ flex: 1 !important;
157
+ min-width: 0 !important;
158
+ margin-right: 0 !important;
159
+
160
+ input {
161
+ width: 100% !important;
162
+ padding-right: 12px !important;
163
+ }
164
+ }
165
+
166
+ /* Spinner Buttons Container - ALWAYS HIDDEN */
167
+ > div:last-child {
168
+ display: none !important;
169
+ visibility: hidden !important;
170
+ width: 0 !important;
171
+ height: 0 !important;
172
+ overflow: hidden !important;
173
+ }
174
+ }
175
+ `;
176
+ const ResponsiveField = styled(Field.Root)`
177
+ width: 100%;
178
+
179
+ label {
180
+ font-size: 15px !important;
181
+ margin-bottom: 10px !important;
182
+ font-weight: 600 !important;
183
+ display: block !important;
184
+
185
+ @media (min-width: 768px) {
186
+ font-size: 14px !important;
187
+ margin-bottom: 8px !important;
188
+ }
189
+ }
190
+
191
+ /* Field hint */
192
+ > span:last-child {
193
+ font-size: 14px !important;
194
+ margin-top: 8px !important;
195
+
196
+ @media (min-width: 768px) {
197
+ font-size: 13px !important;
198
+ margin-top: 6px !important;
199
+ }
200
+ }
201
+ `;
202
+ const SettingsPage = () => {
203
+ const { get, put } = useFetchClient();
204
+ const { toggleNotification } = useNotification();
205
+ const { formatMessage } = useIntl();
206
+ const t = (id, defaultMessage) => formatMessage({ id: `${PLUGIN_ID}.${id}`, defaultMessage });
207
+ const [isLoading, setIsLoading] = useState(true);
208
+ const [isSaving, setIsSaving] = useState(false);
209
+ const [hasChanges, setHasChanges] = useState(false);
210
+ const [settings, setSettings] = useState({
211
+ enabled: true,
212
+ cors: {
213
+ origins: ["http://localhost:3000"]
214
+ },
215
+ connection: {
216
+ maxConnections: 1e3,
217
+ pingTimeout: 2e4,
218
+ pingInterval: 25e3,
219
+ connectionTimeout: 45e3
220
+ },
221
+ security: {
222
+ requireAuthentication: false,
223
+ rateLimiting: {
224
+ enabled: false,
225
+ maxEventsPerSecond: 10
226
+ },
227
+ ipWhitelist: [],
228
+ ipBlacklist: []
229
+ },
230
+ events: {
231
+ customEventNames: false,
232
+ includeRelations: false,
233
+ excludeFields: [],
234
+ onlyPublished: false
235
+ },
236
+ rooms: {
237
+ autoJoinByRole: {},
238
+ enablePrivateRooms: false
239
+ },
240
+ redis: {
241
+ enabled: false,
242
+ url: "redis://localhost:6379"
243
+ },
244
+ namespaces: {
245
+ enabled: false,
246
+ list: {}
247
+ },
248
+ middleware: {
249
+ enabled: false,
250
+ handlers: []
251
+ },
252
+ monitoring: {
253
+ enableConnectionLogging: true,
254
+ enableEventLogging: false,
255
+ maxEventLogSize: 100
256
+ },
257
+ entitySubscriptions: {
258
+ enabled: true,
259
+ maxSubscriptionsPerSocket: 100,
260
+ requireVerification: true,
261
+ allowedContentTypes: [],
262
+ enableMetrics: true
263
+ }
264
+ });
265
+ const [availableContentTypes, setAvailableContentTypes] = useState([]);
266
+ const [availableRoles, setAvailableRoles] = useState([]);
267
+ const fileInputRef = useRef(null);
268
+ const validateSettings = (settingsToValidate) => {
269
+ const errors = [];
270
+ settingsToValidate.cors?.origins?.forEach((origin) => {
271
+ try {
272
+ new URL(origin);
273
+ } catch {
274
+ errors.push(t("validation.invalidOrigin", `Invalid origin: ${origin}`));
275
+ }
276
+ });
277
+ if (settingsToValidate.connection?.pingTimeout <= 0) {
278
+ errors.push(t("validation.pingTimeoutPositive", "Ping timeout must be positive"));
279
+ }
280
+ if (settingsToValidate.connection?.pingInterval <= 0) {
281
+ errors.push(t("validation.pingIntervalPositive", "Ping interval must be positive"));
282
+ }
283
+ if (settingsToValidate.connection?.connectionTimeout <= 0) {
284
+ errors.push(t("validation.connectionTimeoutPositive", "Connection timeout must be positive"));
285
+ }
286
+ if (settingsToValidate.connection?.maxConnections <= 0) {
287
+ errors.push(t("validation.maxConnectionsPositive", "Max connections must be positive"));
288
+ }
289
+ if (settingsToValidate.redis?.enabled && !settingsToValidate.redis?.url) {
290
+ errors.push(t("validation.redisUrlRequired", "Redis URL is required when Redis is enabled"));
291
+ }
292
+ return errors;
293
+ };
294
+ const exportSettings = () => {
295
+ const dataStr = JSON.stringify(settings, null, 2);
296
+ const dataUri = "data:application/json;charset=utf-8," + encodeURIComponent(dataStr);
297
+ const exportFileDefaultName = `socket-io-settings-${Date.now()}.json`;
298
+ const linkElement = document.createElement("a");
299
+ linkElement.setAttribute("href", dataUri);
300
+ linkElement.setAttribute("download", exportFileDefaultName);
301
+ linkElement.click();
302
+ toggleNotification({
303
+ type: "success",
304
+ message: t("settings.exported", "Settings exported successfully!")
305
+ });
306
+ };
307
+ const importSettings = (event) => {
308
+ const file = event.target.files[0];
309
+ if (!file) return;
310
+ const reader = new FileReader();
311
+ reader.onload = (e) => {
312
+ try {
313
+ const imported = JSON.parse(e.target.result);
314
+ const errors = validateSettings(imported);
315
+ if (errors.length > 0) {
316
+ toggleNotification({
317
+ type: "danger",
318
+ message: `${t("settings.importError", "Import failed")}: ${errors.join(", ")}`
319
+ });
320
+ return;
321
+ }
322
+ updateSettings(imported);
323
+ toggleNotification({
324
+ type: "success",
325
+ message: t("settings.imported", "Settings imported successfully!")
326
+ });
327
+ } catch {
328
+ toggleNotification({
329
+ type: "danger",
330
+ message: t("settings.invalidJson", "Invalid settings file!")
331
+ });
332
+ }
333
+ };
334
+ reader.readAsText(file);
335
+ event.target.value = null;
336
+ };
337
+ const enableAllContentTypes = (roleType) => {
338
+ updateSettings((prev) => {
339
+ const contentTypes = {};
340
+ availableContentTypes.forEach((ct) => {
341
+ contentTypes[ct.uid] = { create: true, update: true, delete: true };
342
+ });
343
+ return {
344
+ ...prev,
345
+ rolePermissions: {
346
+ ...prev.rolePermissions,
347
+ [roleType]: {
348
+ ...prev.rolePermissions?.[roleType],
349
+ contentTypes
350
+ }
351
+ }
352
+ };
353
+ });
354
+ };
355
+ const disableAllContentTypes = (roleType) => {
356
+ updateSettings((prev) => ({
357
+ ...prev,
358
+ rolePermissions: {
359
+ ...prev.rolePermissions,
360
+ [roleType]: {
361
+ ...prev.rolePermissions?.[roleType],
362
+ contentTypes: {}
363
+ }
364
+ }
365
+ }));
366
+ };
367
+ useEffect(() => {
368
+ const fetchData = async () => {
369
+ try {
370
+ const [settingsRes, contentTypesRes, rolesRes] = await Promise.all([
371
+ get(`/${PLUGIN_ID}/settings`),
372
+ get(`/${PLUGIN_ID}/content-types`),
373
+ get(`/${PLUGIN_ID}/roles`)
374
+ ]);
375
+ if (settingsRes.data?.data) {
376
+ setSettings(settingsRes.data.data);
377
+ }
378
+ if (contentTypesRes.data?.data) {
379
+ setAvailableContentTypes(contentTypesRes.data.data);
380
+ }
381
+ if (rolesRes.data?.data) {
382
+ setAvailableRoles(rolesRes.data.data);
383
+ }
384
+ } catch (err) {
385
+ console.error("Error loading settings:", err);
386
+ toggleNotification({
387
+ type: "danger",
388
+ message: t("settings.loadError", "Error loading settings")
389
+ });
390
+ } finally {
391
+ setIsLoading(false);
392
+ }
393
+ };
394
+ fetchData();
395
+ }, [get, toggleNotification]);
396
+ const handleSave = async () => {
397
+ const errors = validateSettings(settings);
398
+ if (errors.length > 0) {
399
+ toggleNotification({
400
+ type: "danger",
401
+ message: `${t("validation.errors", "Validation errors")}: ${errors.join(", ")}`
402
+ });
403
+ return;
404
+ }
405
+ setIsSaving(true);
406
+ try {
407
+ await put(`/${PLUGIN_ID}/settings`, settings);
408
+ setHasChanges(false);
409
+ toggleNotification({
410
+ type: "success",
411
+ message: t("settings.success", "Settings saved successfully!")
412
+ });
413
+ } catch (err) {
414
+ console.error("Error saving settings:", err);
415
+ toggleNotification({
416
+ type: "danger",
417
+ message: t("settings.error", "Error saving settings")
418
+ });
419
+ } finally {
420
+ setIsSaving(false);
421
+ }
422
+ };
423
+ const updateSettings = (updater) => {
424
+ setSettings(updater);
425
+ setHasChanges(true);
426
+ };
427
+ const httpMethods = ["GET", "POST", "PUT", "DELETE", "PATCH"];
428
+ const [newOrigin, setNewOrigin] = useState("");
429
+ const [newNamespace, setNewNamespace] = useState("");
430
+ const addOrigin = () => {
431
+ if (newOrigin && !settings.cors?.origins?.includes(newOrigin)) {
432
+ updateSettings((prev) => ({
433
+ ...prev,
434
+ cors: {
435
+ ...prev.cors,
436
+ origins: [...prev.cors?.origins || [], newOrigin]
437
+ }
438
+ }));
439
+ setNewOrigin("");
440
+ }
441
+ };
442
+ const removeOrigin = (origin) => {
443
+ updateSettings((prev) => ({
444
+ ...prev,
445
+ cors: {
446
+ ...prev.cors,
447
+ origins: prev.cors?.origins?.filter((o) => o !== origin) || []
448
+ }
449
+ }));
450
+ };
451
+ const updateConnection = (key, value) => {
452
+ updateSettings((prev) => ({
453
+ ...prev,
454
+ connection: {
455
+ ...prev.connection,
456
+ [key]: parseInt(value) || 0
457
+ }
458
+ }));
459
+ };
460
+ const updateSecurity = (key, value) => {
461
+ updateSettings((prev) => ({
462
+ ...prev,
463
+ security: {
464
+ ...prev.security,
465
+ [key]: value
466
+ }
467
+ }));
468
+ };
469
+ const updateEvents = (key, value) => {
470
+ updateSettings((prev) => ({
471
+ ...prev,
472
+ events: {
473
+ ...prev.events,
474
+ [key]: value
475
+ }
476
+ }));
477
+ };
478
+ const updateMonitoring = (key, value) => {
479
+ updateSettings((prev) => ({
480
+ ...prev,
481
+ monitoring: {
482
+ ...prev.monitoring,
483
+ [key]: value
484
+ }
485
+ }));
486
+ };
487
+ const updateEntitySubscriptions = (key, value) => {
488
+ updateSettings((prev) => ({
489
+ ...prev,
490
+ entitySubscriptions: {
491
+ ...prev.entitySubscriptions,
492
+ [key]: value
493
+ }
494
+ }));
495
+ };
496
+ const updateRedis = (key, value) => {
497
+ updateSettings((prev) => ({
498
+ ...prev,
499
+ redis: {
500
+ ...prev.redis,
501
+ [key]: value
502
+ }
503
+ }));
504
+ };
505
+ const updateNamespaces = (key, value) => {
506
+ updateSettings((prev) => ({
507
+ ...prev,
508
+ namespaces: {
509
+ ...prev.namespaces,
510
+ [key]: value
511
+ }
512
+ }));
513
+ };
514
+ const addNamespace = () => {
515
+ const trimmed = newNamespace.trim();
516
+ console.log("addNamespace called, newNamespace:", trimmed);
517
+ console.log("current list:", settings.namespaces?.list);
518
+ if (!trimmed) {
519
+ console.log("Empty namespace name");
520
+ return;
521
+ }
522
+ const currentList = settings.namespaces?.list || {};
523
+ if (currentList[trimmed]) {
524
+ console.log("Namespace already exists");
525
+ return;
526
+ }
527
+ updateSettings((prev) => ({
528
+ ...prev,
529
+ namespaces: {
530
+ enabled: prev.namespaces?.enabled || false,
531
+ list: {
532
+ ...currentList,
533
+ [trimmed]: { requireAuth: false }
534
+ }
535
+ }
536
+ }));
537
+ setNewNamespace("");
538
+ console.log("Namespace added:", trimmed);
539
+ };
540
+ const removeNamespace = (namespace) => {
541
+ updateSettings((prev) => {
542
+ const newList = { ...prev.namespaces?.list };
543
+ delete newList[namespace];
544
+ return {
545
+ ...prev,
546
+ namespaces: {
547
+ ...prev.namespaces,
548
+ list: newList
549
+ }
550
+ };
551
+ });
552
+ };
553
+ if (isLoading) {
554
+ return /* @__PURE__ */ jsx(ResponsiveMain, { children: /* @__PURE__ */ jsx(Box, { padding: 8, children: /* @__PURE__ */ jsx(Typography, { children: "Loading..." }) }) });
555
+ }
556
+ return /* @__PURE__ */ jsx(ResponsiveMain, { children: /* @__PURE__ */ jsxs(Box, { padding: 8, background: "neutral100", children: [
557
+ /* @__PURE__ */ jsx(ResponsiveHeader, { children: /* @__PURE__ */ jsxs(Flex, { direction: { base: "column", tablet: "row" }, justifyContent: "space-between", alignItems: { base: "flex-start", tablet: "center" }, gap: 3, children: [
558
+ /* @__PURE__ */ jsxs(Box, { children: [
559
+ /* @__PURE__ */ jsxs(ResponsiveTitle, { variant: "alpha", as: "h1", children: [
560
+ t("plugin.name", "Socket.IO"),
561
+ " ",
562
+ t("settings.title", "Settings")
563
+ ] }),
564
+ /* @__PURE__ */ jsx(ResponsiveSubtitle, { variant: "epsilon", textColor: "neutral600", children: t("settings.description", "Configure the Socket.IO connection for real-time events") })
565
+ ] }),
566
+ /* @__PURE__ */ jsxs(ResponsiveButtonGroup, { children: [
567
+ /* @__PURE__ */ jsx(
568
+ Button,
569
+ {
570
+ variant: "secondary",
571
+ startIcon: /* @__PURE__ */ jsx(Download, {}),
572
+ onClick: exportSettings,
573
+ size: "S",
574
+ children: t("settings.export", "Export")
575
+ }
576
+ ),
577
+ /* @__PURE__ */ jsx(
578
+ Button,
579
+ {
580
+ variant: "secondary",
581
+ startIcon: /* @__PURE__ */ jsx(Upload, {}),
582
+ onClick: () => fileInputRef.current?.click(),
583
+ size: "S",
584
+ children: t("settings.import", "Import")
585
+ }
586
+ ),
587
+ /* @__PURE__ */ jsx(
588
+ "input",
589
+ {
590
+ ref: fileInputRef,
591
+ type: "file",
592
+ accept: ".json",
593
+ onChange: importSettings,
594
+ style: { display: "none" }
595
+ }
596
+ ),
597
+ /* @__PURE__ */ jsx(
598
+ Button,
599
+ {
600
+ onClick: handleSave,
601
+ loading: isSaving,
602
+ startIcon: /* @__PURE__ */ jsx(Check, {}),
603
+ disabled: !hasChanges,
604
+ size: "S",
605
+ children: hasChanges ? t("settings.saveAndApply", "Save & Apply") : t("settings.saved", "Saved")
606
+ }
607
+ )
608
+ ] })
609
+ ] }) }),
610
+ /* @__PURE__ */ jsxs(ResponsiveCard, { background: "neutral0", shadow: "filterShadow", hasRadius: true, children: [
611
+ /* @__PURE__ */ jsxs(ResponsiveSection, { children: [
612
+ /* @__PURE__ */ jsx(ResponsiveSectionTitle, { variant: "delta", as: "h2", children: t("cors.title", "CORS Origins") }),
613
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: t("cors.description", "Configure which frontend URLs are allowed to connect") })
614
+ ] }),
615
+ /* @__PURE__ */ jsx(Grid.Root, { gap: 4, children: /* @__PURE__ */ jsx(Grid.Item, { col: 12, children: /* @__PURE__ */ jsxs(ResponsiveField, { children: [
616
+ /* @__PURE__ */ jsx(Field.Label, { children: t("cors.origins", "Allowed Origins") }),
617
+ /* @__PURE__ */ jsxs(Flex, { direction: { base: "column", tablet: "row" }, gap: 2, paddingBottom: 2, children: [
618
+ /* @__PURE__ */ jsx(InputWrapper, { style: { flex: 1, width: "100%" }, children: /* @__PURE__ */ jsx(
619
+ TextInput,
620
+ {
621
+ placeholder: "http://localhost:3000",
622
+ value: newOrigin,
623
+ onChange: (e) => setNewOrigin(e.target.value),
624
+ onKeyPress: (e) => e.key === "Enter" && addOrigin()
625
+ }
626
+ ) }),
627
+ /* @__PURE__ */ jsx(Button, { onClick: addOrigin, size: "L", style: { width: "100%", maxWidth: "200px", minHeight: "44px" }, children: t("cors.add", "Add") })
628
+ ] }),
629
+ /* @__PURE__ */ jsx(Flex, { gap: 2, wrap: "wrap", paddingTop: 2, children: settings.cors?.origins?.map((origin) => /* @__PURE__ */ jsxs(Badge, { onClick: () => removeOrigin(origin), style: { cursor: "pointer", fontSize: "13px", padding: "6px 12px" }, children: [
630
+ origin,
631
+ " ✕"
632
+ ] }, origin)) }),
633
+ /* @__PURE__ */ jsx(Field.Hint, { children: t("cors.originsHint", "Add multiple frontend URLs that can connect") })
634
+ ] }) }) }),
635
+ /* @__PURE__ */ jsx(Box, { paddingTop: 4, paddingBottom: 2, children: /* @__PURE__ */ jsx(Divider, {}) }),
636
+ /* @__PURE__ */ jsxs(ResponsiveSection, { children: [
637
+ /* @__PURE__ */ jsx(ResponsiveSectionTitle, { variant: "delta", as: "h2", children: t("connection.title", "Connection Settings") }),
638
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: t("connection.description", "Configure connection limits and timeouts") })
639
+ ] }),
640
+ /* @__PURE__ */ jsxs(Grid.Root, { gap: 3, children: [
641
+ /* @__PURE__ */ jsx(Grid.Item, { col: 6, s: 12, children: /* @__PURE__ */ jsxs(ResponsiveField, { children: [
642
+ /* @__PURE__ */ jsx(Field.Label, { children: t("connection.maxConnections", "Max Connections") }),
643
+ /* @__PURE__ */ jsx(InputWrapper, { children: /* @__PURE__ */ jsx(
644
+ NumberInput,
645
+ {
646
+ value: settings.connection?.maxConnections || 1e3,
647
+ onValueChange: (value) => updateConnection("maxConnections", value)
648
+ }
649
+ ) })
650
+ ] }) }),
651
+ /* @__PURE__ */ jsx(Grid.Item, { col: 6, s: 12, children: /* @__PURE__ */ jsxs(ResponsiveField, { children: [
652
+ /* @__PURE__ */ jsx(Field.Label, { children: t("connection.pingTimeout", "Ping Timeout (ms)") }),
653
+ /* @__PURE__ */ jsx(InputWrapper, { children: /* @__PURE__ */ jsx(
654
+ NumberInput,
655
+ {
656
+ value: settings.connection?.pingTimeout || 2e4,
657
+ onValueChange: (value) => updateConnection("pingTimeout", value)
658
+ }
659
+ ) })
660
+ ] }) }),
661
+ /* @__PURE__ */ jsx(Grid.Item, { col: 6, s: 12, children: /* @__PURE__ */ jsxs(ResponsiveField, { children: [
662
+ /* @__PURE__ */ jsx(Field.Label, { children: t("connection.pingInterval", "Ping Interval (ms)") }),
663
+ /* @__PURE__ */ jsx(InputWrapper, { children: /* @__PURE__ */ jsx(
664
+ NumberInput,
665
+ {
666
+ value: settings.connection?.pingInterval || 25e3,
667
+ onValueChange: (value) => updateConnection("pingInterval", value)
668
+ }
669
+ ) })
670
+ ] }) }),
671
+ /* @__PURE__ */ jsx(Grid.Item, { col: 6, s: 12, children: /* @__PURE__ */ jsxs(ResponsiveField, { children: [
672
+ /* @__PURE__ */ jsx(Field.Label, { children: t("connection.connectionTimeout", "Connection Timeout (ms)") }),
673
+ /* @__PURE__ */ jsx(InputWrapper, { children: /* @__PURE__ */ jsx(
674
+ NumberInput,
675
+ {
676
+ value: settings.connection?.connectionTimeout || 45e3,
677
+ onValueChange: (value) => updateConnection("connectionTimeout", value)
678
+ }
679
+ ) })
680
+ ] }) })
681
+ ] }),
682
+ /* @__PURE__ */ jsx(Box, { paddingTop: 4, paddingBottom: 2, children: /* @__PURE__ */ jsx(Divider, {}) }),
683
+ /* @__PURE__ */ jsxs(ResponsiveSection, { children: [
684
+ /* @__PURE__ */ jsx(ResponsiveSectionTitle, { variant: "delta", as: "h2", children: t("security.title", "Security Settings") }),
685
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: t("security.description", "Configure authentication and rate limiting") })
686
+ ] }),
687
+ /* @__PURE__ */ jsxs(Grid.Root, { gap: 3, children: [
688
+ /* @__PURE__ */ jsx(Grid.Item, { col: 6, s: 12, children: /* @__PURE__ */ jsx(
689
+ Box,
690
+ {
691
+ padding: 4,
692
+ background: settings.security?.requireAuthentication ? "success100" : "neutral0",
693
+ hasRadius: true,
694
+ style: {
695
+ cursor: "pointer",
696
+ border: `2px solid ${settings.security?.requireAuthentication ? "#5cb176" : "#dcdce4"}`,
697
+ transition: "all 0.2s ease",
698
+ minHeight: "110px",
699
+ display: "flex",
700
+ alignItems: "center"
701
+ },
702
+ onClick: () => updateSecurity("requireAuthentication", !settings.security?.requireAuthentication),
703
+ children: /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "center", style: { width: "100%" }, children: [
704
+ /* @__PURE__ */ jsx(
705
+ Toggle,
706
+ {
707
+ checked: settings.security?.requireAuthentication || false,
708
+ onChange: (e) => updateSecurity("requireAuthentication", e.target.checked)
709
+ }
710
+ ),
711
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", gap: 1, children: [
712
+ /* @__PURE__ */ jsx(
713
+ Typography,
714
+ {
715
+ variant: "omega",
716
+ fontWeight: settings.security?.requireAuthentication ? "bold" : "normal",
717
+ textColor: settings.security?.requireAuthentication ? "success700" : "neutral800",
718
+ children: t("security.requireAuth", "Require Authentication")
719
+ }
720
+ ),
721
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", style: { fontSize: "12px" }, children: settings.security?.requireAuthentication ? "✓ Active" : "Inactive" })
722
+ ] })
723
+ ] })
724
+ }
725
+ ) }),
726
+ /* @__PURE__ */ jsx(Grid.Item, { col: 4, s: 12, children: /* @__PURE__ */ jsx(
727
+ Box,
728
+ {
729
+ padding: 4,
730
+ background: settings.security?.rateLimiting?.enabled ? "warning100" : "neutral0",
731
+ hasRadius: true,
732
+ style: {
733
+ cursor: "pointer",
734
+ border: `2px solid ${settings.security?.rateLimiting?.enabled ? "#f59e0b" : "#dcdce4"}`,
735
+ transition: "all 0.2s ease",
736
+ minHeight: "110px",
737
+ display: "flex",
738
+ alignItems: "center"
739
+ },
740
+ onClick: () => updateSecurity("rateLimiting", { ...settings.security?.rateLimiting, enabled: !settings.security?.rateLimiting?.enabled }),
741
+ children: /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "center", style: { width: "100%" }, children: [
742
+ /* @__PURE__ */ jsx(
743
+ Toggle,
744
+ {
745
+ checked: settings.security?.rateLimiting?.enabled || false,
746
+ onChange: (e) => updateSecurity("rateLimiting", { ...settings.security?.rateLimiting, enabled: e.target.checked })
747
+ }
748
+ ),
749
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", gap: 1, children: [
750
+ /* @__PURE__ */ jsx(
751
+ Typography,
752
+ {
753
+ variant: "omega",
754
+ fontWeight: settings.security?.rateLimiting?.enabled ? "bold" : "normal",
755
+ textColor: settings.security?.rateLimiting?.enabled ? "warning700" : "neutral800",
756
+ children: t("security.rateLimiting", "Enable Rate Limiting")
757
+ }
758
+ ),
759
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", style: { fontSize: "12px" }, children: settings.security?.rateLimiting?.enabled ? "✓ Active" : "Inactive" })
760
+ ] })
761
+ ] })
762
+ }
763
+ ) }),
764
+ settings.security?.rateLimiting?.enabled && /* @__PURE__ */ jsx(Grid.Item, { col: 6, s: 12, children: /* @__PURE__ */ jsx(
765
+ NumberInput,
766
+ {
767
+ label: t("security.maxEventsPerSecond", "Max Events/Second"),
768
+ value: settings.security?.rateLimiting?.maxEventsPerSecond || 10,
769
+ onValueChange: (value) => updateSecurity("rateLimiting", { ...settings.security?.rateLimiting, maxEventsPerSecond: value })
770
+ }
771
+ ) })
772
+ ] }),
773
+ /* @__PURE__ */ jsx(Box, { paddingTop: 4, paddingBottom: 2, children: /* @__PURE__ */ jsx(Divider, {}) }),
774
+ /* @__PURE__ */ jsxs(ResponsiveSection, { children: [
775
+ /* @__PURE__ */ jsx(ResponsiveSectionTitle, { variant: "delta", as: "h2", children: t("events.title", "Event Configuration") }),
776
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: t("events.description", "Global event settings") })
777
+ ] }),
778
+ /* @__PURE__ */ jsxs(Grid.Root, { gap: 3, children: [
779
+ /* @__PURE__ */ jsx(Grid.Item, { col: 4, s: 12, children: /* @__PURE__ */ jsx(
780
+ Box,
781
+ {
782
+ padding: 4,
783
+ background: settings.events?.customEventNames ? "primary100" : "neutral0",
784
+ hasRadius: true,
785
+ style: {
786
+ cursor: "pointer",
787
+ border: `2px solid ${settings.events?.customEventNames ? "#4945ff" : "#dcdce4"}`,
788
+ transition: "all 0.2s ease",
789
+ minHeight: "110px",
790
+ display: "flex",
791
+ alignItems: "center"
792
+ },
793
+ onClick: () => updateEvents("customEventNames", !settings.events?.customEventNames),
794
+ children: /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "center", style: { width: "100%" }, children: [
795
+ /* @__PURE__ */ jsx(
796
+ Toggle,
797
+ {
798
+ checked: settings.events?.customEventNames || false,
799
+ onChange: (e) => updateEvents("customEventNames", e.target.checked)
800
+ }
801
+ ),
802
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", gap: 1, children: [
803
+ /* @__PURE__ */ jsx(
804
+ Typography,
805
+ {
806
+ variant: "omega",
807
+ fontWeight: settings.events?.customEventNames ? "bold" : "normal",
808
+ textColor: settings.events?.customEventNames ? "primary700" : "neutral800",
809
+ children: t("events.customNames", "Use Custom Event Names")
810
+ }
811
+ ),
812
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", style: { fontSize: "12px" }, children: settings.events?.customEventNames ? "✓ Active" : "Inactive" })
813
+ ] })
814
+ ] })
815
+ }
816
+ ) }),
817
+ /* @__PURE__ */ jsx(Grid.Item, { col: 4, s: 12, children: /* @__PURE__ */ jsx(
818
+ Box,
819
+ {
820
+ padding: 4,
821
+ background: settings.events?.includeRelations ? "primary100" : "neutral0",
822
+ hasRadius: true,
823
+ style: {
824
+ cursor: "pointer",
825
+ border: `2px solid ${settings.events?.includeRelations ? "#4945ff" : "#dcdce4"}`,
826
+ transition: "all 0.2s ease",
827
+ minHeight: "110px",
828
+ display: "flex",
829
+ alignItems: "center"
830
+ },
831
+ onClick: () => updateEvents("includeRelations", !settings.events?.includeRelations),
832
+ children: /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "center", style: { width: "100%" }, children: [
833
+ /* @__PURE__ */ jsx(
834
+ Toggle,
835
+ {
836
+ checked: settings.events?.includeRelations || false,
837
+ onChange: (e) => updateEvents("includeRelations", e.target.checked)
838
+ }
839
+ ),
840
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", gap: 1, children: [
841
+ /* @__PURE__ */ jsx(
842
+ Typography,
843
+ {
844
+ variant: "omega",
845
+ fontWeight: settings.events?.includeRelations ? "bold" : "normal",
846
+ textColor: settings.events?.includeRelations ? "primary700" : "neutral800",
847
+ children: t("events.includeRelations", "Include Relations")
848
+ }
849
+ ),
850
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", style: { fontSize: "12px" }, children: settings.events?.includeRelations ? "✓ Active" : "Inactive" })
851
+ ] })
852
+ ] })
853
+ }
854
+ ) }),
855
+ /* @__PURE__ */ jsx(Grid.Item, { col: 4, s: 12, children: /* @__PURE__ */ jsx(
856
+ Box,
857
+ {
858
+ padding: 4,
859
+ background: settings.events?.onlyPublished ? "primary100" : "neutral0",
860
+ hasRadius: true,
861
+ style: {
862
+ cursor: "pointer",
863
+ border: `2px solid ${settings.events?.onlyPublished ? "#4945ff" : "#dcdce4"}`,
864
+ transition: "all 0.2s ease",
865
+ minHeight: "110px",
866
+ display: "flex",
867
+ alignItems: "center"
868
+ },
869
+ onClick: () => updateEvents("onlyPublished", !settings.events?.onlyPublished),
870
+ children: /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "center", style: { width: "100%" }, children: [
871
+ /* @__PURE__ */ jsx(
872
+ Toggle,
873
+ {
874
+ checked: settings.events?.onlyPublished || false,
875
+ onChange: (e) => updateEvents("onlyPublished", e.target.checked)
876
+ }
877
+ ),
878
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", gap: 1, children: [
879
+ /* @__PURE__ */ jsx(
880
+ Typography,
881
+ {
882
+ variant: "omega",
883
+ fontWeight: settings.events?.onlyPublished ? "bold" : "normal",
884
+ textColor: settings.events?.onlyPublished ? "primary700" : "neutral800",
885
+ children: t("events.onlyPublished", "Only Published Content")
886
+ }
887
+ ),
888
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", style: { fontSize: "12px" }, children: settings.events?.onlyPublished ? "✓ Active" : "Inactive" })
889
+ ] })
890
+ ] })
891
+ }
892
+ ) })
893
+ ] }),
894
+ /* @__PURE__ */ jsx(Box, { paddingTop: 4, paddingBottom: 2, children: /* @__PURE__ */ jsx(Divider, {}) }),
895
+ /* @__PURE__ */ jsxs(ResponsiveSection, { children: [
896
+ /* @__PURE__ */ jsx(ResponsiveSectionTitle, { variant: "delta", as: "h2", children: t("permissions.title", "Role Permissions") }),
897
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: t("permissions.description", "Configure Socket.IO permissions per user role") })
898
+ ] }),
899
+ availableRoles.length > 0 ? /* @__PURE__ */ jsx(Accordion.Root, { children: availableRoles.map((role) => {
900
+ const rolePerms = settings.rolePermissions?.[role.type] || {};
901
+ const canConnect = rolePerms.canConnect ?? true;
902
+ const enabledContentTypes = Object.entries(rolePerms.contentTypes || {}).filter(
903
+ ([uid, actions]) => actions.create || actions.update || actions.delete
904
+ ).length;
905
+ return /* @__PURE__ */ jsxs(Accordion.Item, { value: role.type, children: [
906
+ /* @__PURE__ */ jsx(Accordion.Header, { children: /* @__PURE__ */ jsx(Accordion.Trigger, { children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", width: "100%", paddingRight: 4, alignItems: "flex-start", children: [
907
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", gap: 1, children: [
908
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: "bold", children: role.name }),
909
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", children: [
910
+ "(",
911
+ enabledContentTypes,
912
+ " ",
913
+ t("permissions.contentTypesEnabled", "content types enabled"),
914
+ ")"
915
+ ] })
916
+ ] }),
917
+ /* @__PURE__ */ jsx(Badge, { active: canConnect, children: canConnect ? t("permissions.canConnect", "Can Connect") : t("permissions.blocked", "Blocked") })
918
+ ] }) }) }),
919
+ /* @__PURE__ */ jsx(Accordion.Content, { children: /* @__PURE__ */ jsxs(Box, { padding: 4, background: "neutral100", children: [
920
+ /* @__PURE__ */ jsx(Box, { paddingBottom: 4, children: /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "center", children: [
921
+ /* @__PURE__ */ jsx(
922
+ Checkbox,
923
+ {
924
+ checked: canConnect,
925
+ onCheckedChange: (checked) => updateSettings((prev) => ({
926
+ ...prev,
927
+ rolePermissions: {
928
+ ...prev.rolePermissions,
929
+ [role.type]: {
930
+ ...prev.rolePermissions?.[role.type],
931
+ canConnect: checked
932
+ }
933
+ }
934
+ }))
935
+ }
936
+ ),
937
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", gap: 1, children: [
938
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: "bold", children: t("permissions.allowConnection", "Allow Connection") }),
939
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: t("permissions.allowConnectionHint", "Users with this role can connect to Socket.IO") })
940
+ ] })
941
+ ] }) }),
942
+ /* @__PURE__ */ jsx(Box, { paddingBottom: 4, children: /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "center", children: [
943
+ /* @__PURE__ */ jsx(
944
+ Checkbox,
945
+ {
946
+ checked: rolePerms.allowCredentials ?? true,
947
+ onCheckedChange: (checked) => updateSettings((prev) => ({
948
+ ...prev,
949
+ rolePermissions: {
950
+ ...prev.rolePermissions,
951
+ [role.type]: {
952
+ ...prev.rolePermissions?.[role.type],
953
+ allowCredentials: checked
954
+ }
955
+ }
956
+ }))
957
+ }
958
+ ),
959
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", gap: 1, children: [
960
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: "bold", children: t("permissions.allowCredentials", "Allow Credentials") }),
961
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: t("permissions.allowCredentialsHint", "Allow cookies and auth headers") })
962
+ ] })
963
+ ] }) }),
964
+ /* @__PURE__ */ jsxs(Box, { paddingBottom: 4, children: [
965
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: "bold", paddingBottom: 2, children: t("permissions.allowedMethods", "Allowed HTTP Methods") }),
966
+ /* @__PURE__ */ jsx(Flex, { gap: 2, wrap: "wrap", children: httpMethods.map((method) => /* @__PURE__ */ jsx(
967
+ Box,
968
+ {
969
+ padding: 2,
970
+ paddingLeft: 3,
971
+ paddingRight: 3,
972
+ background: rolePerms.allowedMethods?.includes(method) ? "primary100" : "neutral100",
973
+ hasRadius: true,
974
+ style: {
975
+ cursor: "pointer",
976
+ border: `1px solid ${rolePerms.allowedMethods?.includes(method) ? "#4945ff" : "#dcdce4"}`
977
+ },
978
+ onClick: () => {
979
+ const current = rolePerms.allowedMethods || [];
980
+ const updated = current.includes(method) ? current.filter((m) => m !== method) : [...current, method];
981
+ updateSettings((prev) => ({
982
+ ...prev,
983
+ rolePermissions: {
984
+ ...prev.rolePermissions,
985
+ [role.type]: {
986
+ ...prev.rolePermissions?.[role.type],
987
+ allowedMethods: updated
988
+ }
989
+ }
990
+ }));
991
+ },
992
+ children: /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
993
+ /* @__PURE__ */ jsx(
994
+ Checkbox,
995
+ {
996
+ checked: rolePerms.allowedMethods?.includes(method) || false,
997
+ onCheckedChange: () => {
998
+ }
999
+ }
1000
+ ),
1001
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: "bold", children: method })
1002
+ ] })
1003
+ },
1004
+ method
1005
+ )) })
1006
+ ] }),
1007
+ /* @__PURE__ */ jsx(Divider, {}),
1008
+ /* @__PURE__ */ jsxs(Box, { paddingTop: 4, children: [
1009
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", paddingBottom: 3, children: [
1010
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: "bold", children: t("permissions.contentTypePermissions", "Content Type Permissions") }),
1011
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
1012
+ /* @__PURE__ */ jsx(
1013
+ Button,
1014
+ {
1015
+ size: "S",
1016
+ variant: "secondary",
1017
+ onClick: () => enableAllContentTypes(role.type),
1018
+ children: t("permissions.enableAll", "Enable All")
1019
+ }
1020
+ ),
1021
+ /* @__PURE__ */ jsx(
1022
+ Button,
1023
+ {
1024
+ size: "S",
1025
+ variant: "tertiary",
1026
+ onClick: () => disableAllContentTypes(role.type),
1027
+ children: t("permissions.disableAll", "Disable All")
1028
+ }
1029
+ )
1030
+ ] })
1031
+ ] }),
1032
+ availableContentTypes.length > 0 ? /* @__PURE__ */ jsxs(Box, { background: "neutral0", hasRadius: true, style: { border: "1px solid #dcdce4" }, children: [
1033
+ /* @__PURE__ */ jsx(Box, { padding: 2, background: "neutral100", style: { borderBottom: "1px solid #dcdce4" }, children: /* @__PURE__ */ jsxs(Grid.Root, { children: [
1034
+ /* @__PURE__ */ jsx(Grid.Item, { col: 4, children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: t("events.contentType", "CONTENT TYPE") }) }),
1035
+ /* @__PURE__ */ jsx(Grid.Item, { col: 2, children: /* @__PURE__ */ jsx(Flex, { justifyContent: "center", children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: t("events.create", "CREATE") }) }) }),
1036
+ /* @__PURE__ */ jsx(Grid.Item, { col: 2, children: /* @__PURE__ */ jsx(Flex, { justifyContent: "center", children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: t("events.update", "UPDATE") }) }) }),
1037
+ /* @__PURE__ */ jsx(Grid.Item, { col: 2, children: /* @__PURE__ */ jsx(Flex, { justifyContent: "center", children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: t("events.delete", "DELETE") }) }) }),
1038
+ /* @__PURE__ */ jsx(Grid.Item, { col: 2, children: /* @__PURE__ */ jsx(Flex, { justifyContent: "center", children: /* @__PURE__ */ jsxs(Typography, { variant: "sigma", textColor: "neutral600", children: [
1039
+ t("entitySubscriptions.allow", "ENTITIES"),
1040
+ " 🆕"
1041
+ ] }) }) })
1042
+ ] }) }),
1043
+ availableContentTypes.map((ct, idx) => {
1044
+ const hasAnyPermission = rolePerms.contentTypes?.[ct.uid]?.create || rolePerms.contentTypes?.[ct.uid]?.update || rolePerms.contentTypes?.[ct.uid]?.delete;
1045
+ settings.entitySubscriptions?.allowedContentTypes?.includes(ct.uid) || settings.entitySubscriptions?.allowedContentTypes?.length === 0;
1046
+ return /* @__PURE__ */ jsx(
1047
+ Box,
1048
+ {
1049
+ padding: 2,
1050
+ style: {
1051
+ borderBottom: idx < availableContentTypes.length - 1 ? "1px solid #dcdce4" : "none",
1052
+ opacity: hasAnyPermission ? 1 : 0.5
1053
+ },
1054
+ children: /* @__PURE__ */ jsxs(Grid.Root, { children: [
1055
+ /* @__PURE__ */ jsx(Grid.Item, { col: 4, children: /* @__PURE__ */ jsx(Typography, { variant: "omega", children: ct.displayName }) }),
1056
+ /* @__PURE__ */ jsx(Grid.Item, { col: 2, children: /* @__PURE__ */ jsx(Flex, { justifyContent: "center", children: /* @__PURE__ */ jsx(
1057
+ Checkbox,
1058
+ {
1059
+ checked: rolePerms.contentTypes?.[ct.uid]?.create || false,
1060
+ onCheckedChange: (checked) => updateSettings((prev) => ({
1061
+ ...prev,
1062
+ rolePermissions: {
1063
+ ...prev.rolePermissions,
1064
+ [role.type]: {
1065
+ ...prev.rolePermissions?.[role.type],
1066
+ contentTypes: {
1067
+ ...prev.rolePermissions?.[role.type]?.contentTypes,
1068
+ [ct.uid]: {
1069
+ ...prev.rolePermissions?.[role.type]?.contentTypes?.[ct.uid],
1070
+ create: checked
1071
+ }
1072
+ }
1073
+ }
1074
+ }
1075
+ }))
1076
+ }
1077
+ ) }) }),
1078
+ /* @__PURE__ */ jsx(Grid.Item, { col: 2, children: /* @__PURE__ */ jsx(Flex, { justifyContent: "center", children: /* @__PURE__ */ jsx(
1079
+ Checkbox,
1080
+ {
1081
+ checked: rolePerms.contentTypes?.[ct.uid]?.update || false,
1082
+ onCheckedChange: (checked) => updateSettings((prev) => ({
1083
+ ...prev,
1084
+ rolePermissions: {
1085
+ ...prev.rolePermissions,
1086
+ [role.type]: {
1087
+ ...prev.rolePermissions?.[role.type],
1088
+ contentTypes: {
1089
+ ...prev.rolePermissions?.[role.type]?.contentTypes,
1090
+ [ct.uid]: {
1091
+ ...prev.rolePermissions?.[role.type]?.contentTypes?.[ct.uid],
1092
+ update: checked
1093
+ }
1094
+ }
1095
+ }
1096
+ }
1097
+ }))
1098
+ }
1099
+ ) }) }),
1100
+ /* @__PURE__ */ jsx(Grid.Item, { col: 2, children: /* @__PURE__ */ jsx(Flex, { justifyContent: "center", children: /* @__PURE__ */ jsx(
1101
+ Checkbox,
1102
+ {
1103
+ checked: rolePerms.contentTypes?.[ct.uid]?.delete || false,
1104
+ onCheckedChange: (checked) => updateSettings((prev) => ({
1105
+ ...prev,
1106
+ rolePermissions: {
1107
+ ...prev.rolePermissions,
1108
+ [role.type]: {
1109
+ ...prev.rolePermissions?.[role.type],
1110
+ contentTypes: {
1111
+ ...prev.rolePermissions?.[role.type]?.contentTypes,
1112
+ [ct.uid]: {
1113
+ ...prev.rolePermissions?.[role.type]?.contentTypes?.[ct.uid],
1114
+ delete: checked
1115
+ }
1116
+ }
1117
+ }
1118
+ }
1119
+ }))
1120
+ }
1121
+ ) }) }),
1122
+ /* @__PURE__ */ jsx(Grid.Item, { col: 2, children: /* @__PURE__ */ jsx(Flex, { justifyContent: "center", children: settings.entitySubscriptions?.enabled ? /* @__PURE__ */ jsx(
1123
+ Checkbox,
1124
+ {
1125
+ checked: hasAnyPermission && (settings.entitySubscriptions?.allowedContentTypes?.length === 0 || settings.entitySubscriptions?.allowedContentTypes?.includes(ct.uid)),
1126
+ disabled: !hasAnyPermission,
1127
+ onCheckedChange: (checked) => {
1128
+ if (!hasAnyPermission) return;
1129
+ const current = settings.entitySubscriptions?.allowedContentTypes || [];
1130
+ let updated;
1131
+ if (current.length === 0) {
1132
+ if (!checked) {
1133
+ updated = availableContentTypes.filter((t2) => t2.uid !== ct.uid).map((t2) => t2.uid);
1134
+ } else {
1135
+ updated = [];
1136
+ }
1137
+ } else {
1138
+ if (checked) {
1139
+ updated = [...current, ct.uid];
1140
+ } else {
1141
+ updated = current.filter((uid) => uid !== ct.uid);
1142
+ }
1143
+ }
1144
+ updateEntitySubscriptions("allowedContentTypes", updated);
1145
+ }
1146
+ }
1147
+ ) : /* @__PURE__ */ jsx(Checkbox, { checked: false, disabled: true }) }) })
1148
+ ] })
1149
+ },
1150
+ ct.uid
1151
+ );
1152
+ })
1153
+ ] }) : /* @__PURE__ */ jsx(Box, { padding: 4, background: "neutral100", hasRadius: true, children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: t("events.noContentTypes", "No content types found") }) })
1154
+ ] })
1155
+ ] }) })
1156
+ ] }, role.id);
1157
+ }) }) : /* @__PURE__ */ jsx(Box, { padding: 4, background: "neutral100", hasRadius: true, children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: t("permissions.noRoles", "No roles found") }) }),
1158
+ /* @__PURE__ */ jsx(Box, { paddingTop: 6, paddingBottom: 4, children: /* @__PURE__ */ jsx(Divider, {}) }),
1159
+ /* @__PURE__ */ jsxs(Box, { paddingBottom: 4, children: [
1160
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", as: "h2", children: t("redis.title", "Redis Adapter") }),
1161
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: t("redis.description", "Enable Redis for multi-server scaling") })
1162
+ ] }),
1163
+ /* @__PURE__ */ jsxs(Grid.Root, { gap: 4, children: [
1164
+ /* @__PURE__ */ jsx(Grid.Item, { col: 4, s: 12, children: /* @__PURE__ */ jsx(
1165
+ Box,
1166
+ {
1167
+ padding: 4,
1168
+ background: settings.redis?.enabled ? "danger100" : "neutral0",
1169
+ hasRadius: true,
1170
+ style: {
1171
+ cursor: "pointer",
1172
+ border: `2px solid ${settings.redis?.enabled ? "#dc2626" : "#dcdce4"}`,
1173
+ transition: "all 0.2s ease",
1174
+ minHeight: "110px",
1175
+ display: "flex",
1176
+ alignItems: "center"
1177
+ },
1178
+ onClick: () => updateRedis("enabled", !settings.redis?.enabled),
1179
+ children: /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "center", style: { width: "100%" }, children: [
1180
+ /* @__PURE__ */ jsx(
1181
+ Toggle,
1182
+ {
1183
+ checked: settings.redis?.enabled || false,
1184
+ onChange: (e) => updateRedis("enabled", e.target.checked)
1185
+ }
1186
+ ),
1187
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", gap: 1, children: [
1188
+ /* @__PURE__ */ jsx(
1189
+ Typography,
1190
+ {
1191
+ variant: "omega",
1192
+ fontWeight: settings.redis?.enabled ? "bold" : "normal",
1193
+ textColor: settings.redis?.enabled ? "danger700" : "neutral800",
1194
+ children: t("redis.enable", "Enable Redis Adapter")
1195
+ }
1196
+ ),
1197
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", style: { fontSize: "12px" }, children: settings.redis?.enabled ? "✓ Active" : "Inactive" })
1198
+ ] })
1199
+ ] })
1200
+ }
1201
+ ) }),
1202
+ settings.redis?.enabled && /* @__PURE__ */ jsx(Grid.Item, { col: 6, s: 12, children: /* @__PURE__ */ jsxs(ResponsiveField, { children: [
1203
+ /* @__PURE__ */ jsx(Field.Label, { children: t("redis.url", "Redis URL") }),
1204
+ /* @__PURE__ */ jsx(InputWrapper, { children: /* @__PURE__ */ jsx(
1205
+ TextInput,
1206
+ {
1207
+ value: settings.redis?.url || "redis://localhost:6379",
1208
+ onChange: (e) => updateRedis("url", e.target.value),
1209
+ placeholder: "redis://localhost:6379"
1210
+ }
1211
+ ) })
1212
+ ] }) })
1213
+ ] }),
1214
+ /* @__PURE__ */ jsx(Box, { paddingTop: 4, paddingBottom: 2, children: /* @__PURE__ */ jsx(Divider, {}) }),
1215
+ /* @__PURE__ */ jsxs(ResponsiveSection, { children: [
1216
+ /* @__PURE__ */ jsx(ResponsiveSectionTitle, { variant: "delta", as: "h2", children: t("namespaces.title", "Namespaces") }),
1217
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: t("namespaces.description", "Create separate Socket.IO endpoints") })
1218
+ ] }),
1219
+ /* @__PURE__ */ jsxs(Grid.Root, { gap: 3, children: [
1220
+ /* @__PURE__ */ jsx(Grid.Item, { col: 6, s: 12, children: /* @__PURE__ */ jsx(
1221
+ Box,
1222
+ {
1223
+ padding: 4,
1224
+ background: settings.namespaces?.enabled ? "secondary100" : "neutral0",
1225
+ hasRadius: true,
1226
+ style: {
1227
+ cursor: "pointer",
1228
+ border: `2px solid ${settings.namespaces?.enabled ? "#a855f7" : "#dcdce4"}`,
1229
+ transition: "all 0.2s ease",
1230
+ minHeight: "110px",
1231
+ display: "flex",
1232
+ alignItems: "center"
1233
+ },
1234
+ onClick: () => updateNamespaces("enabled", !settings.namespaces?.enabled),
1235
+ children: /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "center", style: { width: "100%" }, children: [
1236
+ /* @__PURE__ */ jsx(
1237
+ Toggle,
1238
+ {
1239
+ checked: settings.namespaces?.enabled || false,
1240
+ onChange: (e) => updateNamespaces("enabled", e.target.checked)
1241
+ }
1242
+ ),
1243
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", gap: 1, children: [
1244
+ /* @__PURE__ */ jsx(
1245
+ Typography,
1246
+ {
1247
+ variant: "omega",
1248
+ fontWeight: settings.namespaces?.enabled ? "bold" : "normal",
1249
+ textColor: settings.namespaces?.enabled ? "secondary700" : "neutral800",
1250
+ children: t("namespaces.enable", "Enable Namespaces")
1251
+ }
1252
+ ),
1253
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", style: { fontSize: "12px" }, children: settings.namespaces?.enabled ? "✓ Active" : "Inactive" })
1254
+ ] })
1255
+ ] })
1256
+ }
1257
+ ) }),
1258
+ settings.namespaces?.enabled && /* @__PURE__ */ jsx(Grid.Item, { col: 12, children: /* @__PURE__ */ jsxs(ResponsiveField, { children: [
1259
+ /* @__PURE__ */ jsx(Field.Label, { children: t("namespaces.list", "Namespaces") }),
1260
+ /* @__PURE__ */ jsxs(Flex, { direction: { base: "column", tablet: "row" }, gap: 2, paddingBottom: 2, children: [
1261
+ /* @__PURE__ */ jsx(InputWrapper, { style: { flex: 1, width: "100%" }, children: /* @__PURE__ */ jsx(
1262
+ TextInput,
1263
+ {
1264
+ placeholder: "admin",
1265
+ value: newNamespace,
1266
+ onChange: (e) => setNewNamespace(e.target.value),
1267
+ onKeyPress: (e) => e.key === "Enter" && addNamespace()
1268
+ }
1269
+ ) }),
1270
+ /* @__PURE__ */ jsx(Button, { onClick: addNamespace, size: "L", style: { width: "100%", maxWidth: "200px", minHeight: "44px" }, children: t("namespaces.add", "Add") })
1271
+ ] }),
1272
+ /* @__PURE__ */ jsx(Flex, { gap: 2, wrap: "wrap", paddingTop: 2, children: Object.entries(settings.namespaces?.list || {}).map(([ns, config]) => /* @__PURE__ */ jsxs(Flex, { gap: 1, alignItems: "center", children: [
1273
+ /* @__PURE__ */ jsxs(Badge, { children: [
1274
+ "/",
1275
+ ns,
1276
+ config.requireAuth && /* @__PURE__ */ jsx("span", { style: { marginLeft: "4px" }, children: "🔒" }),
1277
+ /* @__PURE__ */ jsx(
1278
+ Button,
1279
+ {
1280
+ variant: "ghost",
1281
+ size: "S",
1282
+ onClick: () => removeNamespace(ns),
1283
+ style: { marginLeft: "8px", padding: "0 4px" },
1284
+ children: "×"
1285
+ }
1286
+ )
1287
+ ] }),
1288
+ config.requireAuth && /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", style: { fontSize: "11px" }, children: t("namespaces.authRequired", "Auth required") })
1289
+ ] }, ns)) }),
1290
+ /* @__PURE__ */ jsx(Box, { paddingTop: 2, children: /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: t("namespaces.hint", "Examples: admin, chat, notifications") }) })
1291
+ ] }) })
1292
+ ] }),
1293
+ /* @__PURE__ */ jsx(Box, { paddingTop: 4, paddingBottom: 2, children: /* @__PURE__ */ jsx(Divider, {}) }),
1294
+ /* @__PURE__ */ jsx(ResponsiveSection, { children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
1295
+ /* @__PURE__ */ jsxs(Box, { children: [
1296
+ /* @__PURE__ */ jsxs(ResponsiveSectionTitle, { variant: "delta", as: "h2", children: [
1297
+ t("entitySubscriptions.title", "Entity Subscriptions"),
1298
+ " 🆕"
1299
+ ] }),
1300
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: t("entitySubscriptions.description", "Allow clients to subscribe to specific entities") })
1301
+ ] }),
1302
+ /* @__PURE__ */ jsx(
1303
+ Toggle,
1304
+ {
1305
+ checked: settings.entitySubscriptions?.enabled ?? true,
1306
+ onChange: (e) => updateEntitySubscriptions("enabled", e.target.checked)
1307
+ }
1308
+ )
1309
+ ] }) }),
1310
+ settings.entitySubscriptions?.enabled && /* @__PURE__ */ jsx(Box, { paddingTop: 3, children: /* @__PURE__ */ jsxs(Grid.Root, { gap: 3, children: [
1311
+ /* @__PURE__ */ jsx(Grid.Item, { col: 4, s: 12, children: /* @__PURE__ */ jsxs(ResponsiveField, { children: [
1312
+ /* @__PURE__ */ jsx(Field.Label, { children: t("entitySubscriptions.maxPerSocket", "Max Per Socket") }),
1313
+ /* @__PURE__ */ jsx(InputWrapper, { children: /* @__PURE__ */ jsx(
1314
+ NumberInput,
1315
+ {
1316
+ value: settings.entitySubscriptions?.maxSubscriptionsPerSocket ?? 100,
1317
+ onValueChange: (value) => updateEntitySubscriptions("maxSubscriptionsPerSocket", value)
1318
+ }
1319
+ ) })
1320
+ ] }) }),
1321
+ /* @__PURE__ */ jsx(Grid.Item, { col: 4, s: 12, children: /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", paddingTop: 6, children: [
1322
+ /* @__PURE__ */ jsx(
1323
+ Checkbox,
1324
+ {
1325
+ checked: settings.entitySubscriptions?.requireVerification ?? true,
1326
+ onCheckedChange: (checked) => updateEntitySubscriptions("requireVerification", checked)
1327
+ }
1328
+ ),
1329
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", children: t("entitySubscriptions.verify", "Verify Entity Exists") })
1330
+ ] }) }),
1331
+ /* @__PURE__ */ jsx(Grid.Item, { col: 4, s: 12, children: /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", paddingTop: 6, children: [
1332
+ /* @__PURE__ */ jsx(
1333
+ Checkbox,
1334
+ {
1335
+ checked: settings.entitySubscriptions?.enableMetrics ?? true,
1336
+ onCheckedChange: (checked) => updateEntitySubscriptions("enableMetrics", checked)
1337
+ }
1338
+ ),
1339
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", children: t("entitySubscriptions.metrics", "Track Metrics") })
1340
+ ] }) })
1341
+ ] }) }),
1342
+ /* @__PURE__ */ jsx(Box, { paddingTop: 4, paddingBottom: 2, children: /* @__PURE__ */ jsx(Divider, {}) }),
1343
+ /* @__PURE__ */ jsx(ResponsiveSection, { children: /* @__PURE__ */ jsx(ResponsiveSectionTitle, { variant: "delta", as: "h2", children: t("monitoring.title", "Monitoring & Logging") }) }),
1344
+ /* @__PURE__ */ jsxs(Grid.Root, { gap: 3, children: [
1345
+ /* @__PURE__ */ jsx(Grid.Item, { col: 6, s: 12, children: /* @__PURE__ */ jsx(
1346
+ Box,
1347
+ {
1348
+ padding: 4,
1349
+ background: settings.monitoring?.enableConnectionLogging ? "primary100" : "neutral0",
1350
+ hasRadius: true,
1351
+ style: { cursor: "pointer", border: `1px solid ${settings.monitoring?.enableConnectionLogging ? "#4945ff" : "#dcdce4"}` },
1352
+ onClick: () => updateMonitoring("enableConnectionLogging", !settings.monitoring?.enableConnectionLogging),
1353
+ children: /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "center", children: [
1354
+ /* @__PURE__ */ jsx(
1355
+ Checkbox,
1356
+ {
1357
+ checked: settings.monitoring?.enableConnectionLogging || false,
1358
+ onCheckedChange: (checked) => updateMonitoring("enableConnectionLogging", checked)
1359
+ }
1360
+ ),
1361
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", gap: 1, children: [
1362
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: "bold", children: t("monitoring.connectionLogging", "Connection Logging") }),
1363
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: t("monitoring.connectionLoggingHint", "Log client connections") })
1364
+ ] })
1365
+ ] })
1366
+ }
1367
+ ) }),
1368
+ /* @__PURE__ */ jsx(Grid.Item, { col: 4, s: 12, children: /* @__PURE__ */ jsx(
1369
+ Box,
1370
+ {
1371
+ padding: 4,
1372
+ background: settings.monitoring?.enableEventLogging ? "primary100" : "neutral0",
1373
+ hasRadius: true,
1374
+ style: { cursor: "pointer", border: `1px solid ${settings.monitoring?.enableEventLogging ? "#4945ff" : "#dcdce4"}` },
1375
+ onClick: () => updateMonitoring("enableEventLogging", !settings.monitoring?.enableEventLogging),
1376
+ children: /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "center", children: [
1377
+ /* @__PURE__ */ jsx(
1378
+ Checkbox,
1379
+ {
1380
+ checked: settings.monitoring?.enableEventLogging || false,
1381
+ onCheckedChange: (checked) => updateMonitoring("enableEventLogging", checked)
1382
+ }
1383
+ ),
1384
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", gap: 1, children: [
1385
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: "bold", children: t("monitoring.eventLogging", "Event Logging") }),
1386
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: t("monitoring.eventLoggingHint", "Log all events for debugging") })
1387
+ ] })
1388
+ ] })
1389
+ }
1390
+ ) }),
1391
+ settings.monitoring?.enableEventLogging && /* @__PURE__ */ jsx(Grid.Item, { col: 12, s: 12, children: /* @__PURE__ */ jsx(ResponsiveField, { children: /* @__PURE__ */ jsx(InputWrapper, { children: /* @__PURE__ */ jsx(
1392
+ NumberInput,
1393
+ {
1394
+ label: t("monitoring.maxLogSize", "Max Log Size"),
1395
+ value: settings.monitoring?.maxEventLogSize || 100,
1396
+ onValueChange: (value) => updateMonitoring("maxEventLogSize", value)
1397
+ }
1398
+ ) }) }) })
1399
+ ] })
1400
+ ] }),
1401
+ /* @__PURE__ */ jsx(Box, { marginTop: 3, padding: 3, background: "success100", hasRadius: true, children: /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
1402
+ /* @__PURE__ */ jsx(Check, {}),
1403
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", style: { fontSize: "13px" }, children: t("settings.noRestart", "Changes are applied immediately – no restart required!") })
1404
+ ] }) })
1405
+ ] }) });
1406
+ };
1407
+ export {
1408
+ SettingsPage
1409
+ };