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.
- package/dist/server/index.js +1 -1
- package/dist/server/index.mjs +1 -1
- package/package.json +1 -3
- package/admin/jsconfig.json +0 -10
- package/admin/src/components/Initializer.jsx +0 -11
- package/admin/src/components/LicenseGuard.jsx +0 -591
- package/admin/src/components/OnlineUsersWidget.jsx +0 -212
- package/admin/src/components/PluginIcon.jsx +0 -8
- package/admin/src/components/SessionDetailModal.jsx +0 -449
- package/admin/src/components/SessionInfoCard.jsx +0 -151
- package/admin/src/components/SessionInfoPanel.jsx +0 -385
- package/admin/src/components/index.jsx +0 -5
- package/admin/src/hooks/useLicense.js +0 -103
- package/admin/src/index.js +0 -149
- package/admin/src/pages/ActiveSessions.jsx +0 -12
- package/admin/src/pages/Analytics.jsx +0 -735
- package/admin/src/pages/App.jsx +0 -12
- package/admin/src/pages/HomePage.jsx +0 -1212
- package/admin/src/pages/License.jsx +0 -603
- package/admin/src/pages/Settings.jsx +0 -1646
- package/admin/src/pages/SettingsNew.jsx +0 -1204
- package/admin/src/pages/UpgradePage.jsx +0 -448
- package/admin/src/pages/index.jsx +0 -3
- package/admin/src/pluginId.js +0 -4
- package/admin/src/translations/de.json +0 -299
- package/admin/src/translations/en.json +0 -299
- package/admin/src/translations/es.json +0 -287
- package/admin/src/translations/fr.json +0 -287
- package/admin/src/translations/pt.json +0 -287
- package/admin/src/utils/getTranslation.js +0 -5
- package/admin/src/utils/index.js +0 -2
- package/admin/src/utils/parseUserAgent.js +0 -79
- package/admin/src/utils/theme.js +0 -85
- package/server/jsconfig.json +0 -10
- package/server/src/bootstrap.js +0 -492
- package/server/src/config/index.js +0 -23
- package/server/src/content-types/index.js +0 -9
- package/server/src/content-types/session/schema.json +0 -84
- package/server/src/controllers/controller.js +0 -11
- package/server/src/controllers/index.js +0 -11
- package/server/src/controllers/license.js +0 -266
- package/server/src/controllers/session.js +0 -433
- package/server/src/controllers/settings.js +0 -122
- package/server/src/destroy.js +0 -22
- package/server/src/index.js +0 -23
- package/server/src/middlewares/index.js +0 -5
- package/server/src/middlewares/last-seen.js +0 -62
- package/server/src/policies/index.js +0 -3
- package/server/src/register.js +0 -36
- package/server/src/routes/admin.js +0 -149
- package/server/src/routes/content-api.js +0 -60
- package/server/src/routes/index.js +0 -9
- package/server/src/services/geolocation.js +0 -182
- package/server/src/services/index.js +0 -13
- package/server/src/services/license-guard.js +0 -316
- package/server/src/services/notifications.js +0 -319
- package/server/src/services/service.js +0 -7
- package/server/src/services/session.js +0 -393
- package/server/src/utils/encryption.js +0 -121
- package/server/src/utils/getClientIp.js +0 -118
- package/server/src/utils/logger.js +0 -84
|
@@ -1,1212 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react';
|
|
2
|
-
import { useIntl } from 'react-intl';
|
|
3
|
-
import { useFetchClient, useNotification } from '@strapi/strapi/admin';
|
|
4
|
-
import styled, { keyframes, css } from 'styled-components';
|
|
5
|
-
import { getTranslation } from '../utils/getTranslation';
|
|
6
|
-
import { theme } from '../utils/theme';
|
|
7
|
-
import {
|
|
8
|
-
Box,
|
|
9
|
-
Button,
|
|
10
|
-
Flex,
|
|
11
|
-
Typography,
|
|
12
|
-
Loader,
|
|
13
|
-
Table,
|
|
14
|
-
Thead,
|
|
15
|
-
Tbody,
|
|
16
|
-
Tr,
|
|
17
|
-
Td,
|
|
18
|
-
Th,
|
|
19
|
-
Badge,
|
|
20
|
-
SingleSelect,
|
|
21
|
-
SingleSelectOption,
|
|
22
|
-
} from '@strapi/design-system';
|
|
23
|
-
import {
|
|
24
|
-
Check,
|
|
25
|
-
Cross,
|
|
26
|
-
Clock,
|
|
27
|
-
User,
|
|
28
|
-
Monitor,
|
|
29
|
-
Phone,
|
|
30
|
-
Server,
|
|
31
|
-
Sparkle,
|
|
32
|
-
Trash,
|
|
33
|
-
Search,
|
|
34
|
-
Eye,
|
|
35
|
-
Download,
|
|
36
|
-
} from '@strapi/icons';
|
|
37
|
-
import pluginId from '../pluginId';
|
|
38
|
-
import parseUserAgent from '../utils/parseUserAgent';
|
|
39
|
-
import SessionDetailModal from '../components/SessionDetailModal';
|
|
40
|
-
import { useLicense } from '../hooks/useLicense';
|
|
41
|
-
|
|
42
|
-
// ================ ANIMATIONS ================
|
|
43
|
-
const fadeIn = keyframes`
|
|
44
|
-
from { opacity: 0; transform: translateY(10px); }
|
|
45
|
-
to { opacity: 1; transform: translateY(0); }
|
|
46
|
-
`;
|
|
47
|
-
|
|
48
|
-
const shimmer = keyframes`
|
|
49
|
-
0% { background-position: -200% 0; }
|
|
50
|
-
100% { background-position: 200% 0; }
|
|
51
|
-
`;
|
|
52
|
-
|
|
53
|
-
const float = keyframes`
|
|
54
|
-
0%, 100% { transform: translateY(0px); }
|
|
55
|
-
50% { transform: translateY(-5px); }
|
|
56
|
-
`;
|
|
57
|
-
|
|
58
|
-
const pulse = keyframes`
|
|
59
|
-
0%, 100% { opacity: 1; }
|
|
60
|
-
50% { opacity: 0.5; }
|
|
61
|
-
`;
|
|
62
|
-
|
|
63
|
-
const FloatingEmoji = styled.div`
|
|
64
|
-
position: absolute;
|
|
65
|
-
bottom: 40px;
|
|
66
|
-
right: 40px;
|
|
67
|
-
font-size: 72px;
|
|
68
|
-
opacity: 0.08;
|
|
69
|
-
${css`animation: ${float} 4s ease-in-out infinite;`}
|
|
70
|
-
`;
|
|
71
|
-
|
|
72
|
-
// ================ RESPONSIVE BREAKPOINTS ================
|
|
73
|
-
const breakpoints = {
|
|
74
|
-
mobile: '768px',
|
|
75
|
-
tablet: '1024px',
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
// ================ STYLED COMPONENTS ================
|
|
79
|
-
const Container = styled(Box)`
|
|
80
|
-
${css`animation: ${fadeIn} ${theme.transitions.slow};`}
|
|
81
|
-
min-height: 100vh;
|
|
82
|
-
max-width: 1440px;
|
|
83
|
-
margin: 0 auto;
|
|
84
|
-
padding: ${theme.spacing.xl} ${theme.spacing.lg} 0;
|
|
85
|
-
|
|
86
|
-
@media screen and (max-width: ${breakpoints.mobile}) {
|
|
87
|
-
padding: 16px 12px 0;
|
|
88
|
-
}
|
|
89
|
-
`;
|
|
90
|
-
|
|
91
|
-
const Header = styled(Box)`
|
|
92
|
-
background: linear-gradient(135deg,
|
|
93
|
-
${theme.colors.primary[600]} 0%,
|
|
94
|
-
${theme.colors.secondary[600]} 100%
|
|
95
|
-
);
|
|
96
|
-
border-radius: ${theme.borderRadius.xl};
|
|
97
|
-
padding: ${theme.spacing.xl} ${theme.spacing['2xl']};
|
|
98
|
-
margin-bottom: ${theme.spacing.xl};
|
|
99
|
-
position: relative;
|
|
100
|
-
overflow: hidden;
|
|
101
|
-
box-shadow: ${theme.shadows.xl};
|
|
102
|
-
|
|
103
|
-
@media screen and (max-width: ${breakpoints.mobile}) {
|
|
104
|
-
padding: 24px 20px;
|
|
105
|
-
border-radius: 12px;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
&::before {
|
|
109
|
-
content: '';
|
|
110
|
-
position: absolute;
|
|
111
|
-
top: 0;
|
|
112
|
-
left: -100%;
|
|
113
|
-
width: 200%;
|
|
114
|
-
height: 100%;
|
|
115
|
-
background: linear-gradient(
|
|
116
|
-
90deg,
|
|
117
|
-
transparent,
|
|
118
|
-
rgba(255, 255, 255, 0.15),
|
|
119
|
-
transparent
|
|
120
|
-
);
|
|
121
|
-
${css`animation: ${shimmer} 3s infinite;`}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
&::after {
|
|
125
|
-
content: '';
|
|
126
|
-
position: absolute;
|
|
127
|
-
top: 0;
|
|
128
|
-
right: 0;
|
|
129
|
-
width: 100%;
|
|
130
|
-
height: 100%;
|
|
131
|
-
background-image: radial-gradient(circle at 20% 80%, transparent 50%, rgba(255, 255, 255, 0.1) 50%);
|
|
132
|
-
background-size: 15px 15px;
|
|
133
|
-
opacity: 0.3;
|
|
134
|
-
}
|
|
135
|
-
`;
|
|
136
|
-
|
|
137
|
-
const HeaderContent = styled(Flex)`
|
|
138
|
-
position: relative;
|
|
139
|
-
z-index: 1;
|
|
140
|
-
`;
|
|
141
|
-
|
|
142
|
-
const Title = styled(Typography)`
|
|
143
|
-
color: white;
|
|
144
|
-
font-size: 2rem;
|
|
145
|
-
font-weight: 700;
|
|
146
|
-
letter-spacing: -0.025em;
|
|
147
|
-
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
148
|
-
display: flex;
|
|
149
|
-
align-items: center;
|
|
150
|
-
gap: ${theme.spacing.sm};
|
|
151
|
-
|
|
152
|
-
svg {
|
|
153
|
-
width: 28px;
|
|
154
|
-
height: 28px;
|
|
155
|
-
${css`animation: ${float} 3s ease-in-out infinite;`}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
@media screen and (max-width: ${breakpoints.mobile}) {
|
|
159
|
-
font-size: 1.5rem;
|
|
160
|
-
|
|
161
|
-
svg {
|
|
162
|
-
width: 22px;
|
|
163
|
-
height: 22px;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
`;
|
|
167
|
-
|
|
168
|
-
const Subtitle = styled(Typography)`
|
|
169
|
-
color: rgba(255, 255, 255, 0.95);
|
|
170
|
-
font-size: 0.95rem;
|
|
171
|
-
font-weight: 400;
|
|
172
|
-
margin-top: ${theme.spacing.xs};
|
|
173
|
-
letter-spacing: 0.01em;
|
|
174
|
-
|
|
175
|
-
@media screen and (max-width: ${breakpoints.mobile}) {
|
|
176
|
-
font-size: 0.85rem;
|
|
177
|
-
}
|
|
178
|
-
`;
|
|
179
|
-
|
|
180
|
-
const StatsGrid = styled.div`
|
|
181
|
-
margin-bottom: ${theme.spacing.xl};
|
|
182
|
-
display: grid;
|
|
183
|
-
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
184
|
-
gap: ${theme.spacing.lg};
|
|
185
|
-
justify-content: center;
|
|
186
|
-
max-width: 1200px;
|
|
187
|
-
margin-left: auto;
|
|
188
|
-
margin-right: auto;
|
|
189
|
-
|
|
190
|
-
@media screen and (max-width: ${breakpoints.mobile}) {
|
|
191
|
-
grid-template-columns: repeat(2, 1fr);
|
|
192
|
-
gap: 12px;
|
|
193
|
-
margin-bottom: 24px;
|
|
194
|
-
}
|
|
195
|
-
`;
|
|
196
|
-
|
|
197
|
-
const StatCard = styled(Box)`
|
|
198
|
-
background: ${props => props.theme.colors.neutral0};
|
|
199
|
-
border-radius: ${theme.borderRadius.lg};
|
|
200
|
-
padding: 28px ${theme.spacing.lg};
|
|
201
|
-
position: relative;
|
|
202
|
-
overflow: hidden;
|
|
203
|
-
transition: all ${theme.transitions.normal};
|
|
204
|
-
${css`animation: ${fadeIn} ${theme.transitions.slow} backwards;`}
|
|
205
|
-
animation-delay: ${props => props.$delay || '0s'};
|
|
206
|
-
box-shadow: ${theme.shadows.sm};
|
|
207
|
-
border: 1px solid ${props => props.theme.colors.neutral200};
|
|
208
|
-
min-width: 200px;
|
|
209
|
-
flex: 1;
|
|
210
|
-
text-align: center;
|
|
211
|
-
display: flex;
|
|
212
|
-
flex-direction: column;
|
|
213
|
-
align-items: center;
|
|
214
|
-
justify-content: center;
|
|
215
|
-
|
|
216
|
-
@media screen and (max-width: ${breakpoints.mobile}) {
|
|
217
|
-
min-width: unset;
|
|
218
|
-
padding: 20px 12px;
|
|
219
|
-
|
|
220
|
-
&:hover {
|
|
221
|
-
transform: none;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
&:hover {
|
|
226
|
-
transform: translateY(-6px);
|
|
227
|
-
box-shadow: ${theme.shadows.xl};
|
|
228
|
-
border-color: ${props => props.$color || props.theme.colors.primary600};
|
|
229
|
-
|
|
230
|
-
.stat-icon {
|
|
231
|
-
transform: scale(1.15) rotate(5deg);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
.stat-value {
|
|
235
|
-
transform: scale(1.08);
|
|
236
|
-
color: ${props => props.$color || props.theme.colors.primary600};
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
`;
|
|
240
|
-
|
|
241
|
-
const StatIcon = styled(Box)`
|
|
242
|
-
width: 68px;
|
|
243
|
-
height: 68px;
|
|
244
|
-
border-radius: ${theme.borderRadius.lg};
|
|
245
|
-
display: flex;
|
|
246
|
-
align-items: center;
|
|
247
|
-
justify-content: center;
|
|
248
|
-
background: ${props => props.$bg || props.theme.colors.primary100};
|
|
249
|
-
transition: all ${theme.transitions.normal};
|
|
250
|
-
margin: 0 auto 20px;
|
|
251
|
-
box-shadow: ${theme.shadows.sm};
|
|
252
|
-
|
|
253
|
-
svg {
|
|
254
|
-
width: 34px;
|
|
255
|
-
height: 34px;
|
|
256
|
-
color: ${props => props.$color || props.theme.colors.primary600};
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
@media screen and (max-width: ${breakpoints.mobile}) {
|
|
260
|
-
width: 48px;
|
|
261
|
-
height: 48px;
|
|
262
|
-
margin-bottom: 12px;
|
|
263
|
-
|
|
264
|
-
svg {
|
|
265
|
-
width: 24px;
|
|
266
|
-
height: 24px;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
`;
|
|
270
|
-
|
|
271
|
-
const StatValue = styled(Typography)`
|
|
272
|
-
font-size: 2.75rem;
|
|
273
|
-
font-weight: 700;
|
|
274
|
-
color: ${props => props.theme.colors.neutral800};
|
|
275
|
-
line-height: 1;
|
|
276
|
-
margin-bottom: 10px;
|
|
277
|
-
transition: all ${theme.transitions.normal};
|
|
278
|
-
text-align: center;
|
|
279
|
-
|
|
280
|
-
@media screen and (max-width: ${breakpoints.mobile}) {
|
|
281
|
-
font-size: 2rem;
|
|
282
|
-
margin-bottom: 6px;
|
|
283
|
-
}
|
|
284
|
-
`;
|
|
285
|
-
|
|
286
|
-
const StatLabel = styled(Typography)`
|
|
287
|
-
font-size: 0.95rem;
|
|
288
|
-
color: ${props => props.theme.colors.neutral600};
|
|
289
|
-
font-weight: 500;
|
|
290
|
-
letter-spacing: 0.025em;
|
|
291
|
-
text-align: center;
|
|
292
|
-
|
|
293
|
-
@media screen and (max-width: ${breakpoints.mobile}) {
|
|
294
|
-
font-size: 0.8rem;
|
|
295
|
-
}
|
|
296
|
-
`;
|
|
297
|
-
|
|
298
|
-
const DataTable = styled(Box)`
|
|
299
|
-
background: ${props => props.theme.colors.neutral0};
|
|
300
|
-
border-radius: ${theme.borderRadius.lg};
|
|
301
|
-
overflow: hidden;
|
|
302
|
-
box-shadow: ${theme.shadows.sm};
|
|
303
|
-
border: 1px solid ${props => props.theme.colors.neutral200};
|
|
304
|
-
margin-bottom: ${theme.spacing.xl};
|
|
305
|
-
`;
|
|
306
|
-
|
|
307
|
-
const StyledTable = styled(Table)`
|
|
308
|
-
thead {
|
|
309
|
-
background: ${props => props.theme.colors.neutral100};
|
|
310
|
-
border-bottom: 2px solid ${props => props.theme.colors.neutral200};
|
|
311
|
-
|
|
312
|
-
th {
|
|
313
|
-
font-weight: 600;
|
|
314
|
-
color: ${props => props.theme.colors.neutral800};
|
|
315
|
-
font-size: 0.875rem;
|
|
316
|
-
text-transform: uppercase;
|
|
317
|
-
letter-spacing: 0.025em;
|
|
318
|
-
padding: ${theme.spacing.lg} ${theme.spacing.lg};
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
tbody tr {
|
|
323
|
-
transition: all ${theme.transitions.fast};
|
|
324
|
-
border-bottom: 1px solid ${props => props.theme.colors.neutral150};
|
|
325
|
-
|
|
326
|
-
&:last-child {
|
|
327
|
-
border-bottom: none;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
&:hover {
|
|
331
|
-
background: ${props => props.theme.colors.primary100};
|
|
332
|
-
|
|
333
|
-
.action-buttons {
|
|
334
|
-
opacity: 1;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
td {
|
|
339
|
-
padding: ${theme.spacing.lg} ${theme.spacing.lg};
|
|
340
|
-
color: ${props => props.theme.colors.neutral800};
|
|
341
|
-
vertical-align: middle;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
`;
|
|
345
|
-
|
|
346
|
-
const OnlineIndicator = styled.div`
|
|
347
|
-
width: 10px;
|
|
348
|
-
height: 10px;
|
|
349
|
-
border-radius: 50%;
|
|
350
|
-
background: ${props => props.$online ? theme.colors.success[500] : props.theme.colors.neutral400};
|
|
351
|
-
display: inline-block;
|
|
352
|
-
margin-right: 8px;
|
|
353
|
-
${css`animation: ${props => props.$online ? pulse : 'none'} 2s ease-in-out infinite;`}
|
|
354
|
-
`;
|
|
355
|
-
|
|
356
|
-
const FilterBar = styled(Flex)`
|
|
357
|
-
background: ${props => props.theme.colors.neutral0};
|
|
358
|
-
padding: ${theme.spacing.md} ${theme.spacing.lg};
|
|
359
|
-
border-radius: ${theme.borderRadius.lg};
|
|
360
|
-
margin-bottom: ${theme.spacing.lg};
|
|
361
|
-
box-shadow: ${theme.shadows.sm};
|
|
362
|
-
border: 1px solid ${props => props.theme.colors.neutral200};
|
|
363
|
-
gap: ${theme.spacing.md};
|
|
364
|
-
align-items: center;
|
|
365
|
-
`;
|
|
366
|
-
|
|
367
|
-
const SearchInputWrapper = styled.div`
|
|
368
|
-
position: relative;
|
|
369
|
-
flex: 1;
|
|
370
|
-
display: flex;
|
|
371
|
-
align-items: center;
|
|
372
|
-
`;
|
|
373
|
-
|
|
374
|
-
const SearchIcon = styled(Search)`
|
|
375
|
-
position: absolute;
|
|
376
|
-
left: 12px;
|
|
377
|
-
width: 16px;
|
|
378
|
-
height: 16px;
|
|
379
|
-
color: ${props => props.theme.colors.neutral600};
|
|
380
|
-
pointer-events: none;
|
|
381
|
-
`;
|
|
382
|
-
|
|
383
|
-
const StyledSearchInput = styled.input`
|
|
384
|
-
width: 100%;
|
|
385
|
-
padding: ${theme.spacing.sm} ${theme.spacing.sm} ${theme.spacing.sm} 36px;
|
|
386
|
-
border: 1px solid ${props => props.theme.colors.neutral200};
|
|
387
|
-
border-radius: ${theme.borderRadius.md};
|
|
388
|
-
font-size: 0.875rem;
|
|
389
|
-
transition: all ${theme.transitions.fast};
|
|
390
|
-
background: ${props => props.theme.colors.neutral0};
|
|
391
|
-
color: ${props => props.theme.colors.neutral800};
|
|
392
|
-
|
|
393
|
-
&:focus {
|
|
394
|
-
outline: none;
|
|
395
|
-
border-color: ${props => props.theme.colors.primary600};
|
|
396
|
-
box-shadow: 0 0 0 3px ${props => props.theme.colors.primary100};
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
&::placeholder {
|
|
400
|
-
color: ${props => props.theme.colors.neutral500};
|
|
401
|
-
}
|
|
402
|
-
`;
|
|
403
|
-
|
|
404
|
-
const ActionButtons = styled(Flex)`
|
|
405
|
-
opacity: 0.7;
|
|
406
|
-
transition: all ${theme.transitions.fast};
|
|
407
|
-
gap: ${theme.spacing.xs};
|
|
408
|
-
justify-content: flex-end;
|
|
409
|
-
`;
|
|
410
|
-
|
|
411
|
-
const ClickableRow = styled(Tr)`
|
|
412
|
-
cursor: pointer;
|
|
413
|
-
|
|
414
|
-
&:hover {
|
|
415
|
-
background: ${props => props.theme.colors.primary100} !important;
|
|
416
|
-
}
|
|
417
|
-
`;
|
|
418
|
-
|
|
419
|
-
// Empty state background that works in dark mode
|
|
420
|
-
const EmptyStateBox = styled(Box)`
|
|
421
|
-
background: ${props => props.theme.colors.neutral0};
|
|
422
|
-
border-radius: ${theme.borderRadius.xl};
|
|
423
|
-
border: 2px dashed ${props => props.theme.colors.neutral300};
|
|
424
|
-
padding: 80px 32px;
|
|
425
|
-
text-align: center;
|
|
426
|
-
position: relative;
|
|
427
|
-
overflow: hidden;
|
|
428
|
-
min-height: 400px;
|
|
429
|
-
display: flex;
|
|
430
|
-
align-items: center;
|
|
431
|
-
justify-content: center;
|
|
432
|
-
`;
|
|
433
|
-
|
|
434
|
-
const EmptyStateGradient = styled.div`
|
|
435
|
-
position: absolute;
|
|
436
|
-
top: 0;
|
|
437
|
-
left: 0;
|
|
438
|
-
right: 0;
|
|
439
|
-
bottom: 0;
|
|
440
|
-
background: linear-gradient(135deg, ${theme.colors.primary[50]} 0%, ${theme.colors.secondary[50]} 100%);
|
|
441
|
-
opacity: 0.3;
|
|
442
|
-
z-index: 0;
|
|
443
|
-
`;
|
|
444
|
-
|
|
445
|
-
const HomePage = () => {
|
|
446
|
-
const { formatMessage } = useIntl();
|
|
447
|
-
const { get, post, del } = useFetchClient();
|
|
448
|
-
const { toggleNotification } = useNotification();
|
|
449
|
-
const { isPremium } = useLicense();
|
|
450
|
-
const t = (id, defaultMessage, values) => formatMessage({ id: getTranslation(id), defaultMessage }, values);
|
|
451
|
-
const [sessions, setSessions] = useState([]);
|
|
452
|
-
const [loading, setLoading] = useState(true);
|
|
453
|
-
const [filterStatus, setFilterStatus] = useState('active'); // Default: Active Only
|
|
454
|
-
const [entriesPerPage, setEntriesPerPage] = useState('25');
|
|
455
|
-
const [searchQuery, setSearchQuery] = useState('');
|
|
456
|
-
const [selectedSession, setSelectedSession] = useState(null);
|
|
457
|
-
const [showDetailModal, setShowDetailModal] = useState(false);
|
|
458
|
-
|
|
459
|
-
useEffect(() => {
|
|
460
|
-
fetchSessions();
|
|
461
|
-
|
|
462
|
-
// Auto-refresh every 10 minutes (silent background update)
|
|
463
|
-
// But ONLY if modal is not open (to avoid interrupting user)
|
|
464
|
-
const interval = setInterval(() => {
|
|
465
|
-
if (!showDetailModal) {
|
|
466
|
-
fetchSessions();
|
|
467
|
-
}
|
|
468
|
-
}, 10 * 60 * 1000);
|
|
469
|
-
|
|
470
|
-
return () => clearInterval(interval);
|
|
471
|
-
}, [showDetailModal]);
|
|
472
|
-
|
|
473
|
-
const fetchSessions = async () => {
|
|
474
|
-
setLoading(true);
|
|
475
|
-
try {
|
|
476
|
-
const { data } = await get(`/${pluginId}/sessions`);
|
|
477
|
-
setSessions(data.data || []);
|
|
478
|
-
} catch (err) {
|
|
479
|
-
console.error('[SessionManager] Error fetching sessions:', err);
|
|
480
|
-
} finally {
|
|
481
|
-
setLoading(false);
|
|
482
|
-
}
|
|
483
|
-
};
|
|
484
|
-
|
|
485
|
-
const handleTerminateSession = async (sessionId) => {
|
|
486
|
-
if (!confirm(t('homepage.confirm.terminate', 'Are you sure you want to terminate this session?\n\nThis will set isActive to false (user will be logged out).'))) {
|
|
487
|
-
return;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
try {
|
|
491
|
-
await post(`/${pluginId}/sessions/${sessionId}/terminate`);
|
|
492
|
-
fetchSessions();
|
|
493
|
-
} catch (err) {
|
|
494
|
-
console.error('[SessionManager] Error terminating session:', err);
|
|
495
|
-
}
|
|
496
|
-
};
|
|
497
|
-
|
|
498
|
-
const handleDeleteSession = async (sessionId) => {
|
|
499
|
-
if (!confirm(t('homepage.confirm.delete', '[WARNING] This will PERMANENTLY delete this session from the database!\n\nThis action cannot be undone.\n\nAre you sure?'))) {
|
|
500
|
-
return;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
try {
|
|
504
|
-
await del(`/${pluginId}/sessions/${sessionId}`);
|
|
505
|
-
fetchSessions();
|
|
506
|
-
toggleNotification({
|
|
507
|
-
type: 'success',
|
|
508
|
-
message: t('notifications.success.deleted', 'Session permanently deleted'),
|
|
509
|
-
});
|
|
510
|
-
} catch (err) {
|
|
511
|
-
console.error('[SessionManager] Error deleting session:', err);
|
|
512
|
-
toggleNotification({
|
|
513
|
-
type: 'danger',
|
|
514
|
-
message: t('notifications.error.delete', 'Failed to delete session'),
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
|
-
};
|
|
518
|
-
|
|
519
|
-
const handleExportCSV = () => {
|
|
520
|
-
if (!isPremium) {
|
|
521
|
-
toggleNotification({
|
|
522
|
-
type: 'warning',
|
|
523
|
-
message: t('notifications.warning.premiumRequired', 'Premium license required for export functionality'),
|
|
524
|
-
});
|
|
525
|
-
return;
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
try {
|
|
529
|
-
// CSV Header
|
|
530
|
-
const headers = ['ID', 'Status', 'User Email', 'Username', 'Device', 'Browser', 'OS', 'IP Address', 'Login Time', 'Last Active', 'Logout Time', 'Minutes Idle'];
|
|
531
|
-
|
|
532
|
-
// CSV Rows
|
|
533
|
-
const rows = filteredSessions.map(session => {
|
|
534
|
-
const deviceInfo = parseUserAgent(session.userAgent);
|
|
535
|
-
const status = getSessionStatus(session);
|
|
536
|
-
|
|
537
|
-
return [
|
|
538
|
-
session.id,
|
|
539
|
-
status,
|
|
540
|
-
session.user?.email || '',
|
|
541
|
-
session.user?.username || '',
|
|
542
|
-
deviceInfo.device,
|
|
543
|
-
deviceInfo.browser,
|
|
544
|
-
deviceInfo.os,
|
|
545
|
-
session.ipAddress,
|
|
546
|
-
new Date(session.loginTime).toISOString(),
|
|
547
|
-
new Date(session.lastActive || session.loginTime).toISOString(),
|
|
548
|
-
session.logoutTime ? new Date(session.logoutTime).toISOString() : '',
|
|
549
|
-
session.minutesSinceActive,
|
|
550
|
-
];
|
|
551
|
-
});
|
|
552
|
-
|
|
553
|
-
// Create CSV content
|
|
554
|
-
const csvContent = [
|
|
555
|
-
headers.join(','),
|
|
556
|
-
...rows.map(row => row.map(cell => `"${cell}"`).join(','))
|
|
557
|
-
].join('\n');
|
|
558
|
-
|
|
559
|
-
// Download
|
|
560
|
-
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
|
561
|
-
const link = document.createElement('a');
|
|
562
|
-
link.href = URL.createObjectURL(blob);
|
|
563
|
-
link.download = `sessions-export-${new Date().toISOString().split('T')[0]}.csv`;
|
|
564
|
-
link.click();
|
|
565
|
-
|
|
566
|
-
toggleNotification({
|
|
567
|
-
type: 'success',
|
|
568
|
-
message: t('notifications.success.exported', 'Exported {count} sessions to {format}', { count: filteredSessions.length, format: 'CSV' }),
|
|
569
|
-
});
|
|
570
|
-
} catch (err) {
|
|
571
|
-
console.error('[SessionManager] Export error:', err);
|
|
572
|
-
toggleNotification({
|
|
573
|
-
type: 'danger',
|
|
574
|
-
message: t('notifications.error.export', 'Failed to export sessions'),
|
|
575
|
-
});
|
|
576
|
-
}
|
|
577
|
-
};
|
|
578
|
-
|
|
579
|
-
const handleExportJSON = () => {
|
|
580
|
-
if (!isPremium) {
|
|
581
|
-
toggleNotification({
|
|
582
|
-
type: 'warning',
|
|
583
|
-
message: t('notifications.warning.premiumRequired', 'Premium license required for export functionality'),
|
|
584
|
-
});
|
|
585
|
-
return;
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
try {
|
|
589
|
-
const exportData = {
|
|
590
|
-
exportedAt: new Date().toISOString(),
|
|
591
|
-
filter: filterStatus,
|
|
592
|
-
totalSessions: sessions.length,
|
|
593
|
-
exportedSessions: filteredSessions.length,
|
|
594
|
-
sessions: filteredSessions.map(session => {
|
|
595
|
-
const deviceInfo = parseUserAgent(session.userAgent);
|
|
596
|
-
return {
|
|
597
|
-
id: session.id,
|
|
598
|
-
status: getSessionStatus(session),
|
|
599
|
-
user: {
|
|
600
|
-
id: session.user?.id,
|
|
601
|
-
email: session.user?.email,
|
|
602
|
-
username: session.user?.username,
|
|
603
|
-
},
|
|
604
|
-
device: {
|
|
605
|
-
type: deviceInfo.device,
|
|
606
|
-
browser: deviceInfo.browser,
|
|
607
|
-
browserVersion: deviceInfo.browserVersion,
|
|
608
|
-
os: deviceInfo.os,
|
|
609
|
-
},
|
|
610
|
-
ipAddress: session.ipAddress,
|
|
611
|
-
loginTime: session.loginTime,
|
|
612
|
-
lastActive: session.lastActive,
|
|
613
|
-
logoutTime: session.logoutTime,
|
|
614
|
-
minutesSinceActive: session.minutesSinceActive,
|
|
615
|
-
isActive: session.isActive,
|
|
616
|
-
isTrulyActive: session.isTrulyActive,
|
|
617
|
-
};
|
|
618
|
-
}),
|
|
619
|
-
};
|
|
620
|
-
|
|
621
|
-
const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
|
|
622
|
-
const link = document.createElement('a');
|
|
623
|
-
link.href = URL.createObjectURL(blob);
|
|
624
|
-
link.download = `sessions-export-${new Date().toISOString().split('T')[0]}.json`;
|
|
625
|
-
link.click();
|
|
626
|
-
|
|
627
|
-
toggleNotification({
|
|
628
|
-
type: 'success',
|
|
629
|
-
message: t('notifications.success.exported', 'Exported {count} sessions to {format}', { count: filteredSessions.length, format: 'JSON' }),
|
|
630
|
-
});
|
|
631
|
-
} catch (err) {
|
|
632
|
-
console.error('[SessionManager] Export error:', err);
|
|
633
|
-
toggleNotification({
|
|
634
|
-
type: 'danger',
|
|
635
|
-
message: t('notifications.error.export', 'Failed to export sessions'),
|
|
636
|
-
});
|
|
637
|
-
}
|
|
638
|
-
};
|
|
639
|
-
|
|
640
|
-
const getDeviceIcon = (deviceType) => {
|
|
641
|
-
if (deviceType === 'Mobile' || deviceType === 'Tablet') return Phone;
|
|
642
|
-
if (deviceType === 'Desktop' || deviceType === 'Laptop') return Monitor;
|
|
643
|
-
return Server;
|
|
644
|
-
};
|
|
645
|
-
|
|
646
|
-
// Calculate stats based on new 4-status logic
|
|
647
|
-
const activeSessions = sessions.filter(s => s.isActive && s.isTrulyActive);
|
|
648
|
-
const idleSessions = sessions.filter(s => s.isActive && !s.isTrulyActive);
|
|
649
|
-
const loggedOutSessions = sessions.filter(s => !s.isActive && s.logoutTime);
|
|
650
|
-
const terminatedSessions = sessions.filter(s => !s.isActive && !s.logoutTime);
|
|
651
|
-
|
|
652
|
-
const handleSessionClick = (session) => {
|
|
653
|
-
setSelectedSession(session);
|
|
654
|
-
setShowDetailModal(true);
|
|
655
|
-
};
|
|
656
|
-
|
|
657
|
-
const handleModalClose = () => {
|
|
658
|
-
setShowDetailModal(false);
|
|
659
|
-
setSelectedSession(null);
|
|
660
|
-
};
|
|
661
|
-
|
|
662
|
-
const handleSessionTerminated = () => {
|
|
663
|
-
fetchSessions();
|
|
664
|
-
};
|
|
665
|
-
|
|
666
|
-
// Helper function to get session status
|
|
667
|
-
const getSessionStatus = (session) => {
|
|
668
|
-
if (!session.isActive) {
|
|
669
|
-
return session.logoutTime ? 'loggedout' : 'terminated';
|
|
670
|
-
}
|
|
671
|
-
return session.isTrulyActive ? 'active' : 'idle';
|
|
672
|
-
};
|
|
673
|
-
|
|
674
|
-
// Filter sessions
|
|
675
|
-
const filteredSessions = sessions
|
|
676
|
-
.filter(session => {
|
|
677
|
-
// Filter by status
|
|
678
|
-
const sessionStatus = getSessionStatus(session);
|
|
679
|
-
if (filterStatus === 'active' && sessionStatus !== 'active') return false;
|
|
680
|
-
if (filterStatus === 'idle' && sessionStatus !== 'idle') return false;
|
|
681
|
-
if (filterStatus === 'loggedout' && sessionStatus !== 'loggedout') return false;
|
|
682
|
-
if (filterStatus === 'terminated' && sessionStatus !== 'terminated') return false;
|
|
683
|
-
|
|
684
|
-
// Filter by search query
|
|
685
|
-
if (searchQuery) {
|
|
686
|
-
const query = searchQuery.toLowerCase();
|
|
687
|
-
const matchesUser = session.user?.email?.toLowerCase().includes(query) ||
|
|
688
|
-
session.user?.username?.toLowerCase().includes(query);
|
|
689
|
-
const matchesIp = session.ipAddress?.toLowerCase().includes(query);
|
|
690
|
-
const deviceInfo = parseUserAgent(session.userAgent);
|
|
691
|
-
const matchesDevice = deviceInfo.device?.toLowerCase().includes(query) ||
|
|
692
|
-
deviceInfo.browser?.toLowerCase().includes(query) ||
|
|
693
|
-
deviceInfo.os?.toLowerCase().includes(query);
|
|
694
|
-
|
|
695
|
-
return matchesUser || matchesIp || matchesDevice;
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
return true;
|
|
699
|
-
})
|
|
700
|
-
.slice(0, parseInt(entriesPerPage));
|
|
701
|
-
|
|
702
|
-
return (
|
|
703
|
-
<Container padding={8}>
|
|
704
|
-
{/* Gradient Header */}
|
|
705
|
-
<Header>
|
|
706
|
-
<HeaderContent justifyContent="space-between" alignItems="center">
|
|
707
|
-
<Flex direction="column" alignItems="flex-start" gap={2}>
|
|
708
|
-
<Title>
|
|
709
|
-
<Monitor /> {t('homepage.title', 'Session Manager')}
|
|
710
|
-
</Title>
|
|
711
|
-
<Subtitle>
|
|
712
|
-
{t('homepage.subtitle', 'Monitor and manage user sessions in real-time')}
|
|
713
|
-
</Subtitle>
|
|
714
|
-
</Flex>
|
|
715
|
-
|
|
716
|
-
{isPremium && filteredSessions.length > 0 && (
|
|
717
|
-
<Flex gap={2}>
|
|
718
|
-
<Button
|
|
719
|
-
onClick={handleExportCSV}
|
|
720
|
-
startIcon={<Download />}
|
|
721
|
-
size="M"
|
|
722
|
-
variant="secondary"
|
|
723
|
-
style={{
|
|
724
|
-
backgroundColor: 'rgba(255,255,255,0.2)',
|
|
725
|
-
color: 'white',
|
|
726
|
-
border: '1px solid rgba(255,255,255,0.3)',
|
|
727
|
-
fontWeight: '600',
|
|
728
|
-
}}
|
|
729
|
-
>
|
|
730
|
-
{t('homepage.export.csv', 'Export CSV')}
|
|
731
|
-
</Button>
|
|
732
|
-
<Button
|
|
733
|
-
onClick={handleExportJSON}
|
|
734
|
-
startIcon={<Download />}
|
|
735
|
-
size="M"
|
|
736
|
-
variant="secondary"
|
|
737
|
-
style={{
|
|
738
|
-
backgroundColor: 'rgba(255,255,255,0.2)',
|
|
739
|
-
color: 'white',
|
|
740
|
-
border: '1px solid rgba(255,255,255,0.3)',
|
|
741
|
-
fontWeight: '600',
|
|
742
|
-
}}
|
|
743
|
-
>
|
|
744
|
-
{t('homepage.export.json', 'Export JSON')}
|
|
745
|
-
</Button>
|
|
746
|
-
</Flex>
|
|
747
|
-
)}
|
|
748
|
-
</HeaderContent>
|
|
749
|
-
</Header>
|
|
750
|
-
|
|
751
|
-
{/* Stats Cards */}
|
|
752
|
-
<StatsGrid>
|
|
753
|
-
<StatCard $delay="0.1s" $color={theme.colors.success[500]}>
|
|
754
|
-
<StatIcon className="stat-icon" $bg={theme.colors.success[100]} $color={theme.colors.success[600]}>
|
|
755
|
-
<Check />
|
|
756
|
-
</StatIcon>
|
|
757
|
-
<StatValue className="stat-value">{activeSessions.length}</StatValue>
|
|
758
|
-
<StatLabel>{t('homepage.stats.active', 'Active')}</StatLabel>
|
|
759
|
-
</StatCard>
|
|
760
|
-
|
|
761
|
-
<StatCard $delay="0.2s" $color={theme.colors.warning[500]}>
|
|
762
|
-
<StatIcon className="stat-icon" $bg={theme.colors.warning[100]} $color={theme.colors.warning[600]}>
|
|
763
|
-
<Clock />
|
|
764
|
-
</StatIcon>
|
|
765
|
-
<StatValue className="stat-value">{idleSessions.length}</StatValue>
|
|
766
|
-
<StatLabel>{t('homepage.stats.idle', 'Idle')}</StatLabel>
|
|
767
|
-
</StatCard>
|
|
768
|
-
|
|
769
|
-
<StatCard $delay="0.3s" $color={theme.colors.danger[500]}>
|
|
770
|
-
<StatIcon className="stat-icon" $bg={theme.colors.danger[100]} $color={theme.colors.danger[600]}>
|
|
771
|
-
<Cross />
|
|
772
|
-
</StatIcon>
|
|
773
|
-
<StatValue className="stat-value">{loggedOutSessions.length}</StatValue>
|
|
774
|
-
<StatLabel>{t('homepage.stats.loggedOut', 'Logged Out')}</StatLabel>
|
|
775
|
-
</StatCard>
|
|
776
|
-
|
|
777
|
-
<StatCard $delay="0.4s" $color="#4B5563">
|
|
778
|
-
<StatIcon className="stat-icon" $bg="#F3F4F6" $color="#4B5563">
|
|
779
|
-
<Cross />
|
|
780
|
-
</StatIcon>
|
|
781
|
-
<StatValue className="stat-value">{terminatedSessions.length}</StatValue>
|
|
782
|
-
<StatLabel>{t('homepage.stats.terminated', 'Terminated')}</StatLabel>
|
|
783
|
-
</StatCard>
|
|
784
|
-
|
|
785
|
-
<StatCard $delay="0.5s" $color="#A855F7">
|
|
786
|
-
<StatIcon className="stat-icon" $bg="#EDE9FE" $color="#9333EA">
|
|
787
|
-
<User />
|
|
788
|
-
</StatIcon>
|
|
789
|
-
<StatValue className="stat-value">{sessions.length}</StatValue>
|
|
790
|
-
<StatLabel>{t('homepage.stats.total', 'Total')}</StatLabel>
|
|
791
|
-
</StatCard>
|
|
792
|
-
</StatsGrid>
|
|
793
|
-
|
|
794
|
-
{/* Loading */}
|
|
795
|
-
{loading && (
|
|
796
|
-
<Flex justifyContent="center" padding={8}>
|
|
797
|
-
<Loader>{t('homepage.loading', 'Loading sessions...')}</Loader>
|
|
798
|
-
</Flex>
|
|
799
|
-
)}
|
|
800
|
-
|
|
801
|
-
{/* Sessions Table */}
|
|
802
|
-
{!loading && sessions.length > 0 && (
|
|
803
|
-
<Box>
|
|
804
|
-
<Box style={{ marginBottom: theme.spacing.md }}>
|
|
805
|
-
<Typography variant="delta" textColor="neutral700" style={{ marginBottom: theme.spacing.md }}>
|
|
806
|
-
{t('homepage.allSessions', 'All Sessions')}
|
|
807
|
-
</Typography>
|
|
808
|
-
</Box>
|
|
809
|
-
|
|
810
|
-
{/* Filter Bar */}
|
|
811
|
-
<FilterBar>
|
|
812
|
-
{/* Search Input */}
|
|
813
|
-
<SearchInputWrapper>
|
|
814
|
-
<SearchIcon />
|
|
815
|
-
<StyledSearchInput
|
|
816
|
-
value={searchQuery}
|
|
817
|
-
onChange={(e) => setSearchQuery(e.target.value)}
|
|
818
|
-
placeholder={t('homepage.search.placeholder', 'Search by user, IP address, or device...')}
|
|
819
|
-
type="text"
|
|
820
|
-
/>
|
|
821
|
-
</SearchInputWrapper>
|
|
822
|
-
|
|
823
|
-
{/* Status Filter */}
|
|
824
|
-
<Box style={{ minWidth: '180px' }}>
|
|
825
|
-
<SingleSelect
|
|
826
|
-
value={filterStatus}
|
|
827
|
-
onChange={setFilterStatus}
|
|
828
|
-
placeholder="Filter"
|
|
829
|
-
size="S"
|
|
830
|
-
>
|
|
831
|
-
<SingleSelectOption value="all">{t('homepage.filter.all', 'All Sessions')}</SingleSelectOption>
|
|
832
|
-
<SingleSelectOption value="active">{t('homepage.filter.active', 'Active (less than 15 min)')}</SingleSelectOption>
|
|
833
|
-
<SingleSelectOption value="idle">{t('homepage.filter.idle', 'Idle (more than 15 min)')}</SingleSelectOption>
|
|
834
|
-
<SingleSelectOption value="loggedout">{t('homepage.filter.loggedout', 'Logged Out')}</SingleSelectOption>
|
|
835
|
-
<SingleSelectOption value="terminated">{t('homepage.filter.terminated', 'Terminated')}</SingleSelectOption>
|
|
836
|
-
</SingleSelect>
|
|
837
|
-
</Box>
|
|
838
|
-
|
|
839
|
-
{/* Entries per page */}
|
|
840
|
-
<Box style={{ minWidth: '130px' }}>
|
|
841
|
-
<SingleSelect
|
|
842
|
-
value={entriesPerPage}
|
|
843
|
-
onChange={setEntriesPerPage}
|
|
844
|
-
placeholder="Entries"
|
|
845
|
-
size="S"
|
|
846
|
-
>
|
|
847
|
-
<SingleSelectOption value="10">{t('homepage.entries.10', '10 entries')}</SingleSelectOption>
|
|
848
|
-
<SingleSelectOption value="25">{t('homepage.entries.25', '25 entries')}</SingleSelectOption>
|
|
849
|
-
<SingleSelectOption value="50">{t('homepage.entries.50', '50 entries')}</SingleSelectOption>
|
|
850
|
-
<SingleSelectOption value="100">{t('homepage.entries.100', '100 entries')}</SingleSelectOption>
|
|
851
|
-
</SingleSelect>
|
|
852
|
-
</Box>
|
|
853
|
-
</FilterBar>
|
|
854
|
-
|
|
855
|
-
{/* Results count */}
|
|
856
|
-
<Box style={{ marginBottom: theme.spacing.md }}>
|
|
857
|
-
<Typography variant="pi" textColor="neutral600">
|
|
858
|
-
{searchQuery
|
|
859
|
-
? t('homepage.showingFiltered', 'Showing {count} of {total} sessions (filtered by "{query}")', { count: filteredSessions.length, total: sessions.length, query: searchQuery })
|
|
860
|
-
: t('homepage.showing', 'Showing {count} of {total} sessions', { count: filteredSessions.length, total: sessions.length })
|
|
861
|
-
}
|
|
862
|
-
</Typography>
|
|
863
|
-
</Box>
|
|
864
|
-
|
|
865
|
-
{/* Table or No Results */}
|
|
866
|
-
{filteredSessions.length > 0 ? (
|
|
867
|
-
<DataTable>
|
|
868
|
-
<StyledTable>
|
|
869
|
-
<Thead>
|
|
870
|
-
<Tr>
|
|
871
|
-
<Th>{t('homepage.table.status', 'Status')}</Th>
|
|
872
|
-
<Th>{t('homepage.table.user', 'User')}</Th>
|
|
873
|
-
<Th>{t('homepage.table.device', 'Device')}</Th>
|
|
874
|
-
<Th>{t('homepage.table.ipAddress', 'IP Address')}</Th>
|
|
875
|
-
<Th>{t('homepage.table.loginTime', 'Login Time')}</Th>
|
|
876
|
-
<Th>{t('homepage.table.lastActive', 'Last Active')}</Th>
|
|
877
|
-
<Th>{t('homepage.table.actions', 'Actions')}</Th>
|
|
878
|
-
</Tr>
|
|
879
|
-
</Thead>
|
|
880
|
-
<Tbody>
|
|
881
|
-
{filteredSessions.map((session) => {
|
|
882
|
-
const deviceInfo = parseUserAgent(session.userAgent);
|
|
883
|
-
const DeviceIcon = getDeviceIcon(deviceInfo.device);
|
|
884
|
-
const sessionStatus = getSessionStatus(session);
|
|
885
|
-
|
|
886
|
-
// Status colors and labels
|
|
887
|
-
const statusConfig = {
|
|
888
|
-
active: {
|
|
889
|
-
bg: theme.colors.success[50],
|
|
890
|
-
badgeColor: 'success600',
|
|
891
|
-
label: t('homepage.status.active', 'Active'),
|
|
892
|
-
indicator: true
|
|
893
|
-
},
|
|
894
|
-
idle: {
|
|
895
|
-
bg: theme.colors.warning[50],
|
|
896
|
-
badgeColor: 'warning600',
|
|
897
|
-
label: t('homepage.status.idle', 'Idle'),
|
|
898
|
-
indicator: false
|
|
899
|
-
},
|
|
900
|
-
loggedout: {
|
|
901
|
-
bg: theme.colors.danger[50],
|
|
902
|
-
badgeColor: 'danger600',
|
|
903
|
-
label: t('homepage.status.loggedOut', 'Logged Out'),
|
|
904
|
-
indicator: false,
|
|
905
|
-
opacity: 0.7
|
|
906
|
-
},
|
|
907
|
-
terminated: {
|
|
908
|
-
bg: '#F3F4F6',
|
|
909
|
-
badgeColor: 'neutral600',
|
|
910
|
-
label: t('homepage.status.terminated', 'Terminated'),
|
|
911
|
-
indicator: false,
|
|
912
|
-
opacity: 0.6
|
|
913
|
-
},
|
|
914
|
-
};
|
|
915
|
-
|
|
916
|
-
const config = statusConfig[sessionStatus];
|
|
917
|
-
|
|
918
|
-
return (
|
|
919
|
-
<ClickableRow
|
|
920
|
-
key={session.id}
|
|
921
|
-
onClick={() => handleSessionClick(session)}
|
|
922
|
-
style={{
|
|
923
|
-
background: config.bg,
|
|
924
|
-
opacity: config.opacity || 1,
|
|
925
|
-
}}
|
|
926
|
-
>
|
|
927
|
-
{/* Status */}
|
|
928
|
-
<Td>
|
|
929
|
-
<Flex alignItems="center" gap={2}>
|
|
930
|
-
<OnlineIndicator $online={config.indicator} />
|
|
931
|
-
<Badge
|
|
932
|
-
backgroundColor={config.badgeColor}
|
|
933
|
-
textColor="neutral0"
|
|
934
|
-
size="S"
|
|
935
|
-
>
|
|
936
|
-
{config.label}
|
|
937
|
-
</Badge>
|
|
938
|
-
</Flex>
|
|
939
|
-
</Td>
|
|
940
|
-
|
|
941
|
-
{/* User */}
|
|
942
|
-
<Td>
|
|
943
|
-
<Flex direction="column" alignItems="flex-start">
|
|
944
|
-
<Typography fontWeight="semiBold" ellipsis>
|
|
945
|
-
{session.user?.username || session.user?.email || t('homepage.user.unknown', 'Unknown')}
|
|
946
|
-
</Typography>
|
|
947
|
-
{session.user?.email && session.user?.username && (
|
|
948
|
-
<Typography variant="pi" textColor="neutral600" ellipsis>
|
|
949
|
-
{session.user.email}
|
|
950
|
-
</Typography>
|
|
951
|
-
)}
|
|
952
|
-
</Flex>
|
|
953
|
-
</Td>
|
|
954
|
-
|
|
955
|
-
{/* Device */}
|
|
956
|
-
<Td>
|
|
957
|
-
<Flex alignItems="center" gap={2}>
|
|
958
|
-
<DeviceIcon width="18px" height="18px" />
|
|
959
|
-
<Flex direction="column" alignItems="flex-start">
|
|
960
|
-
<Typography variant="omega" fontWeight="semiBold">
|
|
961
|
-
{deviceInfo.device}
|
|
962
|
-
</Typography>
|
|
963
|
-
<Typography variant="pi" textColor="neutral600">
|
|
964
|
-
{deviceInfo.browser} on {deviceInfo.os}
|
|
965
|
-
</Typography>
|
|
966
|
-
</Flex>
|
|
967
|
-
</Flex>
|
|
968
|
-
</Td>
|
|
969
|
-
|
|
970
|
-
{/* IP Address */}
|
|
971
|
-
<Td>
|
|
972
|
-
<Typography variant="omega" style={{ fontFamily: 'monospace' }}>
|
|
973
|
-
{session.ipAddress}
|
|
974
|
-
</Typography>
|
|
975
|
-
</Td>
|
|
976
|
-
|
|
977
|
-
{/* Login Time */}
|
|
978
|
-
<Td>
|
|
979
|
-
<Typography variant="pi" textColor="neutral700">
|
|
980
|
-
{new Date(session.loginTime).toLocaleString()}
|
|
981
|
-
</Typography>
|
|
982
|
-
</Td>
|
|
983
|
-
|
|
984
|
-
{/* Last Active */}
|
|
985
|
-
<Td>
|
|
986
|
-
<Flex direction="column" alignItems="flex-start">
|
|
987
|
-
<Typography variant="pi" textColor="neutral700">
|
|
988
|
-
{new Date(session.lastActive || session.loginTime).toLocaleString()}
|
|
989
|
-
</Typography>
|
|
990
|
-
<Typography variant="pi" textColor={sessionStatus === 'active' ? 'success600' : 'neutral500'}>
|
|
991
|
-
{t('homepage.time.minAgo', '{minutes} min ago', { minutes: session.minutesSinceActive })}
|
|
992
|
-
</Typography>
|
|
993
|
-
</Flex>
|
|
994
|
-
</Td>
|
|
995
|
-
|
|
996
|
-
{/* Actions */}
|
|
997
|
-
<Td onClick={(e) => e.stopPropagation()}>
|
|
998
|
-
<ActionButtons className="action-buttons">
|
|
999
|
-
<Button
|
|
1000
|
-
variant="secondary"
|
|
1001
|
-
size="S"
|
|
1002
|
-
onClick={(e) => {
|
|
1003
|
-
e.stopPropagation();
|
|
1004
|
-
handleSessionClick(session);
|
|
1005
|
-
}}
|
|
1006
|
-
title={t('homepage.actions.viewDetails', 'View Details')}
|
|
1007
|
-
>
|
|
1008
|
-
<Eye />
|
|
1009
|
-
</Button>
|
|
1010
|
-
<Button
|
|
1011
|
-
variant="danger-light"
|
|
1012
|
-
size="S"
|
|
1013
|
-
onClick={(e) => {
|
|
1014
|
-
e.stopPropagation();
|
|
1015
|
-
handleTerminateSession(session.id);
|
|
1016
|
-
}}
|
|
1017
|
-
disabled={sessionStatus !== 'active' && sessionStatus !== 'idle'}
|
|
1018
|
-
title={session.isActive ? t('homepage.actions.terminate', 'Terminate (Logout)') : t('homepage.actions.alreadyInactive', 'Already inactive')}
|
|
1019
|
-
>
|
|
1020
|
-
<Cross />
|
|
1021
|
-
</Button>
|
|
1022
|
-
<Button
|
|
1023
|
-
variant="danger"
|
|
1024
|
-
size="S"
|
|
1025
|
-
onClick={(e) => {
|
|
1026
|
-
e.stopPropagation();
|
|
1027
|
-
handleDeleteSession(session.id);
|
|
1028
|
-
}}
|
|
1029
|
-
title={t('homepage.actions.deletePermanently', 'Delete Permanently')}
|
|
1030
|
-
>
|
|
1031
|
-
<Trash />
|
|
1032
|
-
</Button>
|
|
1033
|
-
</ActionButtons>
|
|
1034
|
-
</Td>
|
|
1035
|
-
</ClickableRow>
|
|
1036
|
-
);
|
|
1037
|
-
})}
|
|
1038
|
-
</Tbody>
|
|
1039
|
-
</StyledTable>
|
|
1040
|
-
</DataTable>
|
|
1041
|
-
) : (
|
|
1042
|
-
/* No results found */
|
|
1043
|
-
<Box
|
|
1044
|
-
background="neutral0"
|
|
1045
|
-
style={{
|
|
1046
|
-
borderRadius: theme.borderRadius.xl,
|
|
1047
|
-
border: '2px dashed #E5E7EB',
|
|
1048
|
-
padding: '60px 32px',
|
|
1049
|
-
textAlign: 'center',
|
|
1050
|
-
position: 'relative',
|
|
1051
|
-
overflow: 'hidden',
|
|
1052
|
-
minHeight: '300px',
|
|
1053
|
-
display: 'flex',
|
|
1054
|
-
alignItems: 'center',
|
|
1055
|
-
justifyContent: 'center',
|
|
1056
|
-
}}
|
|
1057
|
-
>
|
|
1058
|
-
{/* Background Gradient */}
|
|
1059
|
-
<Box
|
|
1060
|
-
style={{
|
|
1061
|
-
position: 'absolute',
|
|
1062
|
-
top: 0,
|
|
1063
|
-
left: 0,
|
|
1064
|
-
right: 0,
|
|
1065
|
-
bottom: 0,
|
|
1066
|
-
background: `linear-gradient(135deg, ${theme.colors.primary[50]} 0%, ${theme.colors.secondary[50]} 100%)`,
|
|
1067
|
-
opacity: 0.3,
|
|
1068
|
-
zIndex: 0,
|
|
1069
|
-
}}
|
|
1070
|
-
/>
|
|
1071
|
-
|
|
1072
|
-
{/* Floating Emoji */}
|
|
1073
|
-
<FloatingEmoji>
|
|
1074
|
-
🔍
|
|
1075
|
-
</FloatingEmoji>
|
|
1076
|
-
|
|
1077
|
-
{/* Content */}
|
|
1078
|
-
<Flex direction="column" alignItems="center" gap={4} style={{ position: 'relative', zIndex: 1 }}>
|
|
1079
|
-
{/* Icon Circle */}
|
|
1080
|
-
<Box
|
|
1081
|
-
style={{
|
|
1082
|
-
width: '100px',
|
|
1083
|
-
height: '100px',
|
|
1084
|
-
borderRadius: '50%',
|
|
1085
|
-
background: `linear-gradient(135deg, ${theme.colors.primary[100]} 0%, ${theme.colors.secondary[100]} 100%)`,
|
|
1086
|
-
display: 'flex',
|
|
1087
|
-
alignItems: 'center',
|
|
1088
|
-
justifyContent: 'center',
|
|
1089
|
-
boxShadow: theme.shadows.xl,
|
|
1090
|
-
}}
|
|
1091
|
-
>
|
|
1092
|
-
<Search style={{ width: '50px', height: '50px', color: '#0284C7' }} />
|
|
1093
|
-
</Box>
|
|
1094
|
-
|
|
1095
|
-
<Typography
|
|
1096
|
-
variant="alpha"
|
|
1097
|
-
textColor="neutral800"
|
|
1098
|
-
style={{
|
|
1099
|
-
fontSize: '1.5rem',
|
|
1100
|
-
fontWeight: '700',
|
|
1101
|
-
marginBottom: '4px',
|
|
1102
|
-
}}
|
|
1103
|
-
>
|
|
1104
|
-
{t('homepage.noResults.title', 'No sessions found')}
|
|
1105
|
-
</Typography>
|
|
1106
|
-
|
|
1107
|
-
<Typography
|
|
1108
|
-
variant="omega"
|
|
1109
|
-
textColor="neutral600"
|
|
1110
|
-
style={{
|
|
1111
|
-
fontSize: '1rem',
|
|
1112
|
-
maxWidth: '400px',
|
|
1113
|
-
lineHeight: '1.6',
|
|
1114
|
-
}}
|
|
1115
|
-
>
|
|
1116
|
-
{t('homepage.noResults.description', 'Try adjusting your search query or filters to find sessions')}
|
|
1117
|
-
</Typography>
|
|
1118
|
-
</Flex>
|
|
1119
|
-
</Box>
|
|
1120
|
-
)}
|
|
1121
|
-
</Box>
|
|
1122
|
-
)}
|
|
1123
|
-
|
|
1124
|
-
{/* Empty State */}
|
|
1125
|
-
{!loading && sessions.length === 0 && (
|
|
1126
|
-
<Box
|
|
1127
|
-
background="neutral0"
|
|
1128
|
-
style={{
|
|
1129
|
-
borderRadius: theme.borderRadius.xl,
|
|
1130
|
-
border: '2px dashed #E5E7EB',
|
|
1131
|
-
padding: '80px 32px',
|
|
1132
|
-
textAlign: 'center',
|
|
1133
|
-
position: 'relative',
|
|
1134
|
-
overflow: 'hidden',
|
|
1135
|
-
minHeight: '400px',
|
|
1136
|
-
display: 'flex',
|
|
1137
|
-
alignItems: 'center',
|
|
1138
|
-
justifyContent: 'center',
|
|
1139
|
-
}}
|
|
1140
|
-
>
|
|
1141
|
-
{/* Background Gradient */}
|
|
1142
|
-
<Box
|
|
1143
|
-
style={{
|
|
1144
|
-
position: 'absolute',
|
|
1145
|
-
top: 0,
|
|
1146
|
-
left: 0,
|
|
1147
|
-
right: 0,
|
|
1148
|
-
bottom: 0,
|
|
1149
|
-
background: `linear-gradient(135deg, ${theme.colors.primary[50]} 0%, ${theme.colors.secondary[50]} 100%)`,
|
|
1150
|
-
opacity: 0.3,
|
|
1151
|
-
zIndex: 0,
|
|
1152
|
-
}}
|
|
1153
|
-
/>
|
|
1154
|
-
|
|
1155
|
-
{/* Floating Icon (removed emoji) */}
|
|
1156
|
-
|
|
1157
|
-
<Flex direction="column" alignItems="center" gap={6} style={{ position: 'relative', zIndex: 1 }}>
|
|
1158
|
-
<Box
|
|
1159
|
-
style={{
|
|
1160
|
-
width: '120px',
|
|
1161
|
-
height: '120px',
|
|
1162
|
-
borderRadius: '50%',
|
|
1163
|
-
background: `linear-gradient(135deg, ${theme.colors.primary[100]} 0%, ${theme.colors.secondary[100]} 100%)`,
|
|
1164
|
-
display: 'flex',
|
|
1165
|
-
alignItems: 'center',
|
|
1166
|
-
justifyContent: 'center',
|
|
1167
|
-
boxShadow: theme.shadows.xl,
|
|
1168
|
-
}}
|
|
1169
|
-
>
|
|
1170
|
-
<Monitor style={{ width: '60px', height: '60px', color: '#0284C7' }} />
|
|
1171
|
-
</Box>
|
|
1172
|
-
|
|
1173
|
-
<Typography
|
|
1174
|
-
variant="alpha"
|
|
1175
|
-
textColor="neutral800"
|
|
1176
|
-
style={{
|
|
1177
|
-
fontSize: '1.75rem',
|
|
1178
|
-
fontWeight: '700',
|
|
1179
|
-
marginBottom: '8px',
|
|
1180
|
-
}}
|
|
1181
|
-
>
|
|
1182
|
-
{t('homepage.empty.title', 'No sessions yet')}
|
|
1183
|
-
</Typography>
|
|
1184
|
-
|
|
1185
|
-
<Typography
|
|
1186
|
-
variant="omega"
|
|
1187
|
-
textColor="neutral600"
|
|
1188
|
-
style={{
|
|
1189
|
-
fontSize: '1rem',
|
|
1190
|
-
maxWidth: '500px',
|
|
1191
|
-
lineHeight: '1.6',
|
|
1192
|
-
}}
|
|
1193
|
-
>
|
|
1194
|
-
{t('homepage.empty.description', 'Sessions will appear here when users log in to your application')}
|
|
1195
|
-
</Typography>
|
|
1196
|
-
</Flex>
|
|
1197
|
-
</Box>
|
|
1198
|
-
)}
|
|
1199
|
-
|
|
1200
|
-
{/* Session Detail Modal */}
|
|
1201
|
-
{showDetailModal && selectedSession && (
|
|
1202
|
-
<SessionDetailModal
|
|
1203
|
-
session={selectedSession}
|
|
1204
|
-
onClose={handleModalClose}
|
|
1205
|
-
onSessionTerminated={handleSessionTerminated}
|
|
1206
|
-
/>
|
|
1207
|
-
)}
|
|
1208
|
-
</Container>
|
|
1209
|
-
);
|
|
1210
|
-
};
|
|
1211
|
-
|
|
1212
|
-
export default HomePage;
|