strapi-plugin-magic-sessionmanager 4.0.0 → 4.0.2
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/README.md +2 -2
- package/admin/src/components/LicenseGuard.jsx +6 -6
- package/admin/src/components/OnlineUsersWidget.jsx +11 -7
- package/admin/src/components/SessionDetailModal.jsx +45 -41
- package/admin/src/components/SessionInfoCard.jsx +3 -3
- package/admin/src/components/SessionInfoPanel.jsx +31 -21
- package/admin/src/index.js +9 -0
- package/admin/src/pages/Analytics.jsx +2 -2
- package/admin/src/pages/HomePage.jsx +129 -165
- package/admin/src/pages/License.jsx +5 -5
- package/admin/src/pages/Settings.jsx +148 -144
- package/admin/src/pages/SettingsNew.jsx +21 -21
- package/admin/src/pages/UpgradePage.jsx +448 -0
- package/admin/src/pluginId.js +1 -0
- package/admin/src/translations/de.json +294 -15
- package/admin/src/translations/en.json +293 -14
- package/admin/src/translations/es.json +284 -18
- package/admin/src/translations/fr.json +284 -18
- package/admin/src/translations/pt.json +284 -18
- package/admin/src/utils/parseUserAgent.js +6 -6
- package/admin/src/utils/theme.js +85 -0
- package/dist/_chunks/{Analytics-mYu_uGwU.mjs → Analytics-DTE_zmRV.mjs} +4 -4
- package/dist/_chunks/{Analytics-ioaeEh-E.js → Analytics-lw_JaOVy.js} +4 -4
- package/dist/_chunks/{App-DdnUYWbC.js → App-DDKYCjKw.js} +221 -216
- package/dist/_chunks/{App-BXpIS12l.mjs → App-DJW1ZNl5.mjs} +221 -216
- package/dist/_chunks/{License-C03C2j9P.mjs → License-DaOFuImm.mjs} +6 -10
- package/dist/_chunks/{License-DZYrOgcx.js → License-Tk-6UfPl.js} +6 -10
- package/dist/_chunks/{OnlineUsersWidget-B8JS1xZu.js → OnlineUsersWidget-C1qTpsws.js} +11 -7
- package/dist/_chunks/{OnlineUsersWidget-ArMl0nen.mjs → OnlineUsersWidget-CADphbXG.mjs} +11 -7
- package/dist/_chunks/{Settings-0ocB3qHk.mjs → Settings-C9xvckgq.mjs} +200 -188
- package/dist/_chunks/{Settings-C6_CqpCC.js → Settings-DyEAuTNQ.js} +200 -188
- package/dist/_chunks/UpgradePage-Dssk8A0Z.js +354 -0
- package/dist/_chunks/UpgradePage-cINvE9zY.mjs +352 -0
- package/dist/_chunks/de-CDA1V0rF.mjs +292 -0
- package/dist/_chunks/de-I-Q-pWqu.js +292 -0
- package/dist/_chunks/en-Bd7_h-4e.js +292 -0
- package/dist/_chunks/en-DzmOCyzQ.mjs +292 -0
- package/dist/_chunks/es-BcAx18XG.js +277 -0
- package/dist/_chunks/es-Cx-SN6qV.mjs +277 -0
- package/dist/_chunks/fr-DCzYMuJ-.js +277 -0
- package/dist/_chunks/fr-DXlXE5Eo.mjs +277 -0
- package/dist/_chunks/{index-DC8Y0qxx.js → index-CWcvrfXc.js} +52 -49
- package/dist/_chunks/{index-DBRS3kt5.mjs → index-DQO9bNP7.mjs} +52 -49
- package/dist/_chunks/pt-21-MAb72.js +277 -0
- package/dist/_chunks/pt-zsdTSjba.mjs +277 -0
- package/dist/_chunks/{useLicense-qgGfMvse.js → useLicense-DtvJOszr.js} +1 -1
- package/dist/_chunks/{useLicense-DSLL9n3Y.mjs → useLicense-DxbD4Wf8.mjs} +1 -1
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/server/index.js +142 -33
- package/dist/server/index.mjs +142 -33
- package/package.json +1 -1
- package/server/src/bootstrap.js +76 -4
- package/server/src/controllers/session.js +59 -9
- package/server/src/middlewares/last-seen.js +5 -4
- package/server/src/routes/content-api.js +11 -2
- package/server/src/services/notifications.js +10 -10
- package/server/src/services/session.js +24 -4
- package/dist/_chunks/de-BxFx1pwE.js +0 -23
- package/dist/_chunks/de-CdO3s01z.mjs +0 -23
- package/dist/_chunks/en-CsPpPJL3.mjs +0 -23
- package/dist/_chunks/en-RqmpDHdS.js +0 -23
- package/dist/_chunks/es-CuLHazN1.js +0 -23
- package/dist/_chunks/es-Dkmjhy9c.mjs +0 -23
- package/dist/_chunks/fr-BAJp2yhI.js +0 -23
- package/dist/_chunks/fr-Bssg_3UF.mjs +0 -23
- package/dist/_chunks/pt-BAP9cKs3.js +0 -23
- package/dist/_chunks/pt-BVNoNcuY.mjs +0 -23
package/README.md
CHANGED
|
@@ -136,7 +136,7 @@ npm run develop
|
|
|
136
136
|
3. Click it!
|
|
137
137
|
4. You'll see the dashboard (like the screenshot above)
|
|
138
138
|
|
|
139
|
-
**That's it! You're done!**
|
|
139
|
+
**That's it! You're done!**
|
|
140
140
|
|
|
141
141
|
---
|
|
142
142
|
|
|
@@ -621,4 +621,4 @@ Language automatically follows your Strapi admin interface setting.
|
|
|
621
621
|
|
|
622
622
|
---
|
|
623
623
|
|
|
624
|
-
**Made
|
|
624
|
+
**Made for Strapi v5**
|
|
@@ -407,12 +407,12 @@ const LicenseGuard = ({ children }) => {
|
|
|
407
407
|
>
|
|
408
408
|
<Typography variant="omega" style={{ fontSize: '13px', lineHeight: '1.6' }}>
|
|
409
409
|
{useExistingKey
|
|
410
|
-
? '
|
|
410
|
+
? 'Enter your email and license key to activate.'
|
|
411
411
|
: useAutoCreate && adminUser && adminUser.email
|
|
412
|
-
?
|
|
412
|
+
? `Click "Activate" to auto-create a license with your account (${adminUser.email})`
|
|
413
413
|
: useAutoCreate
|
|
414
|
-
? '
|
|
415
|
-
: '
|
|
414
|
+
? 'Click "Activate" to auto-create a license with your admin account'
|
|
415
|
+
: 'A license will be created with the details below.'
|
|
416
416
|
}
|
|
417
417
|
</Typography>
|
|
418
418
|
</Box>
|
|
@@ -476,10 +476,10 @@ const LicenseGuard = ({ children }) => {
|
|
|
476
476
|
Ready to activate with your account:
|
|
477
477
|
</Typography>
|
|
478
478
|
<Typography variant="pi" style={{ marginBottom: '4px', display: 'block' }}>
|
|
479
|
-
|
|
479
|
+
{adminUser.firstname || 'Admin'} {adminUser.lastname || 'User'}
|
|
480
480
|
</Typography>
|
|
481
481
|
<Typography variant="pi" textColor="neutral600">
|
|
482
|
-
|
|
482
|
+
{adminUser.email || 'Loading...'}
|
|
483
483
|
</Typography>
|
|
484
484
|
</Box>
|
|
485
485
|
) : (
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import { useIntl } from 'react-intl';
|
|
2
3
|
import { Box, Typography, Flex, Grid } from '@strapi/design-system';
|
|
3
4
|
import { Check, Cross, Clock, User, Monitor } from '@strapi/icons';
|
|
4
5
|
import { useFetchClient } from '@strapi/strapi/admin';
|
|
6
|
+
import { getTranslation } from '../utils/getTranslation';
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Online Users Widget - Dashboard widget showing user activity statistics
|
|
8
10
|
* Styled exactly like Project Statistics
|
|
9
11
|
*/
|
|
10
12
|
const OnlineUsersWidget = () => {
|
|
13
|
+
const { formatMessage } = useIntl();
|
|
11
14
|
const { get } = useFetchClient();
|
|
15
|
+
const t = (id, defaultMessage, values) => formatMessage({ id: getTranslation(id), defaultMessage }, values);
|
|
12
16
|
const [stats, setStats] = useState({
|
|
13
17
|
onlineNow: 0,
|
|
14
18
|
offline: 0,
|
|
@@ -137,7 +141,7 @@ const OnlineUsersWidget = () => {
|
|
|
137
141
|
if (loading) {
|
|
138
142
|
return (
|
|
139
143
|
<Box padding={4}>
|
|
140
|
-
<Typography variant="pi" textColor="neutral600">Loading
|
|
144
|
+
<Typography variant="pi" textColor="neutral600">{t('common.loading', 'Loading...')}</Typography>
|
|
141
145
|
</Box>
|
|
142
146
|
);
|
|
143
147
|
}
|
|
@@ -149,7 +153,7 @@ const OnlineUsersWidget = () => {
|
|
|
149
153
|
<Grid.Item col={1}>
|
|
150
154
|
<StatCard
|
|
151
155
|
icon={Check}
|
|
152
|
-
label=
|
|
156
|
+
label={t('widget.stats.onlineNow', 'Online Now')}
|
|
153
157
|
value={stats.onlineNow}
|
|
154
158
|
color="success"
|
|
155
159
|
/>
|
|
@@ -158,7 +162,7 @@ const OnlineUsersWidget = () => {
|
|
|
158
162
|
<Grid.Item col={1}>
|
|
159
163
|
<StatCard
|
|
160
164
|
icon={Cross}
|
|
161
|
-
label=
|
|
165
|
+
label={t('widget.stats.offline', 'Offline')}
|
|
162
166
|
value={stats.offline}
|
|
163
167
|
color="neutral"
|
|
164
168
|
/>
|
|
@@ -167,7 +171,7 @@ const OnlineUsersWidget = () => {
|
|
|
167
171
|
<Grid.Item col={1}>
|
|
168
172
|
<StatCard
|
|
169
173
|
icon={Clock}
|
|
170
|
-
label=
|
|
174
|
+
label={t('widget.stats.last15min', 'Last 15 min')}
|
|
171
175
|
value={stats.last15min}
|
|
172
176
|
color="primary"
|
|
173
177
|
/>
|
|
@@ -176,7 +180,7 @@ const OnlineUsersWidget = () => {
|
|
|
176
180
|
<Grid.Item col={1}>
|
|
177
181
|
<StatCard
|
|
178
182
|
icon={Clock}
|
|
179
|
-
label=
|
|
183
|
+
label={t('widget.stats.last30min', 'Last 30 min')}
|
|
180
184
|
value={stats.last30min}
|
|
181
185
|
color="secondary"
|
|
182
186
|
/>
|
|
@@ -185,7 +189,7 @@ const OnlineUsersWidget = () => {
|
|
|
185
189
|
<Grid.Item col={1}>
|
|
186
190
|
<StatCard
|
|
187
191
|
icon={User}
|
|
188
|
-
label=
|
|
192
|
+
label={t('widget.stats.totalUsers', 'Total Users')}
|
|
189
193
|
value={stats.totalUsers}
|
|
190
194
|
color="neutral"
|
|
191
195
|
/>
|
|
@@ -194,7 +198,7 @@ const OnlineUsersWidget = () => {
|
|
|
194
198
|
<Grid.Item col={1}>
|
|
195
199
|
<StatCard
|
|
196
200
|
icon={Cross}
|
|
197
|
-
label=
|
|
201
|
+
label={t('widget.stats.blocked', 'Blocked')}
|
|
198
202
|
value={stats.blocked}
|
|
199
203
|
color="danger"
|
|
200
204
|
/>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react';
|
|
2
|
+
import { useIntl } from 'react-intl';
|
|
2
3
|
import styled from 'styled-components';
|
|
3
4
|
import {
|
|
4
5
|
Modal,
|
|
@@ -25,6 +26,7 @@ import { useFetchClient, useNotification } from '@strapi/strapi/admin';
|
|
|
25
26
|
import parseUserAgent from '../utils/parseUserAgent';
|
|
26
27
|
import pluginId from '../pluginId';
|
|
27
28
|
import { useLicense } from '../hooks/useLicense';
|
|
29
|
+
import { getTranslation } from '../utils/getTranslation';
|
|
28
30
|
|
|
29
31
|
const TwoColumnGrid = styled.div`
|
|
30
32
|
display: grid;
|
|
@@ -53,6 +55,7 @@ const Section = styled(Box)`
|
|
|
53
55
|
`;
|
|
54
56
|
|
|
55
57
|
const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
|
|
58
|
+
const { formatMessage } = useIntl();
|
|
56
59
|
const { get, post } = useFetchClient();
|
|
57
60
|
const { toggleNotification } = useNotification();
|
|
58
61
|
const { isPremium, loading: licenseLoading } = useLicense();
|
|
@@ -60,6 +63,7 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
|
|
|
60
63
|
const [showUserAgent, setShowUserAgent] = useState(false);
|
|
61
64
|
const [geoData, setGeoData] = useState(null);
|
|
62
65
|
const [geoLoading, setGeoLoading] = useState(false);
|
|
66
|
+
const t = (id, defaultMessage, values) => formatMessage({ id: getTranslation(id), defaultMessage }, values);
|
|
63
67
|
|
|
64
68
|
if (!session) return null;
|
|
65
69
|
|
|
@@ -82,7 +86,7 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
|
|
|
82
86
|
console.error('[SessionDetailModal] Error fetching geolocation:', err);
|
|
83
87
|
// Fallback to mock data if API fails
|
|
84
88
|
setGeoData({
|
|
85
|
-
country_flag: '
|
|
89
|
+
country_flag: '',
|
|
86
90
|
country: 'Unknown',
|
|
87
91
|
city: 'Unknown',
|
|
88
92
|
timezone: 'Unknown',
|
|
@@ -98,7 +102,7 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
|
|
|
98
102
|
|
|
99
103
|
// Use real data if available, otherwise fallback
|
|
100
104
|
const premiumData = geoData || {
|
|
101
|
-
country_flag: '
|
|
105
|
+
country_flag: '',
|
|
102
106
|
country: 'Loading...',
|
|
103
107
|
city: 'Loading...',
|
|
104
108
|
timezone: 'Loading...',
|
|
@@ -117,7 +121,7 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
|
|
|
117
121
|
const DeviceIcon = getDeviceIcon(deviceInfo.device);
|
|
118
122
|
|
|
119
123
|
const handleTerminate = async () => {
|
|
120
|
-
if (!confirm('Are you sure you want to terminate this session?')) {
|
|
124
|
+
if (!confirm(t('modal.confirm.terminate', 'Are you sure you want to terminate this session?'))) {
|
|
121
125
|
return;
|
|
122
126
|
}
|
|
123
127
|
|
|
@@ -127,7 +131,7 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
|
|
|
127
131
|
|
|
128
132
|
toggleNotification({
|
|
129
133
|
type: 'success',
|
|
130
|
-
message: 'Session terminated successfully',
|
|
134
|
+
message: t('notifications.success.terminated', 'Session terminated successfully'),
|
|
131
135
|
});
|
|
132
136
|
|
|
133
137
|
onSessionTerminated();
|
|
@@ -136,7 +140,7 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
|
|
|
136
140
|
console.error('[SessionDetailModal] Error:', err);
|
|
137
141
|
toggleNotification({
|
|
138
142
|
type: 'danger',
|
|
139
|
-
message: 'Failed to terminate session',
|
|
143
|
+
message: t('notifications.error.terminate', 'Failed to terminate session'),
|
|
140
144
|
});
|
|
141
145
|
} finally {
|
|
142
146
|
setTerminating(false);
|
|
@@ -188,10 +192,10 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
|
|
|
188
192
|
</Box>
|
|
189
193
|
<Flex direction="column" alignItems="flex-start">
|
|
190
194
|
<Typography variant="beta" fontWeight="bold">
|
|
191
|
-
Session Details
|
|
195
|
+
{t('modal.title', 'Session Details')}
|
|
192
196
|
</Typography>
|
|
193
197
|
<Typography variant="pi" textColor="neutral600">
|
|
194
|
-
ID: {session.id}
|
|
198
|
+
{t('modal.id', 'ID: {id}', { id: session.id })}
|
|
195
199
|
</Typography>
|
|
196
200
|
</Flex>
|
|
197
201
|
</Flex>
|
|
@@ -207,7 +211,7 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
|
|
|
207
211
|
size="M"
|
|
208
212
|
style={{ fontSize: '14px', padding: '8px 20px', fontWeight: '600' }}
|
|
209
213
|
>
|
|
210
|
-
{isOnline ? '
|
|
214
|
+
{isOnline ? t('modal.status.online', 'ONLINE') : t('modal.status.offline', 'OFFLINE')}
|
|
211
215
|
</Badge>
|
|
212
216
|
</Flex>
|
|
213
217
|
|
|
@@ -220,24 +224,24 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
|
|
|
220
224
|
{/* User Information */}
|
|
221
225
|
<Section>
|
|
222
226
|
<SectionTitle>
|
|
223
|
-
|
|
227
|
+
{t('modal.section.user', 'User')}
|
|
224
228
|
</SectionTitle>
|
|
225
229
|
|
|
226
|
-
<DetailRow compact icon={Check} label=
|
|
227
|
-
<DetailRow compact icon={Information} label=
|
|
228
|
-
<DetailRow compact icon={Information} label=
|
|
230
|
+
<DetailRow compact icon={Check} label={t('modal.user.username', 'Username')} value={session.user?.username || t('modal.user.na', 'N/A')} />
|
|
231
|
+
<DetailRow compact icon={Information} label={t('modal.user.email', 'Email')} value={session.user?.email || t('modal.user.na', 'N/A')} />
|
|
232
|
+
<DetailRow compact icon={Information} label={t('modal.user.id', 'User ID')} value={session.user?.id || t('modal.user.na', 'N/A')} />
|
|
229
233
|
</Section>
|
|
230
234
|
|
|
231
235
|
{/* Device Information */}
|
|
232
236
|
<Section>
|
|
233
237
|
<SectionTitle>
|
|
234
|
-
|
|
238
|
+
{t('modal.section.device', 'Device')}
|
|
235
239
|
</SectionTitle>
|
|
236
240
|
|
|
237
|
-
<DetailRow compact icon={DeviceIcon} label=
|
|
238
|
-
<DetailRow compact icon={Monitor} label=
|
|
239
|
-
<DetailRow compact icon={Server} label=
|
|
240
|
-
<DetailRow compact icon={Information} label=
|
|
241
|
+
<DetailRow compact icon={DeviceIcon} label={t('modal.device.device', 'Device')} value={deviceInfo.device} />
|
|
242
|
+
<DetailRow compact icon={Monitor} label={t('modal.device.browser', 'Browser')} value={`${deviceInfo.browser} ${deviceInfo.browserVersion || ''}`} />
|
|
243
|
+
<DetailRow compact icon={Server} label={t('modal.device.os', 'OS')} value={deviceInfo.os} />
|
|
244
|
+
<DetailRow compact icon={Information} label={t('modal.device.ip', 'IP')} value={session.ipAddress} />
|
|
241
245
|
</Section>
|
|
242
246
|
</Box>
|
|
243
247
|
|
|
@@ -245,13 +249,13 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
|
|
|
245
249
|
<Box>
|
|
246
250
|
<Section>
|
|
247
251
|
<SectionTitle>
|
|
248
|
-
|
|
252
|
+
{t('modal.section.timeline', 'Timeline')}
|
|
249
253
|
</SectionTitle>
|
|
250
254
|
|
|
251
255
|
<DetailRow
|
|
252
256
|
compact
|
|
253
257
|
icon={Clock}
|
|
254
|
-
label=
|
|
258
|
+
label={t('modal.timeline.login', 'Login')}
|
|
255
259
|
value={new Date(session.loginTime).toLocaleString('de-DE', {
|
|
256
260
|
day: '2-digit',
|
|
257
261
|
month: 'short',
|
|
@@ -262,7 +266,7 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
|
|
|
262
266
|
<DetailRow
|
|
263
267
|
compact
|
|
264
268
|
icon={Clock}
|
|
265
|
-
label=
|
|
269
|
+
label={t('modal.timeline.lastActive', 'Last Active')}
|
|
266
270
|
value={new Date(session.lastActive || session.loginTime).toLocaleString('de-DE', {
|
|
267
271
|
day: '2-digit',
|
|
268
272
|
month: 'short',
|
|
@@ -273,14 +277,14 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
|
|
|
273
277
|
<DetailRow
|
|
274
278
|
compact
|
|
275
279
|
icon={Clock}
|
|
276
|
-
label=
|
|
277
|
-
value={
|
|
280
|
+
label={t('modal.timeline.idleTime', 'Idle Time')}
|
|
281
|
+
value={t('modal.timeline.minutes', '{minutes} min', { minutes: session.minutesSinceActive })}
|
|
278
282
|
/>
|
|
279
283
|
{session.logoutTime && (
|
|
280
284
|
<DetailRow
|
|
281
285
|
compact
|
|
282
286
|
icon={Cross}
|
|
283
|
-
label=
|
|
287
|
+
label={t('modal.timeline.logout', 'Logout')}
|
|
284
288
|
value={new Date(session.logoutTime).toLocaleString('de-DE', {
|
|
285
289
|
day: '2-digit',
|
|
286
290
|
month: 'short',
|
|
@@ -297,13 +301,13 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
|
|
|
297
301
|
{isPremium ? (
|
|
298
302
|
<Section>
|
|
299
303
|
<SectionTitle>
|
|
300
|
-
|
|
304
|
+
{t('modal.section.security', 'Location and Security')}
|
|
301
305
|
</SectionTitle>
|
|
302
306
|
|
|
303
307
|
{geoLoading ? (
|
|
304
308
|
<Box padding={4} style={{ textAlign: 'center' }}>
|
|
305
309
|
<Typography variant="pi" textColor="neutral600">
|
|
306
|
-
Loading location data...
|
|
310
|
+
{t('modal.security.loading', 'Loading location data...')}
|
|
307
311
|
</Typography>
|
|
308
312
|
</Box>
|
|
309
313
|
) : (
|
|
@@ -312,30 +316,30 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
|
|
|
312
316
|
<DetailRow
|
|
313
317
|
compact
|
|
314
318
|
icon={Earth}
|
|
315
|
-
label=
|
|
316
|
-
value={`${premiumData.country_flag || '
|
|
319
|
+
label={t('modal.security.country', 'Country')}
|
|
320
|
+
value={`${premiumData.country_flag || ''} ${premiumData.country}`.trim()}
|
|
317
321
|
/>
|
|
318
|
-
<DetailRow compact icon={Earth} label=
|
|
319
|
-
<DetailRow compact icon={Clock} label=
|
|
322
|
+
<DetailRow compact icon={Earth} label={t('modal.security.city', 'City')} value={premiumData.city} />
|
|
323
|
+
<DetailRow compact icon={Clock} label={t('modal.security.timezone', 'Timezone')} value={premiumData.timezone} />
|
|
320
324
|
</Box>
|
|
321
325
|
<Box>
|
|
322
326
|
<DetailRow
|
|
323
327
|
compact
|
|
324
328
|
icon={Shield}
|
|
325
|
-
label=
|
|
329
|
+
label={t('modal.security.score', 'Security')}
|
|
326
330
|
value={`${premiumData.securityScore}/100 (${premiumData.riskLevel})`}
|
|
327
331
|
/>
|
|
328
332
|
<DetailRow
|
|
329
333
|
compact
|
|
330
334
|
icon={Shield}
|
|
331
|
-
label=
|
|
332
|
-
value={premiumData.isVpn ? '
|
|
335
|
+
label={t('modal.security.vpn', 'VPN')}
|
|
336
|
+
value={premiumData.isVpn ? t('modal.security.vpnWarning', '[WARNING] Yes') : t('modal.security.no', 'No')}
|
|
333
337
|
/>
|
|
334
338
|
<DetailRow
|
|
335
339
|
compact
|
|
336
340
|
icon={Shield}
|
|
337
|
-
label=
|
|
338
|
-
value={premiumData.isProxy ? '
|
|
341
|
+
label={t('modal.security.proxy', 'Proxy')}
|
|
342
|
+
value={premiumData.isProxy ? t('modal.security.vpnWarning', '[WARNING] Yes') : t('modal.security.no', 'No')}
|
|
339
343
|
/>
|
|
340
344
|
</Box>
|
|
341
345
|
</TwoColumnGrid>
|
|
@@ -355,10 +359,10 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
|
|
|
355
359
|
<Flex direction="column" alignItems="center" gap={3}>
|
|
356
360
|
<Crown style={{ width: '40px', height: '40px', color: '#d97706' }} />
|
|
357
361
|
<Typography variant="beta" style={{ color: '#92400e', fontWeight: '700' }}>
|
|
358
|
-
|
|
362
|
+
{t('modal.premium.title', 'Location and Security Analysis')}
|
|
359
363
|
</Typography>
|
|
360
364
|
<Typography variant="omega" style={{ color: '#78350f', fontSize: '14px', lineHeight: '1.6' }}>
|
|
361
|
-
Unlock premium features to get IP geolocation, security scoring, and VPN/Proxy detection for every session
|
|
365
|
+
{t('modal.premium.description', 'Unlock premium features to get IP geolocation, security scoring, and VPN/Proxy detection for every session')}
|
|
362
366
|
</Typography>
|
|
363
367
|
<Button
|
|
364
368
|
variant="secondary"
|
|
@@ -373,7 +377,7 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
|
|
|
373
377
|
boxShadow: '0 4px 12px rgba(245, 158, 11, 0.3)',
|
|
374
378
|
}}
|
|
375
379
|
>
|
|
376
|
-
Upgrade to Premium
|
|
380
|
+
{t('modal.premium.upgrade', 'Upgrade to Premium')}
|
|
377
381
|
</Button>
|
|
378
382
|
</Flex>
|
|
379
383
|
</Box>
|
|
@@ -384,7 +388,7 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
|
|
|
384
388
|
<Section>
|
|
385
389
|
<Flex justifyContent="space-between" alignItems="center" style={{ marginBottom: '12px' }}>
|
|
386
390
|
<SectionTitle style={{ marginBottom: 0, paddingBottom: 0, border: 'none' }}>
|
|
387
|
-
|
|
391
|
+
{t('modal.section.technical', 'Technical Details')}
|
|
388
392
|
</SectionTitle>
|
|
389
393
|
<Button
|
|
390
394
|
variant="tertiary"
|
|
@@ -392,7 +396,7 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
|
|
|
392
396
|
onClick={() => setShowUserAgent(!showUserAgent)}
|
|
393
397
|
style={{ fontSize: '12px' }}
|
|
394
398
|
>
|
|
395
|
-
{showUserAgent ? '
|
|
399
|
+
{showUserAgent ? `▲ ${t('modal.technical.hide', 'Hide Details')}` : `▼ ${t('modal.technical.show', 'Show Details')}`}
|
|
396
400
|
</Button>
|
|
397
401
|
</Flex>
|
|
398
402
|
|
|
@@ -423,7 +427,7 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
|
|
|
423
427
|
<Modal.Footer>
|
|
424
428
|
<Flex justifyContent="space-between" style={{ width: '100%' }}>
|
|
425
429
|
<Button onClick={onClose} variant="tertiary">
|
|
426
|
-
Close
|
|
430
|
+
{t('modal.actions.close', 'Close')}
|
|
427
431
|
</Button>
|
|
428
432
|
<Button
|
|
429
433
|
onClick={handleTerminate}
|
|
@@ -432,7 +436,7 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
|
|
|
432
436
|
loading={terminating}
|
|
433
437
|
startIcon={<Cross />}
|
|
434
438
|
>
|
|
435
|
-
Terminate Session
|
|
439
|
+
{t('modal.actions.terminate', 'Terminate Session')}
|
|
436
440
|
</Button>
|
|
437
441
|
</Flex>
|
|
438
442
|
</Modal.Footer>
|
|
@@ -76,11 +76,11 @@ const SessionInfoCard = ({ id, model }) => {
|
|
|
76
76
|
</Typography>
|
|
77
77
|
{isOnline ? (
|
|
78
78
|
<Badge active backgroundColor="success500">
|
|
79
|
-
|
|
79
|
+
Online
|
|
80
80
|
</Badge>
|
|
81
81
|
) : (
|
|
82
82
|
<Badge backgroundColor="neutral150">
|
|
83
|
-
|
|
83
|
+
Offline
|
|
84
84
|
</Badge>
|
|
85
85
|
)}
|
|
86
86
|
</Flex>
|
|
@@ -110,7 +110,7 @@ const SessionInfoCard = ({ id, model }) => {
|
|
|
110
110
|
marginBottom={2}
|
|
111
111
|
>
|
|
112
112
|
<Typography variant="pi" textColor="neutral800">
|
|
113
|
-
|
|
113
|
+
IP: {session.ipAddress}
|
|
114
114
|
</Typography>
|
|
115
115
|
<Typography variant="pi" textColor="neutral600" fontSize="11px">
|
|
116
116
|
{new Date(session.loginTime).toLocaleString()}
|
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react';
|
|
2
|
+
import { useIntl } from 'react-intl';
|
|
2
3
|
import { Box, Typography, Flex, Button, Badge, Divider } from '@strapi/design-system';
|
|
3
4
|
import { Check, Cross, Monitor, Phone, Server, Clock } from '@strapi/icons';
|
|
4
5
|
import { useFetchClient, useNotification } from '@strapi/strapi/admin';
|
|
5
6
|
import parseUserAgent from '../utils/parseUserAgent';
|
|
7
|
+
import { getTranslation } from '../utils/getTranslation';
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* Session Info Panel - Native Strapi design
|
|
9
11
|
* Clean, professional sidebar panel for Content Manager
|
|
10
12
|
*/
|
|
11
13
|
const SessionInfoPanel = ({ documentId, model, document }) => {
|
|
14
|
+
const { formatMessage } = useIntl();
|
|
12
15
|
const [sessions, setSessions] = useState([]);
|
|
13
16
|
const [loading, setLoading] = useState(true);
|
|
14
17
|
const [isBlocked, setIsBlocked] = useState(false);
|
|
15
18
|
const [actionLoading, setActionLoading] = useState(false);
|
|
16
19
|
const { get, post: postRequest } = useFetchClient();
|
|
17
20
|
const { toggleNotification } = useNotification();
|
|
21
|
+
const t = (id, defaultMessage, values) => formatMessage({ id: getTranslation(id), defaultMessage }, values);
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
// Strapi v5: Use documentId (string UUID) instead of numeric id
|
|
24
|
+
const userId = document?.documentId || documentId;
|
|
20
25
|
|
|
21
26
|
useEffect(() => {
|
|
22
27
|
if (model !== 'plugin::users-permissions.user' || !userId) {
|
|
@@ -52,14 +57,14 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
|
|
|
52
57
|
if (response.data?.success) {
|
|
53
58
|
toggleNotification({
|
|
54
59
|
type: 'success',
|
|
55
|
-
message: 'All sessions terminated successfully',
|
|
60
|
+
message: t('notifications.success.terminatedAll', 'All sessions terminated successfully'),
|
|
56
61
|
});
|
|
57
62
|
setSessions([]);
|
|
58
63
|
}
|
|
59
64
|
} catch (error) {
|
|
60
65
|
toggleNotification({
|
|
61
66
|
type: 'warning',
|
|
62
|
-
message: 'Failed to terminate sessions',
|
|
67
|
+
message: t('notifications.error.terminateAll', 'Failed to terminate sessions'),
|
|
63
68
|
});
|
|
64
69
|
console.error('[SessionInfoPanel] Logout all error:', error);
|
|
65
70
|
} finally {
|
|
@@ -80,7 +85,9 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
|
|
|
80
85
|
|
|
81
86
|
toggleNotification({
|
|
82
87
|
type: 'success',
|
|
83
|
-
message: newBlockedStatus
|
|
88
|
+
message: newBlockedStatus
|
|
89
|
+
? t('notifications.success.blocked', 'User blocked successfully')
|
|
90
|
+
: t('notifications.success.unblocked', 'User unblocked successfully'),
|
|
84
91
|
});
|
|
85
92
|
|
|
86
93
|
if (newBlockedStatus) {
|
|
@@ -90,7 +97,7 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
|
|
|
90
97
|
} catch (error) {
|
|
91
98
|
toggleNotification({
|
|
92
99
|
type: 'warning',
|
|
93
|
-
message: 'Failed to update user status',
|
|
100
|
+
message: t('notifications.error.block', 'Failed to update user status'),
|
|
94
101
|
});
|
|
95
102
|
console.error('[SessionInfoPanel] Toggle block error:', error);
|
|
96
103
|
} finally {
|
|
@@ -111,10 +118,10 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
|
|
|
111
118
|
|
|
112
119
|
if (loading) {
|
|
113
120
|
return {
|
|
114
|
-
title: 'Session Info',
|
|
121
|
+
title: t('panel.title', 'Session Info'),
|
|
115
122
|
content: (
|
|
116
123
|
<Box padding={4} background="neutral0">
|
|
117
|
-
<Typography variant="pi" textColor="neutral600">Loading
|
|
124
|
+
<Typography variant="pi" textColor="neutral600">{t('panel.loading', 'Loading...')}</Typography>
|
|
118
125
|
</Box>
|
|
119
126
|
),
|
|
120
127
|
};
|
|
@@ -123,7 +130,7 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
|
|
|
123
130
|
const isOnline = sessions.length > 0;
|
|
124
131
|
|
|
125
132
|
return {
|
|
126
|
-
title: 'Session Info',
|
|
133
|
+
title: t('panel.title', 'Session Info'),
|
|
127
134
|
content: (
|
|
128
135
|
<Box style={{ width: '100%' }}>
|
|
129
136
|
<Flex direction="column" gap={4} alignItems="stretch">
|
|
@@ -144,10 +151,10 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
|
|
|
144
151
|
size="M"
|
|
145
152
|
style={{ fontSize: '14px', padding: '6px 12px' }}
|
|
146
153
|
>
|
|
147
|
-
{isOnline ? '
|
|
154
|
+
{isOnline ? t('panel.status.active', 'ACTIVE') : t('panel.status.offline', 'OFFLINE')}
|
|
148
155
|
</Badge>
|
|
149
156
|
<Typography variant="omega" fontWeight="semiBold" textColor={isOnline ? 'success700' : 'neutral700'}>
|
|
150
|
-
{sessions.
|
|
157
|
+
{t('panel.sessions.count', '{count} active session{count, plural, one {} other {s}}', { count: sessions.length })}
|
|
151
158
|
</Typography>
|
|
152
159
|
</Flex>
|
|
153
160
|
</Box>
|
|
@@ -160,10 +167,10 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
|
|
|
160
167
|
hasRadius
|
|
161
168
|
>
|
|
162
169
|
<Typography variant="omega" fontWeight="semiBold" textColor="danger700" marginBottom={1}>
|
|
163
|
-
User is blocked
|
|
170
|
+
{t('panel.blocked.title', 'User is blocked')}
|
|
164
171
|
</Typography>
|
|
165
172
|
<Typography variant="pi" textColor="danger600">
|
|
166
|
-
Authentication disabled
|
|
173
|
+
{t('panel.blocked.description', 'Authentication disabled')}
|
|
167
174
|
</Typography>
|
|
168
175
|
</Box>
|
|
169
176
|
)}
|
|
@@ -176,7 +183,7 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
|
|
|
176
183
|
letterSpacing: '0.5px',
|
|
177
184
|
fontSize: '12px'
|
|
178
185
|
}}>
|
|
179
|
-
Active Sessions
|
|
186
|
+
{t('panel.sessions.title', 'Active Sessions')}
|
|
180
187
|
</Typography>
|
|
181
188
|
|
|
182
189
|
{sessions.slice(0, 5).map((session) => {
|
|
@@ -218,7 +225,7 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
|
|
|
218
225
|
textColor="neutral0"
|
|
219
226
|
size="S"
|
|
220
227
|
>
|
|
221
|
-
Active
|
|
228
|
+
{t('panel.sessions.active', 'Active')}
|
|
222
229
|
</Badge>
|
|
223
230
|
|
|
224
231
|
{/* Browser & OS */}
|
|
@@ -252,7 +259,10 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
|
|
|
252
259
|
{/* Show minutes since last activity */}
|
|
253
260
|
{session.minutesSinceActive !== undefined && session.minutesSinceActive < 60 && (
|
|
254
261
|
<Typography variant="pi" textColor="success600" fontWeight="semiBold">
|
|
255
|
-
|
|
262
|
+
{session.minutesSinceActive === 0
|
|
263
|
+
? t('panel.sessions.activeNow', 'Active now')
|
|
264
|
+
: t('panel.sessions.activeAgo', 'Active {minutes} min ago', { minutes: session.minutesSinceActive })
|
|
265
|
+
}
|
|
256
266
|
</Typography>
|
|
257
267
|
)}
|
|
258
268
|
</Flex>
|
|
@@ -263,7 +273,7 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
|
|
|
263
273
|
{sessions.length > 5 && (
|
|
264
274
|
<Box padding={3} background="primary100" hasRadius textAlign="center">
|
|
265
275
|
<Typography variant="pi" textColor="primary600" fontWeight="semiBold">
|
|
266
|
-
|
|
276
|
+
{t('panel.sessions.more', '+{count} more session{count, plural, one {} other {s}}', { count: sessions.length - 5 })}
|
|
267
277
|
</Typography>
|
|
268
278
|
</Box>
|
|
269
279
|
)}
|
|
@@ -287,10 +297,10 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
|
|
|
287
297
|
💤
|
|
288
298
|
</Typography>
|
|
289
299
|
<Typography variant="omega" fontWeight="semiBold" textColor="neutral700">
|
|
290
|
-
No active sessions
|
|
300
|
+
{t('panel.empty.title', 'No active sessions')}
|
|
291
301
|
</Typography>
|
|
292
302
|
<Typography variant="pi" textColor="neutral500" style={{ fontSize: '13px' }}>
|
|
293
|
-
User has not logged in yet
|
|
303
|
+
{t('panel.empty.description', 'User has not logged in yet')}
|
|
294
304
|
</Typography>
|
|
295
305
|
</Flex>
|
|
296
306
|
</Box>
|
|
@@ -305,7 +315,7 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
|
|
|
305
315
|
letterSpacing: '0.5px',
|
|
306
316
|
fontSize: '12px'
|
|
307
317
|
}}>
|
|
308
|
-
Actions
|
|
318
|
+
{t('panel.actions.title', 'Actions')}
|
|
309
319
|
</Typography>
|
|
310
320
|
|
|
311
321
|
<Button
|
|
@@ -334,7 +344,7 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
|
|
|
334
344
|
}
|
|
335
345
|
}}
|
|
336
346
|
>
|
|
337
|
-
Terminate All Sessions
|
|
347
|
+
{t('panel.actions.terminateAll', 'Terminate All Sessions')}
|
|
338
348
|
</Button>
|
|
339
349
|
|
|
340
350
|
<Button
|
|
@@ -363,7 +373,7 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
|
|
|
363
373
|
}
|
|
364
374
|
}}
|
|
365
375
|
>
|
|
366
|
-
{isBlocked ? 'Unblock User' : 'Block User'}
|
|
376
|
+
{isBlocked ? t('panel.actions.unblockUser', 'Unblock User') : t('panel.actions.blockUser', 'Block User')}
|
|
367
377
|
</Button>
|
|
368
378
|
</Flex>
|
|
369
379
|
</Flex>
|
package/admin/src/index.js
CHANGED
|
@@ -34,6 +34,15 @@ export default {
|
|
|
34
34
|
to: `/settings/${pluginId}`,
|
|
35
35
|
},
|
|
36
36
|
[
|
|
37
|
+
{
|
|
38
|
+
intlLabel: {
|
|
39
|
+
id: `${pluginId}.settings.upgrade`,
|
|
40
|
+
defaultMessage: 'Upgrade',
|
|
41
|
+
},
|
|
42
|
+
id: 'upgrade',
|
|
43
|
+
to: `/settings/${pluginId}/upgrade`,
|
|
44
|
+
Component: () => import('./pages/UpgradePage'),
|
|
45
|
+
},
|
|
37
46
|
{
|
|
38
47
|
intlLabel: {
|
|
39
48
|
id: `${pluginId}.settings.general`,
|