strapi-plugin-magic-sessionmanager 4.2.3 → 4.2.5

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.
Files changed (61) hide show
  1. package/dist/server/index.js +1 -1
  2. package/dist/server/index.mjs +1 -1
  3. package/package.json +1 -3
  4. package/admin/jsconfig.json +0 -10
  5. package/admin/src/components/Initializer.jsx +0 -11
  6. package/admin/src/components/LicenseGuard.jsx +0 -591
  7. package/admin/src/components/OnlineUsersWidget.jsx +0 -212
  8. package/admin/src/components/PluginIcon.jsx +0 -8
  9. package/admin/src/components/SessionDetailModal.jsx +0 -449
  10. package/admin/src/components/SessionInfoCard.jsx +0 -151
  11. package/admin/src/components/SessionInfoPanel.jsx +0 -385
  12. package/admin/src/components/index.jsx +0 -5
  13. package/admin/src/hooks/useLicense.js +0 -103
  14. package/admin/src/index.js +0 -149
  15. package/admin/src/pages/ActiveSessions.jsx +0 -12
  16. package/admin/src/pages/Analytics.jsx +0 -735
  17. package/admin/src/pages/App.jsx +0 -12
  18. package/admin/src/pages/HomePage.jsx +0 -1212
  19. package/admin/src/pages/License.jsx +0 -603
  20. package/admin/src/pages/Settings.jsx +0 -1646
  21. package/admin/src/pages/SettingsNew.jsx +0 -1204
  22. package/admin/src/pages/UpgradePage.jsx +0 -448
  23. package/admin/src/pages/index.jsx +0 -3
  24. package/admin/src/pluginId.js +0 -4
  25. package/admin/src/translations/de.json +0 -299
  26. package/admin/src/translations/en.json +0 -299
  27. package/admin/src/translations/es.json +0 -287
  28. package/admin/src/translations/fr.json +0 -287
  29. package/admin/src/translations/pt.json +0 -287
  30. package/admin/src/utils/getTranslation.js +0 -5
  31. package/admin/src/utils/index.js +0 -2
  32. package/admin/src/utils/parseUserAgent.js +0 -79
  33. package/admin/src/utils/theme.js +0 -85
  34. package/server/jsconfig.json +0 -10
  35. package/server/src/bootstrap.js +0 -492
  36. package/server/src/config/index.js +0 -23
  37. package/server/src/content-types/index.js +0 -9
  38. package/server/src/content-types/session/schema.json +0 -84
  39. package/server/src/controllers/controller.js +0 -11
  40. package/server/src/controllers/index.js +0 -11
  41. package/server/src/controllers/license.js +0 -266
  42. package/server/src/controllers/session.js +0 -433
  43. package/server/src/controllers/settings.js +0 -122
  44. package/server/src/destroy.js +0 -22
  45. package/server/src/index.js +0 -23
  46. package/server/src/middlewares/index.js +0 -5
  47. package/server/src/middlewares/last-seen.js +0 -62
  48. package/server/src/policies/index.js +0 -3
  49. package/server/src/register.js +0 -36
  50. package/server/src/routes/admin.js +0 -149
  51. package/server/src/routes/content-api.js +0 -60
  52. package/server/src/routes/index.js +0 -9
  53. package/server/src/services/geolocation.js +0 -182
  54. package/server/src/services/index.js +0 -13
  55. package/server/src/services/license-guard.js +0 -316
  56. package/server/src/services/notifications.js +0 -319
  57. package/server/src/services/service.js +0 -7
  58. package/server/src/services/session.js +0 -393
  59. package/server/src/utils/encryption.js +0 -121
  60. package/server/src/utils/getClientIp.js +0 -118
  61. package/server/src/utils/logger.js +0 -84
@@ -1,1204 +0,0 @@
1
- import { useState, useEffect } from 'react';
2
- import {
3
- Box,
4
- Typography,
5
- Flex,
6
- Button,
7
- Loader,
8
- SingleSelect,
9
- SingleSelectOption,
10
- Checkbox,
11
- Alert,
12
- TextInput,
13
- Textarea,
14
- Tabs,
15
- Divider,
16
- Badge,
17
- Accordion,
18
- Grid,
19
- Toggle,
20
- NumberInput,
21
- } from '@strapi/design-system';
22
- import { useFetchClient, useNotification } from '@strapi/strapi/admin';
23
- import { Check, Information, Duplicate, Trash, Mail, Code, Cog, Shield, Clock } from '@strapi/icons';
24
- import styled, { keyframes, css } from 'styled-components';
25
- import pluginId from '../pluginId';
26
- import { useLicense } from '../hooks/useLicense';
27
-
28
- // ================ THEME ================
29
- const theme = {
30
- colors: {
31
- primary: { 600: '#0284C7', 700: '#075985', 100: '#E0F2FE', 50: '#F0F9FF' },
32
- success: { 600: '#16A34A', 700: '#15803D', 100: '#DCFCE7', 50: '#F0FDF4' },
33
- danger: { 600: '#DC2626', 700: '#B91C1C', 100: '#FEE2E2', 50: '#FEF2F2' },
34
- warning: { 600: '#D97706', 700: '#A16207', 100: '#FEF3C7', 50: '#FFFBEB' },
35
- neutral: { 0: '#FFFFFF', 50: '#F9FAFB', 100: '#F3F4F6', 200: '#E5E7EB', 400: '#9CA3AF', 600: '#4B5563', 700: '#374151', 800: '#1F2937' }
36
- },
37
- shadows: { sm: '0 1px 3px rgba(0,0,0,0.1)', md: '0 4px 6px rgba(0,0,0,0.1)', xl: '0 20px 25px rgba(0,0,0,0.1)' },
38
- borderRadius: { md: '8px', lg: '12px', xl: '16px' }
39
- };
40
-
41
- // ================ ANIMATIONS ================
42
- const fadeIn = keyframes`
43
- from { opacity: 0; transform: translateY(10px); }
44
- to { opacity: 1; transform: translateY(0); }
45
- `;
46
-
47
- const shimmer = keyframes`
48
- 0% { background-position: -200% 0; }
49
- 100% { background-position: 200% 0; }
50
- `;
51
-
52
- // ================ STYLED COMPONENTS ================
53
- const Container = styled(Box)`
54
- ${css`animation: ${fadeIn} 0.5s;`}
55
- max-width: 1400px;
56
- margin: 0 auto;
57
- `;
58
-
59
- const StickySaveBar = styled(Box)`
60
- position: sticky;
61
- top: 0;
62
- z-index: 10;
63
- background: white;
64
- border-bottom: 1px solid ${theme.colors.neutral[200]};
65
- box-shadow: ${theme.shadows.sm};
66
- `;
67
-
68
- const ToggleCard = styled(Box)`
69
- background: ${props => props.$active ? theme.colors.success[50] : theme.colors.neutral[50]};
70
- border-radius: ${theme.borderRadius.md};
71
- padding: 20px;
72
- transition: all 0.3s;
73
- border: 2px solid ${props => props.$active ? theme.colors.success[600] : theme.colors.neutral[200]};
74
- box-shadow: ${props => props.$active ? '0 4px 12px rgba(34, 197, 94, 0.2)' : '0 1px 3px rgba(0, 0, 0, 0.1)'};
75
- position: relative;
76
- cursor: pointer;
77
-
78
- &:hover {
79
- transform: translateY(-2px);
80
- box-shadow: ${props => props.$active ? '0 6px 16px rgba(34, 197, 94, 0.3)' : '0 3px 8px rgba(0, 0, 0, 0.15)'};
81
- }
82
-
83
- ${props => props.$active && `
84
- &::before {
85
- content: 'ACTIVE';
86
- position: absolute;
87
- top: 8px;
88
- right: 8px;
89
- background: ${theme.colors.success[600]};
90
- color: white;
91
- padding: 2px 8px;
92
- border-radius: 4px;
93
- font-size: 10px;
94
- font-weight: bold;
95
- }
96
- `}
97
-
98
- ${props => !props.$active && `
99
- &::before {
100
- content: 'INACTIVE';
101
- position: absolute;
102
- top: 8px;
103
- right: 8px;
104
- background: ${theme.colors.neutral[400]};
105
- color: white;
106
- padding: 2px 8px;
107
- border-radius: 4px;
108
- font-size: 10px;
109
- font-weight: bold;
110
- }
111
- `}
112
- `;
113
-
114
- const GreenToggle = styled.div`
115
- ${props => props.$isActive && `
116
- button[role="switch"] {
117
- background-color: #16A34A !important;
118
- border-color: #16A34A !important;
119
-
120
- &:hover {
121
- background-color: #15803D !important;
122
- border-color: #15803D !important;
123
- }
124
-
125
- &:focus {
126
- background-color: #16A34A !important;
127
- border-color: #16A34A !important;
128
- box-shadow: 0 0 0 3px rgba(22, 163, 74, 0.2) !important;
129
- }
130
- }
131
-
132
- /* Toggle handle */
133
- button[role="switch"] > span {
134
- background-color: white !important;
135
- }
136
- `}
137
-
138
- ${props => !props.$isActive && `
139
- button[role="switch"] {
140
- background-color: #E5E7EB;
141
-
142
- &:hover {
143
- background-color: #D1D5DB;
144
- }
145
- }
146
- `}
147
- `;
148
-
149
- // Template variable definitions
150
- const TEMPLATE_VARIABLES = {
151
- suspiciousLogin: [
152
- { var: '{{user.email}}', desc: 'User email address' },
153
- { var: '{{user.username}}', desc: 'Username' },
154
- { var: '{{session.loginTime}}', desc: 'Login timestamp' },
155
- { var: '{{session.ipAddress}}', desc: 'IP address' },
156
- { var: '{{geo.city}}', desc: 'City (if available)' },
157
- { var: '{{geo.country}}', desc: 'Country (if available)' },
158
- { var: '{{geo.timezone}}', desc: 'Timezone (if available)' },
159
- { var: '{{session.userAgent}}', desc: 'Browser/Device info' },
160
- { var: '{{reason.isVpn}}', desc: 'VPN detected (true/false)' },
161
- { var: '{{reason.isProxy}}', desc: 'Proxy detected (true/false)' },
162
- { var: '{{reason.isThreat}}', desc: 'Threat detected (true/false)' },
163
- { var: '{{reason.securityScore}}', desc: 'Security score (0-100)' },
164
- ],
165
- newLocation: [
166
- { var: '{{user.email}}', desc: 'User email address' },
167
- { var: '{{user.username}}', desc: 'Username' },
168
- { var: '{{session.loginTime}}', desc: 'Login timestamp' },
169
- { var: '{{session.ipAddress}}', desc: 'IP address' },
170
- { var: '{{geo.city}}', desc: 'City' },
171
- { var: '{{geo.country}}', desc: 'Country' },
172
- { var: '{{geo.timezone}}', desc: 'Timezone' },
173
- { var: '{{session.userAgent}}', desc: 'Browser/Device info' },
174
- ],
175
- vpnProxy: [
176
- { var: '{{user.email}}', desc: 'User email address' },
177
- { var: '{{user.username}}', desc: 'Username' },
178
- { var: '{{session.loginTime}}', desc: 'Login timestamp' },
179
- { var: '{{session.ipAddress}}', desc: 'IP address' },
180
- { var: '{{geo.city}}', desc: 'City (if available)' },
181
- { var: '{{geo.country}}', desc: 'Country (if available)' },
182
- { var: '{{session.userAgent}}', desc: 'Browser/Device info' },
183
- { var: '{{reason.isVpn}}', desc: 'VPN detected (true/false)' },
184
- { var: '{{reason.isProxy}}', desc: 'Proxy detected (true/false)' },
185
- ],
186
- };
187
-
188
- // Validate template variables
189
- const validateTemplate = (template, templateType) => {
190
- const requiredVars = TEMPLATE_VARIABLES[templateType];
191
- const foundVars = [];
192
-
193
- requiredVars.forEach(({ var: variable }) => {
194
- if (template.includes(variable)) {
195
- foundVars.push(variable);
196
- }
197
- });
198
-
199
- return {
200
- isValid: foundVars.length > 0,
201
- foundVars,
202
- totalAvailable: requiredVars.length,
203
- };
204
- };
205
-
206
- // Get default email templates
207
- const getDefaultTemplates = () => ({
208
- suspiciousLogin: {
209
- subject: '[ALERT] Suspicious Login Alert - Session Manager',
210
- html: `
211
- <html>
212
- <body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
213
- <div style="max-width: 600px; margin: 0 auto; padding: 20px; background-color: #f9fafb; border-radius: 10px;">
214
- <h2 style="color: #dc2626;">[ALERT] Suspicious Login Detected</h2>
215
- <p>A potentially suspicious login was detected for your account.</p>
216
-
217
- <div style="background: white; padding: 15px; border-radius: 8px; margin: 20px 0;">
218
- <h3 style="margin-top: 0;">Account Information:</h3>
219
- <ul>
220
- <li><strong>Email:</strong> {{user.email}}</li>
221
- <li><strong>Username:</strong> {{user.username}}</li>
222
- </ul>
223
-
224
- <h3>Login Details:</h3>
225
- <ul>
226
- <li><strong>Time:</strong> {{session.loginTime}}</li>
227
- <li><strong>IP Address:</strong> {{session.ipAddress}}</li>
228
- <li><strong>Location:</strong> {{geo.city}}, {{geo.country}}</li>
229
- <li><strong>Timezone:</strong> {{geo.timezone}}</li>
230
- <li><strong>Device:</strong> {{session.userAgent}}</li>
231
- </ul>
232
-
233
- <h3 style="color: #dc2626;">Security Alert:</h3>
234
- <ul>
235
- <li>VPN Detected: {{reason.isVpn}}</li>
236
- <li>Proxy Detected: {{reason.isProxy}}</li>
237
- <li>Threat Detected: {{reason.isThreat}}</li>
238
- <li>Security Score: {{reason.securityScore}}/100</li>
239
- </ul>
240
- </div>
241
-
242
- <p>If this was you, you can safely ignore this email. If you don't recognize this activity, please secure your account immediately.</p>
243
-
244
- <hr style="border: none; border-top: 1px solid #e5e7eb; margin: 20px 0;"/>
245
- <p style="color: #666; font-size: 12px;">This is an automated security notification from Magic Session Manager.</p>
246
- </div>
247
- </body>
248
- </html>`,
249
- text: `[ALERT] Suspicious Login Detected
250
-
251
- A potentially suspicious login was detected for your account.
252
-
253
- Account: {{user.email}}
254
- Username: {{user.username}}
255
-
256
- Login Details:
257
- - Time: {{session.loginTime}}
258
- - IP: {{session.ipAddress}}
259
- - Location: {{geo.city}}, {{geo.country}}
260
-
261
- Security: VPN={{reason.isVpn}}, Proxy={{reason.isProxy}}, Threat={{reason.isThreat}}, Score={{reason.securityScore}}/100`,
262
- },
263
- newLocation: {
264
- subject: '[LOCATION] New Location Login Detected',
265
- html: `
266
- <html>
267
- <body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
268
- <div style="max-width: 600px; margin: 0 auto; padding: 20px; background-color: #f0f9ff; border-radius: 10px;">
269
- <h2 style="color: #0284c7;">[LOCATION] Login from New Location</h2>
270
- <p>Your account was accessed from a new location.</p>
271
-
272
- <div style="background: white; padding: 15px; border-radius: 8px; margin: 20px 0;">
273
- <h3 style="margin-top: 0;">Account:</h3>
274
- <p><strong>{{user.email}}</strong></p>
275
-
276
- <h3>New Location Details:</h3>
277
- <ul>
278
- <li><strong>Time:</strong> {{session.loginTime}}</li>
279
- <li><strong>Location:</strong> {{geo.city}}, {{geo.country}}</li>
280
- <li><strong>IP Address:</strong> {{session.ipAddress}}</li>
281
- <li><strong>Device:</strong> {{session.userAgent}}</li>
282
- </ul>
283
- </div>
284
-
285
- <p>If this was you, no action is needed. If you don't recognize this login, please secure your account.</p>
286
-
287
- <hr style="border: none; border-top: 1px solid #e5e7eb; margin: 20px 0;"/>
288
- <p style="color: #666; font-size: 12px;">Magic Session Manager notification</p>
289
- </div>
290
- </body>
291
- </html>`,
292
- text: `[LOCATION] Login from New Location
293
-
294
- Your account was accessed from a new location.
295
-
296
- Account: {{user.email}}
297
-
298
- New Location Details:
299
- - Time: {{session.loginTime}}
300
- - Location: {{geo.city}}, {{geo.country}}
301
- - IP Address: {{session.ipAddress}}
302
- - Device: {{session.userAgent}}
303
-
304
- If this was you, no action is needed.`,
305
- },
306
- vpnProxy: {
307
- subject: '[WARNING] VPN/Proxy Login Detected',
308
- html: `
309
- <html>
310
- <body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
311
- <div style="max-width: 600px; margin: 0 auto; padding: 20px; background-color: #fffbeb; border-radius: 10px;">
312
- <h2 style="color: #d97706;">[WARNING] VPN/Proxy Detected</h2>
313
- <p>A login from a VPN or proxy service was detected on your account.</p>
314
-
315
- <div style="background: white; padding: 15px; border-radius: 8px; margin: 20px 0;">
316
- <h3 style="margin-top: 0;">Account:</h3>
317
- <p><strong>{{user.email}}</strong></p>
318
-
319
- <h3>Login Details:</h3>
320
- <ul>
321
- <li><strong>Time:</strong> {{session.loginTime}}</li>
322
- <li><strong>IP Address:</strong> {{session.ipAddress}}</li>
323
- <li><strong>Location:</strong> {{geo.city}}, {{geo.country}}</li>
324
- <li><strong>Device:</strong> {{session.userAgent}}</li>
325
- <li><strong>VPN:</strong> {{reason.isVpn}}</li>
326
- <li><strong>Proxy:</strong> {{reason.isProxy}}</li>
327
- </ul>
328
- </div>
329
-
330
- <p>VPN/Proxy usage may indicate suspicious activity. If this was you, you can safely ignore this email.</p>
331
-
332
- <hr style="border: none; border-top: 1px solid #e5e7eb; margin: 20px 0;"/>
333
- <p style="color: #666; font-size: 12px;">Magic Session Manager notification</p>
334
- </div>
335
- </body>
336
- </html>`,
337
- text: `[WARNING] VPN/Proxy Detected
338
-
339
- A login from a VPN or proxy service was detected on your account.
340
-
341
- Account: {{user.email}}
342
-
343
- Login Details:
344
- - Time: {{session.loginTime}}
345
- - IP Address: {{session.ipAddress}}
346
- - Location: {{geo.city}}, {{geo.country}}
347
- - VPN: {{reason.isVpn}}, Proxy: {{reason.isProxy}}`,
348
- },
349
- });
350
-
351
- const SettingsPage = () => {
352
- const { get, post } = useFetchClient();
353
- const { toggleNotification } = useNotification();
354
- const { isPremium, isAdvanced, isEnterprise } = useLicense();
355
- const [loading, setLoading] = useState(true);
356
- const [saving, setSaving] = useState(false);
357
- const [hasChanges, setHasChanges] = useState(false);
358
- const [cleaning, setCleaning] = useState(false);
359
- const [activeTemplateTab, setActiveTemplateTab] = useState('suspiciousLogin');
360
-
361
- const [settings, setSettings] = useState({
362
- inactivityTimeout: 15,
363
- cleanupInterval: 30,
364
- lastSeenRateLimit: 30,
365
- retentionDays: 90,
366
- enableGeolocation: true,
367
- enableSecurityScoring: true,
368
- blockSuspiciousSessions: false,
369
- maxFailedLogins: 5,
370
- enableEmailAlerts: false,
371
- alertOnSuspiciousLogin: true,
372
- alertOnNewLocation: true,
373
- alertOnVpnProxy: true,
374
- enableWebhooks: false,
375
- discordWebhookUrl: '',
376
- slackWebhookUrl: '',
377
- enableGeofencing: false,
378
- allowedCountries: [],
379
- blockedCountries: [],
380
- emailTemplates: {
381
- suspiciousLogin: { subject: '', html: '', text: '' },
382
- newLocation: { subject: '', html: '', text: '' },
383
- vpnProxy: { subject: '', html: '', text: '' },
384
- },
385
- });
386
-
387
- useEffect(() => {
388
- fetchSettings();
389
- }, []);
390
-
391
- const fetchSettings = async () => {
392
- setLoading(true);
393
- try {
394
- const saved = localStorage.getItem(`${pluginId}-settings`);
395
- if (saved) {
396
- const loadedSettings = JSON.parse(saved);
397
- if (!loadedSettings.emailTemplates) {
398
- loadedSettings.emailTemplates = getDefaultTemplates();
399
- }
400
- setSettings(loadedSettings);
401
- } else {
402
- setSettings(prev => ({ ...prev, emailTemplates: getDefaultTemplates() }));
403
- }
404
- } catch (err) {
405
- console.error('[Settings] Error loading:', err);
406
- } finally {
407
- setLoading(false);
408
- }
409
- };
410
-
411
- const handleChange = (key, value) => {
412
- setSettings(prev => ({ ...prev, [key]: value }));
413
- setHasChanges(true);
414
- };
415
-
416
- const updateSetting = (key, value) => {
417
- handleChange(key, value);
418
- };
419
-
420
- const handleSave = async () => {
421
- setSaving(true);
422
- try {
423
- localStorage.setItem(`${pluginId}-settings`, JSON.stringify(settings));
424
-
425
- toggleNotification({
426
- type: 'success',
427
- message: 'Settings saved successfully!',
428
- });
429
-
430
- setHasChanges(false);
431
- } catch (err) {
432
- toggleNotification({
433
- type: 'danger',
434
- message: 'Failed to save settings',
435
- });
436
- } finally {
437
- setSaving(false);
438
- }
439
- };
440
-
441
- const handleReset = () => {
442
- fetchSettings();
443
- setHasChanges(false);
444
- };
445
-
446
- const handleCleanInactive = async () => {
447
- if (!confirm('[WARNING] This will permanently delete ALL inactive sessions.\n\nContinue?')) {
448
- return;
449
- }
450
-
451
- setCleaning(true);
452
- try {
453
- const { data } = await post(`/${pluginId}/sessions/clean-inactive`);
454
-
455
- toggleNotification({
456
- type: 'success',
457
- message: `Successfully deleted ${data.deletedCount} inactive sessions!`,
458
- });
459
- } catch (err) {
460
- toggleNotification({
461
- type: 'danger',
462
- message: 'Failed to delete inactive sessions',
463
- });
464
- } finally {
465
- setCleaning(false);
466
- }
467
- };
468
-
469
- if (loading) {
470
- return (
471
- <Flex justifyContent="center" padding={8}>
472
- <Loader>Loading settings...</Loader>
473
- </Flex>
474
- );
475
- }
476
-
477
- return (
478
- <Container>
479
- {/* Sticky Header */}
480
- <StickySaveBar paddingTop={5} paddingBottom={5} paddingLeft={6} paddingRight={6}>
481
- <Flex justifyContent="space-between" alignItems="center">
482
- <Flex direction="column" gap={1} alignItems="flex-start">
483
- <Typography variant="alpha" fontWeight="bold" style={{ fontSize: '24px' }}>
484
- ⚙️ Session Manager Settings
485
- </Typography>
486
- <Typography variant="epsilon" textColor="neutral600">
487
- Configure session tracking, security, and email notifications
488
- </Typography>
489
- </Flex>
490
- <Flex gap={2}>
491
- {hasChanges && (
492
- <Button onClick={handleReset} variant="tertiary" size="L">
493
- Reset
494
- </Button>
495
- )}
496
- <Button
497
- onClick={handleSave}
498
- loading={saving}
499
- startIcon={<Check />}
500
- size="L"
501
- disabled={!hasChanges || saving}
502
- style={{
503
- background: hasChanges && !saving
504
- ? 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'
505
- : '#e5e7eb',
506
- color: hasChanges && !saving ? 'white' : '#9ca3af',
507
- fontWeight: '600',
508
- padding: '12px 24px',
509
- border: 'none',
510
- boxShadow: hasChanges && !saving ? '0 4px 12px rgba(102, 126, 234, 0.4)' : 'none',
511
- transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
512
- }}
513
- onMouseEnter={(e) => {
514
- if (hasChanges && !saving) {
515
- e.currentTarget.style.transform = 'translateY(-2px)';
516
- e.currentTarget.style.boxShadow = '0 8px 20px rgba(102, 126, 234, 0.5)';
517
- }
518
- }}
519
- onMouseLeave={(e) => {
520
- e.currentTarget.style.transform = 'translateY(0)';
521
- e.currentTarget.style.boxShadow = hasChanges && !saving ? '0 4px 12px rgba(102, 126, 234, 0.4)' : 'none';
522
- }}
523
- >
524
- {saving ? 'Saving...' : hasChanges ? 'Save Changes' : 'No Changes'}
525
- </Button>
526
- </Flex>
527
- </Flex>
528
- </StickySaveBar>
529
-
530
- {/* Content */}
531
- <Box paddingTop={6} paddingLeft={6} paddingRight={6} paddingBottom={10}>
532
-
533
- {/* Info Alert */}
534
- <Alert variant="default" title="Configuration Note" closeLabel="Close" style={{ marginBottom: '24px' }}>
535
- Changes require a server restart. Update config/plugins.ts for permanent changes.
536
- </Alert>
537
-
538
- {/* Accordion Layout */}
539
- <Accordion.Root type="multiple" defaultValue={['general', 'security', 'email']}>
540
-
541
- {/* General Settings */}
542
- <Accordion.Item value="general">
543
- <Accordion.Header>
544
- <Accordion.Trigger
545
- icon={Cog}
546
- description="Basic session tracking configuration"
547
- >
548
- General Settings
549
- </Accordion.Trigger>
550
- </Accordion.Header>
551
- <Accordion.Content>
552
- <Box padding={6}>
553
-
554
- {/* Session Timeout */}
555
- <Typography variant="sigma" fontWeight="bold" style={{ marginBottom: '16px', display: 'block', color: theme.colors.neutral[700] }}>
556
- SESSION TIMEOUT
557
- </Typography>
558
- <Grid.Root gap={6} style={{ marginBottom: '32px' }}>
559
- <Grid.Item col={6} s={12}>
560
- <Box>
561
- <Typography variant="pi" fontWeight="bold" style={{ marginBottom: '8px', display: 'block' }}>
562
- Inactivity Timeout
563
- </Typography>
564
- <SingleSelect
565
- value={String(settings.inactivityTimeout)}
566
- onChange={(value) => handleChange('inactivityTimeout', parseInt(value))}
567
- >
568
- <SingleSelectOption value="5">5 minutes (Very Strict)</SingleSelectOption>
569
- <SingleSelectOption value="10">10 minutes (Strict)</SingleSelectOption>
570
- <SingleSelectOption value="15">15 minutes (Recommended)</SingleSelectOption>
571
- <SingleSelectOption value="30">30 minutes (Moderate)</SingleSelectOption>
572
- <SingleSelectOption value="60">1 hour (Relaxed)</SingleSelectOption>
573
- <SingleSelectOption value="120">2 hours (Very Relaxed)</SingleSelectOption>
574
- </SingleSelect>
575
- <Typography variant="pi" textColor="neutral600" style={{ fontSize: '11px', marginTop: '8px' }}>
576
- Sessions inactive for more than {settings.inactivityTimeout} minutes will be marked as offline
577
- </Typography>
578
- </Box>
579
- </Grid.Item>
580
-
581
- <Grid.Item col={6} s={12}>
582
- <Box>
583
- <Typography variant="pi" fontWeight="bold" style={{ marginBottom: '8px', display: 'block' }}>
584
- Last Seen Rate Limit
585
- </Typography>
586
- <SingleSelect
587
- value={String(settings.lastSeenRateLimit)}
588
- onChange={(value) => handleChange('lastSeenRateLimit', parseInt(value))}
589
- >
590
- <SingleSelectOption value="10">10 seconds</SingleSelectOption>
591
- <SingleSelectOption value="30">30 seconds (Recommended)</SingleSelectOption>
592
- <SingleSelectOption value="60">1 minute</SingleSelectOption>
593
- <SingleSelectOption value="120">2 minutes</SingleSelectOption>
594
- <SingleSelectOption value="300">5 minutes</SingleSelectOption>
595
- </SingleSelect>
596
- <Typography variant="pi" textColor="neutral600" style={{ fontSize: '11px', marginTop: '8px' }}>
597
- Prevents excessive database writes. Updates throttled to once every {settings.lastSeenRateLimit} seconds
598
- </Typography>
599
- </Box>
600
- </Grid.Item>
601
- </Grid.Root>
602
-
603
- {/* Cleanup & Retention */}
604
- <Divider style={{ marginBottom: '24px' }} />
605
- <Typography variant="sigma" fontWeight="bold" style={{ marginBottom: '16px', display: 'block', color: theme.colors.neutral[700] }}>
606
- 🧹 AUTO-CLEANUP & RETENTION
607
- </Typography>
608
- <Grid.Root gap={6}>
609
- <Grid.Item col={6} s={12}>
610
- <Box>
611
- <Typography variant="pi" fontWeight="bold" style={{ marginBottom: '8px', display: 'block' }}>
612
- Cleanup Interval
613
- </Typography>
614
- <SingleSelect
615
- value={String(settings.cleanupInterval)}
616
- onChange={(value) => handleChange('cleanupInterval', parseInt(value))}
617
- >
618
- <SingleSelectOption value="15">15 minutes</SingleSelectOption>
619
- <SingleSelectOption value="30">30 minutes (Recommended)</SingleSelectOption>
620
- <SingleSelectOption value="60">1 hour</SingleSelectOption>
621
- <SingleSelectOption value="120">2 hours</SingleSelectOption>
622
- </SingleSelect>
623
- <Typography variant="pi" textColor="neutral600" style={{ fontSize: '11px', marginTop: '8px' }}>
624
- Inactive sessions are automatically cleaned every {settings.cleanupInterval} minutes
625
- </Typography>
626
- </Box>
627
- </Grid.Item>
628
-
629
- <Grid.Item col={6} s={12}>
630
- <Box>
631
- <Typography variant="pi" fontWeight="bold" style={{ marginBottom: '8px', display: 'block' }}>
632
- Retention Period
633
- </Typography>
634
- <SingleSelect
635
- value={String(settings.retentionDays)}
636
- onChange={(value) => handleChange('retentionDays', parseInt(value))}
637
- >
638
- <SingleSelectOption value="7">7 days</SingleSelectOption>
639
- <SingleSelectOption value="30">30 days</SingleSelectOption>
640
- <SingleSelectOption value="60">60 days</SingleSelectOption>
641
- <SingleSelectOption value="90">90 days (Recommended)</SingleSelectOption>
642
- <SingleSelectOption value="180">180 days</SingleSelectOption>
643
- <SingleSelectOption value="365">1 year</SingleSelectOption>
644
- <SingleSelectOption value="-1">Forever</SingleSelectOption>
645
- </SingleSelect>
646
- <Typography variant="pi" textColor="neutral600" style={{ fontSize: '11px', marginTop: '8px' }}>
647
- Old sessions deleted after {settings.retentionDays === -1 ? 'never' : `${settings.retentionDays} days`}
648
- </Typography>
649
- </Box>
650
- </Grid.Item>
651
-
652
- <Grid.Item col={12}>
653
- <Box padding={4} background="danger100" style={{ borderRadius: theme.borderRadius.md, border: `2px solid ${theme.colors.danger[200]}` }}>
654
- <Flex gap={3} alignItems="flex-start">
655
- <Trash style={{ width: '18px', height: '18px', color: theme.colors.danger[600], flexShrink: 0, marginTop: '2px' }} />
656
- <Box style={{ flex: 1 }}>
657
- <Typography variant="omega" fontWeight="bold" textColor="danger700" style={{ marginBottom: '8px', display: 'block' }}>
658
- Danger Zone
659
- </Typography>
660
- <Typography variant="pi" textColor="danger600" style={{ fontSize: '13px', lineHeight: '1.7' }}>
661
- <strong>Clean All Inactive:</strong> Permanently deletes all inactive sessions. This cannot be undone.
662
- </Typography>
663
- </Box>
664
- <Button
665
- onClick={handleCleanInactive}
666
- loading={cleaning}
667
- startIcon={<Trash />}
668
- variant="danger"
669
- size="S"
670
- style={{ flexShrink: 0 }}
671
- >
672
- Clean Now
673
- </Button>
674
- </Flex>
675
- </Box>
676
- </Grid.Item>
677
- </Grid.Root>
678
-
679
- </Box>
680
- </Accordion.Content>
681
- </Accordion.Item>
682
-
683
- {/* Security Settings */}
684
- <Accordion.Item value="security">
685
- <Accordion.Header>
686
- <Accordion.Trigger
687
- icon={Shield}
688
- description="Security policies and threat protection"
689
- >
690
- Security Settings
691
- </Accordion.Trigger>
692
- </Accordion.Header>
693
- <Accordion.Content>
694
- <Box padding={6}>
695
-
696
- <Typography variant="sigma" fontWeight="bold" style={{ marginBottom: '16px', display: 'block', color: theme.colors.neutral[700] }}>
697
- SECURITY OPTIONS
698
- </Typography>
699
-
700
- {/* Feature Toggles */}
701
- <Box background="neutral100" padding={5} style={{ borderRadius: theme.borderRadius.md, marginBottom: '32px' }}>
702
- <Grid.Root gap={4}>
703
- <Grid.Item col={6} s={12}>
704
- <ToggleCard
705
- $active={settings.blockSuspiciousSessions}
706
- onClick={() => handleChange('blockSuspiciousSessions', !settings.blockSuspiciousSessions)}
707
- >
708
- <Flex direction="column" gap={2}>
709
- <Flex justifyContent="space-between" alignItems="center">
710
- <Typography variant="omega" fontWeight="bold" textColor={settings.blockSuspiciousSessions ? 'success700' : 'neutral700'}>
711
- Block Suspicious Sessions
712
- </Typography>
713
- <GreenToggle $isActive={settings.blockSuspiciousSessions}>
714
- <Toggle
715
- checked={settings.blockSuspiciousSessions}
716
- onChange={() => handleChange('blockSuspiciousSessions', !settings.blockSuspiciousSessions)}
717
- />
718
- </GreenToggle>
719
- </Flex>
720
- <Typography variant="pi" textColor="neutral600" style={{ fontSize: '12px' }}>
721
- Automatically block sessions from VPNs, proxies, or threat IPs
722
- </Typography>
723
- </Flex>
724
- </ToggleCard>
725
- </Grid.Item>
726
-
727
- {isPremium && (
728
- <>
729
- <Grid.Item col={6} s={12}>
730
- <ToggleCard
731
- $active={settings.enableGeolocation}
732
- onClick={() => handleChange('enableGeolocation', !settings.enableGeolocation)}
733
- >
734
- <Flex direction="column" gap={2}>
735
- <Flex justifyContent="space-between" alignItems="center">
736
- <Typography variant="omega" fontWeight="bold" textColor={settings.enableGeolocation ? 'success700' : 'neutral700'}>
737
- IP Geolocation
738
- </Typography>
739
- <GreenToggle $isActive={settings.enableGeolocation}>
740
- <Toggle
741
- checked={settings.enableGeolocation}
742
- onChange={() => handleChange('enableGeolocation', !settings.enableGeolocation)}
743
- />
744
- </GreenToggle>
745
- </Flex>
746
- <Typography variant="pi" textColor="neutral600" style={{ fontSize: '12px' }}>
747
- Fetch location data for each session (Premium)
748
- </Typography>
749
- </Flex>
750
- </ToggleCard>
751
- </Grid.Item>
752
-
753
- <Grid.Item col={6} s={12}>
754
- <ToggleCard
755
- $active={settings.enableSecurityScoring}
756
- onClick={() => handleChange('enableSecurityScoring', !settings.enableSecurityScoring)}
757
- >
758
- <Flex direction="column" gap={2}>
759
- <Flex justifyContent="space-between" alignItems="center">
760
- <Typography variant="omega" fontWeight="bold" textColor={settings.enableSecurityScoring ? 'success700' : 'neutral700'}>
761
- Security Scoring
762
- </Typography>
763
- <GreenToggle $isActive={settings.enableSecurityScoring}>
764
- <Toggle
765
- checked={settings.enableSecurityScoring}
766
- onChange={() => handleChange('enableSecurityScoring', !settings.enableSecurityScoring)}
767
- />
768
- </GreenToggle>
769
- </Flex>
770
- <Typography variant="pi" textColor="neutral600" style={{ fontSize: '12px' }}>
771
- Calculate security scores and detect threats (Premium)
772
- </Typography>
773
- </Flex>
774
- </ToggleCard>
775
- </Grid.Item>
776
- </>
777
- )}
778
- </Grid.Root>
779
- </Box>
780
-
781
- {/* Max Failed Logins */}
782
- <Grid.Root gap={6}>
783
- <Grid.Item col={6} s={12}>
784
- <Box>
785
- <Typography variant="pi" fontWeight="bold" style={{ marginBottom: '8px', display: 'block' }}>
786
- 🚫 Max Failed Login Attempts
787
- </Typography>
788
- <NumberInput
789
- value={settings.maxFailedLogins}
790
- onValueChange={(val) => handleChange('maxFailedLogins', val)}
791
- min={1}
792
- max={20}
793
- />
794
- <Box padding={2} background="warning50" style={{ borderRadius: '4px', marginTop: '8px' }}>
795
- <Typography variant="pi" textColor="warning700" style={{ fontSize: '11px' }}>
796
- User will be blocked after {settings.maxFailedLogins} failed attempts
797
- </Typography>
798
- </Box>
799
- </Box>
800
- </Grid.Item>
801
- </Grid.Root>
802
-
803
- </Box>
804
- </Accordion.Content>
805
- </Accordion.Item>
806
-
807
- {/* Email Notifications - Advanced Only */}
808
- {isAdvanced && (
809
- <Accordion.Item value="email">
810
- <Accordion.Header>
811
- <Accordion.Trigger
812
- icon={Mail}
813
- description="Email alerts for security events"
814
- >
815
- Email Notifications (Advanced)
816
- </Accordion.Trigger>
817
- </Accordion.Header>
818
- <Accordion.Content>
819
- <Box padding={6}>
820
-
821
- {/* Email Alerts Toggle */}
822
- <Box background="neutral100" padding={5} style={{ borderRadius: theme.borderRadius.md, marginBottom: '32px' }}>
823
- <Typography variant="sigma" fontWeight="bold" style={{ marginBottom: '8px', display: 'block', textAlign: 'center', color: theme.colors.neutral[700] }}>
824
- 📧 EMAIL ALERTS
825
- </Typography>
826
- <Typography variant="pi" textColor="neutral600" style={{ marginBottom: '20px', display: 'block', textAlign: 'center', fontSize: '12px' }}>
827
- Send security alerts to users via email
828
- </Typography>
829
- <Grid.Root gap={4}>
830
- <Grid.Item col={12}>
831
- <ToggleCard
832
- $active={settings.enableEmailAlerts}
833
- onClick={() => handleChange('enableEmailAlerts', !settings.enableEmailAlerts)}
834
- >
835
- <Flex direction="column" gap={2}>
836
- <Flex justifyContent="space-between" alignItems="center">
837
- <Typography variant="omega" fontWeight="bold" textColor={settings.enableEmailAlerts ? 'success700' : 'neutral700'}>
838
- Enable Email Alerts
839
- </Typography>
840
- <GreenToggle $isActive={settings.enableEmailAlerts}>
841
- <Toggle
842
- checked={settings.enableEmailAlerts}
843
- onChange={() => handleChange('enableEmailAlerts', !settings.enableEmailAlerts)}
844
- />
845
- </GreenToggle>
846
- </Flex>
847
- <Typography variant="pi" textColor="neutral600" style={{ fontSize: '12px' }}>
848
- Send security alerts for suspicious logins, new locations, and VPN/Proxy usage
849
- </Typography>
850
- </Flex>
851
- </ToggleCard>
852
- </Grid.Item>
853
- </Grid.Root>
854
- </Box>
855
-
856
- {/* Alert Type Checkboxes */}
857
- {settings.enableEmailAlerts && (
858
- <>
859
- <Typography variant="sigma" fontWeight="bold" style={{ marginBottom: '16px', display: 'block', color: theme.colors.neutral[700] }}>
860
- ⚙️ ALERT TYPES
861
- </Typography>
862
- <Grid.Root gap={4} style={{ marginBottom: '32px' }}>
863
- <Grid.Item col={4} s={12}>
864
- <Box padding={3} background="neutral50" style={{ borderRadius: theme.borderRadius.md, border: '1px solid #E5E7EB' }}>
865
- <Checkbox
866
- checked={settings.alertOnSuspiciousLogin}
867
- onChange={() => handleChange('alertOnSuspiciousLogin', !settings.alertOnSuspiciousLogin)}
868
- >
869
- Suspicious Login
870
- </Checkbox>
871
- </Box>
872
- </Grid.Item>
873
- <Grid.Item col={4} s={12}>
874
- <Box padding={3} background="neutral50" style={{ borderRadius: theme.borderRadius.md, border: '1px solid #E5E7EB' }}>
875
- <Checkbox
876
- checked={settings.alertOnNewLocation}
877
- onChange={() => handleChange('alertOnNewLocation', !settings.alertOnNewLocation)}
878
- >
879
- New Location
880
- </Checkbox>
881
- </Box>
882
- </Grid.Item>
883
- <Grid.Item col={4} s={12}>
884
- <Box padding={3} background="neutral50" style={{ borderRadius: theme.borderRadius.md, border: '1px solid #E5E7EB' }}>
885
- <Checkbox
886
- checked={settings.alertOnVpnProxy}
887
- onChange={() => handleChange('alertOnVpnProxy', !settings.alertOnVpnProxy)}
888
- >
889
- VPN/Proxy
890
- </Checkbox>
891
- </Box>
892
- </Grid.Item>
893
- </Grid.Root>
894
-
895
- {/* Email Templates */}
896
- <Divider style={{ marginBottom: '24px' }} />
897
- <Typography variant="sigma" fontWeight="bold" style={{ marginBottom: '8px', display: 'block', color: theme.colors.neutral[700] }}>
898
- EMAIL TEMPLATES
899
- </Typography>
900
- <Typography variant="pi" textColor="neutral600" style={{ marginBottom: '20px', display: 'block', fontSize: '12px' }}>
901
- Customize email notification templates with dynamic variables
902
- </Typography>
903
-
904
- {/* Template Tabs */}
905
- <Tabs.Root value={activeTemplateTab} onValueChange={setActiveTemplateTab}>
906
- <Tabs.List aria-label="Email Templates">
907
- <Tabs.Trigger value="suspiciousLogin">Suspicious Login</Tabs.Trigger>
908
- <Tabs.Trigger value="newLocation">New Location</Tabs.Trigger>
909
- <Tabs.Trigger value="vpnProxy">VPN/Proxy</Tabs.Trigger>
910
- </Tabs.List>
911
-
912
- {Object.keys(settings.emailTemplates).map((templateKey) => (
913
- <Tabs.Content key={templateKey} value={templateKey}>
914
- <Box paddingTop={4}>
915
- {/* Subject */}
916
- <Box style={{ marginBottom: '24px' }}>
917
- <Typography variant="pi" fontWeight="bold" style={{ marginBottom: '8px', display: 'block' }}>
918
- Email Subject
919
- </Typography>
920
- <TextInput
921
- value={settings.emailTemplates[templateKey].subject}
922
- onChange={(e) => {
923
- const newTemplates = { ...settings.emailTemplates };
924
- newTemplates[templateKey].subject = e.target.value;
925
- handleChange('emailTemplates', newTemplates);
926
- }}
927
- placeholder="Enter email subject..."
928
- />
929
- </Box>
930
-
931
- {/* Available Variables */}
932
- <Box
933
- padding={3}
934
- background="primary100"
935
- style={{ borderRadius: theme.borderRadius.md, marginBottom: '20px', border: '2px solid #BAE6FD' }}
936
- >
937
- <Flex direction="column" gap={2}>
938
- <Flex alignItems="center" gap={2}>
939
- <Code style={{ width: '16px', height: '16px', color: theme.colors.primary[600] }} />
940
- <Typography variant="omega" fontWeight="bold" textColor="primary600">
941
- Available Variables (click to copy)
942
- </Typography>
943
- </Flex>
944
- <Flex gap={2} wrap="wrap">
945
- {TEMPLATE_VARIABLES[templateKey].map(({ var: variable, desc }) => (
946
- <Button
947
- key={variable}
948
- size="S"
949
- variant="tertiary"
950
- onClick={() => {
951
- navigator.clipboard.writeText(variable);
952
- toggleNotification({ type: 'success', message: `${variable} copied!` });
953
- }}
954
- style={{
955
- fontFamily: 'monospace',
956
- fontSize: '11px',
957
- padding: '4px 8px',
958
- }}
959
- title={desc}
960
- >
961
- {variable}
962
- </Button>
963
- ))}
964
- </Flex>
965
- </Flex>
966
- </Box>
967
-
968
- {/* HTML Template */}
969
- <Box style={{ marginBottom: '24px' }}>
970
- <Flex justifyContent="space-between" alignItems="center" style={{ marginBottom: '10px' }}>
971
- <Typography variant="pi" fontWeight="bold">
972
- 🎨 HTML Template
973
- </Typography>
974
- <Button
975
- size="S"
976
- variant="secondary"
977
- onClick={() => {
978
- const validation = validateTemplate(settings.emailTemplates[templateKey].html, templateKey);
979
- toggleNotification({
980
- type: validation.isValid ? 'success' : 'warning',
981
- message: validation.isValid
982
- ? `Template valid! Found ${validation.foundVars.length} variables.`
983
- : 'No variables found in template. Add at least one variable.',
984
- });
985
- }}
986
- >
987
- ✓ Validate
988
- </Button>
989
- </Flex>
990
- <Textarea
991
- value={settings.emailTemplates[templateKey].html}
992
- onChange={(e) => {
993
- const newTemplates = { ...settings.emailTemplates };
994
- newTemplates[templateKey].html = e.target.value;
995
- handleChange('emailTemplates', newTemplates);
996
- }}
997
- style={{
998
- fontFamily: 'Monaco, Consolas, monospace',
999
- fontSize: '12px',
1000
- minHeight: '250px',
1001
- lineHeight: '1.8',
1002
- }}
1003
- placeholder="Enter HTML template with variables like {{user.email}}..."
1004
- />
1005
- <Typography variant="pi" textColor="neutral600" style={{ fontSize: '11px', marginTop: '8px' }}>
1006
- Use HTML formatting and insert variables from the list above
1007
- </Typography>
1008
- </Box>
1009
-
1010
- {/* Text Template */}
1011
- <Box style={{ marginBottom: '24px' }}>
1012
- <Typography variant="pi" fontWeight="bold" style={{ marginBottom: '8px', display: 'block' }}>
1013
- 📄 Text Template (Fallback)
1014
- </Typography>
1015
- <Textarea
1016
- value={settings.emailTemplates[templateKey].text}
1017
- onChange={(e) => {
1018
- const newTemplates = { ...settings.emailTemplates };
1019
- newTemplates[templateKey].text = e.target.value;
1020
- handleChange('emailTemplates', newTemplates);
1021
- }}
1022
- style={{
1023
- fontFamily: 'Monaco, Consolas, monospace',
1024
- fontSize: '12px',
1025
- minHeight: '150px',
1026
- lineHeight: '1.8',
1027
- }}
1028
- placeholder="Plain text version (no HTML)..."
1029
- />
1030
- <Typography variant="pi" textColor="neutral600" style={{ fontSize: '11px', marginTop: '8px' }}>
1031
- Plain text version for email clients that don't support HTML
1032
- </Typography>
1033
- </Box>
1034
-
1035
- {/* Load Default Template Button */}
1036
- <Button
1037
- size="S"
1038
- variant="secondary"
1039
- onClick={() => {
1040
- const defaultTemplates = getDefaultTemplates();
1041
- const newTemplates = { ...settings.emailTemplates };
1042
- newTemplates[templateKey] = defaultTemplates[templateKey];
1043
- handleChange('emailTemplates', newTemplates);
1044
- toggleNotification({ type: 'success', message: 'Default template loaded!' });
1045
- }}
1046
- >
1047
- 📋 Load Default Template
1048
- </Button>
1049
- </Box>
1050
- </Tabs.Content>
1051
- ))}
1052
- </Tabs.Root>
1053
- </>
1054
- )}
1055
-
1056
- </Box>
1057
- </Accordion.Content>
1058
- </Accordion.Item>
1059
- )}
1060
-
1061
- {/* Webhooks - Advanced Only */}
1062
- {isAdvanced && (
1063
- <Accordion.Item value="webhooks">
1064
- <Accordion.Header>
1065
- <Accordion.Trigger
1066
- icon={Code}
1067
- description="Discord & Slack integration"
1068
- >
1069
- Webhook Integration (Advanced)
1070
- </Accordion.Trigger>
1071
- </Accordion.Header>
1072
- <Accordion.Content>
1073
- <Box padding={6}>
1074
-
1075
- {/* Enable Webhooks Toggle */}
1076
- <Box background="neutral100" padding={5} style={{ borderRadius: theme.borderRadius.md, marginBottom: '32px' }}>
1077
- <Grid.Root gap={4}>
1078
- <Grid.Item col={12}>
1079
- <ToggleCard
1080
- $active={settings.enableWebhooks}
1081
- onClick={() => handleChange('enableWebhooks', !settings.enableWebhooks)}
1082
- >
1083
- <Flex direction="column" gap={2}>
1084
- <Flex justifyContent="space-between" alignItems="center">
1085
- <Typography variant="omega" fontWeight="bold" textColor={settings.enableWebhooks ? 'success700' : 'neutral700'}>
1086
- Enable Webhooks
1087
- </Typography>
1088
- <GreenToggle $isActive={settings.enableWebhooks}>
1089
- <Toggle
1090
- checked={settings.enableWebhooks}
1091
- onChange={() => handleChange('enableWebhooks', !settings.enableWebhooks)}
1092
- />
1093
- </GreenToggle>
1094
- </Flex>
1095
- <Typography variant="pi" textColor="neutral600" style={{ fontSize: '12px' }}>
1096
- Send session events to Discord, Slack, or custom endpoints
1097
- </Typography>
1098
- </Flex>
1099
- </ToggleCard>
1100
- </Grid.Item>
1101
- </Grid.Root>
1102
- </Box>
1103
-
1104
- {/* Webhook URLs */}
1105
- {settings.enableWebhooks && (
1106
- <Grid.Root gap={6}>
1107
- <Grid.Item col={12}>
1108
- <Box>
1109
- <Typography variant="pi" fontWeight="bold" style={{ marginBottom: '8px', display: 'block' }}>
1110
- Discord Webhook URL
1111
- </Typography>
1112
- <TextInput
1113
- placeholder="https://discord.com/api/webhooks/..."
1114
- value={settings.discordWebhookUrl}
1115
- onChange={(e) => handleChange('discordWebhookUrl', e.target.value)}
1116
- />
1117
- <Typography variant="pi" textColor="neutral600" style={{ fontSize: '11px', marginTop: '8px' }}>
1118
- Optional: Post session alerts to Discord
1119
- </Typography>
1120
- </Box>
1121
- </Grid.Item>
1122
-
1123
- <Grid.Item col={12}>
1124
- <Box>
1125
- <Typography variant="pi" fontWeight="bold" style={{ marginBottom: '8px', display: 'block' }}>
1126
- Slack Webhook URL
1127
- </Typography>
1128
- <TextInput
1129
- placeholder="https://hooks.slack.com/services/..."
1130
- value={settings.slackWebhookUrl}
1131
- onChange={(e) => handleChange('slackWebhookUrl', e.target.value)}
1132
- />
1133
- <Typography variant="pi" textColor="neutral600" style={{ fontSize: '11px', marginTop: '8px' }}>
1134
- Optional: Post session alerts to Slack
1135
- </Typography>
1136
- </Box>
1137
- </Grid.Item>
1138
- </Grid.Root>
1139
- )}
1140
-
1141
- </Box>
1142
- </Accordion.Content>
1143
- </Accordion.Item>
1144
- )}
1145
-
1146
- </Accordion.Root>
1147
-
1148
- {/* Footer Info */}
1149
- <Box padding={5} background="neutral100" style={{ borderRadius: theme.borderRadius.md, marginTop: '32px' }}>
1150
- <Flex gap={3} alignItems="flex-start">
1151
- <Information style={{ width: '20px', height: '20px', color: theme.colors.neutral[600], flexShrink: 0, marginTop: '2px' }} />
1152
- <Box style={{ flex: 1 }}>
1153
- <Typography variant="omega" fontWeight="bold" style={{ marginBottom: '8px', display: 'block' }}>
1154
- How to Apply These Settings
1155
- </Typography>
1156
- <Typography variant="pi" textColor="neutral600" style={{ fontSize: '13px', lineHeight: '1.8' }}>
1157
- Settings are saved in your browser. To apply permanently, copy the config below and paste it into{' '}
1158
- <code style={{ background: '#e5e7eb', padding: '2px 6px', borderRadius: '4px' }}>config/plugins.ts</code>, then restart.
1159
- </Typography>
1160
- <Button
1161
- onClick={async () => {
1162
- const configCode = `'magic-sessionmanager': {
1163
- config: {
1164
- inactivityTimeout: ${settings.inactivityTimeout * 60 * 1000}, // ${settings.inactivityTimeout} min
1165
- cleanupInterval: ${settings.cleanupInterval * 60 * 1000}, // ${settings.cleanupInterval} min
1166
- lastSeenRateLimit: ${settings.lastSeenRateLimit * 1000}, // ${settings.lastSeenRateLimit} sec
1167
- retentionDays: ${settings.retentionDays},
1168
- enableGeolocation: ${settings.enableGeolocation},
1169
- enableSecurityScoring: ${settings.enableSecurityScoring},
1170
- blockSuspiciousSessions: ${settings.blockSuspiciousSessions},
1171
- maxFailedLogins: ${settings.maxFailedLogins},
1172
- enableEmailAlerts: ${settings.enableEmailAlerts},
1173
- alertOnSuspiciousLogin: ${settings.alertOnSuspiciousLogin},
1174
- alertOnNewLocation: ${settings.alertOnNewLocation},
1175
- alertOnVpnProxy: ${settings.alertOnVpnProxy},
1176
- enableWebhooks: ${settings.enableWebhooks},
1177
- }
1178
- }`;
1179
-
1180
- try {
1181
- await navigator.clipboard.writeText(configCode);
1182
- toggleNotification({ type: 'success', message: 'Config copied to clipboard!' });
1183
- } catch (err) {
1184
- toggleNotification({ type: 'danger', message: 'Failed to copy' });
1185
- }
1186
- }}
1187
- startIcon={<Duplicate />}
1188
- size="S"
1189
- variant="secondary"
1190
- style={{ marginTop: '16px' }}
1191
- >
1192
- Copy Config
1193
- </Button>
1194
- </Box>
1195
- </Flex>
1196
- </Box>
1197
-
1198
- </Box>
1199
- </Container>
1200
- );
1201
- };
1202
-
1203
- export default SettingsPage;
1204
-