strapi-plugin-magic-sessionmanager 4.0.1 → 4.0.3
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/OnlineUsersWidget.jsx +11 -7
- package/admin/src/components/SessionDetailModal.jsx +42 -38
- package/admin/src/components/SessionInfoPanel.jsx +29 -20
- package/admin/src/index.js +9 -0
- package/admin/src/pages/HomePage.jsx +128 -161
- package/admin/src/pages/License.jsx +3 -3
- package/admin/src/pages/Settings.jsx +139 -135
- 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/theme.js +85 -0
- package/dist/_chunks/{Analytics-Dv9f_0eZ.mjs → Analytics-DTE_zmRV.mjs} +2 -2
- package/dist/_chunks/{Analytics-BBdv1I5y.js → Analytics-lw_JaOVy.js} +2 -2
- package/dist/_chunks/{App-CJaZPNjt.js → App-DDKYCjKw.js} +216 -206
- package/dist/_chunks/{App-CIQ-7sa7.mjs → App-DJW1ZNl5.mjs} +216 -206
- package/dist/_chunks/{License-nrmFxoBm.mjs → License-DaOFuImm.mjs} +4 -8
- package/dist/_chunks/{License-D24rgaZQ.js → License-Tk-6UfPl.js} +4 -8
- 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-D5dLEGc_.mjs → Settings-C9xvckgq.mjs} +191 -179
- package/dist/_chunks/{Settings-CqxgjU0y.js → Settings-DyEAuTNQ.js} +191 -179
- 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-WH04CS1c.js → index-CWcvrfXc.js} +45 -42
- package/dist/_chunks/{index-Duk1_Wrz.mjs → index-DQO9bNP7.mjs} +45 -42
- package/dist/_chunks/pt-21-MAb72.js +277 -0
- package/dist/_chunks/pt-zsdTSjba.mjs +277 -0
- package/dist/_chunks/{useLicense-BwOlCyhc.js → useLicense-DtvJOszr.js} +1 -1
- package/dist/_chunks/{useLicense-Ce8GaxB0.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 +1 -1
- package/dist/server/index.mjs +1 -1
- package/package.json +1 -1
- 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**
|
|
@@ -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
|
|
|
@@ -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 ? 'ONLINE' : 'OFFLINE'}
|
|
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
|
-
User
|
|
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
|
-
Device
|
|
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
|
-
Timeline
|
|
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
|
-
Location and Security
|
|
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=
|
|
319
|
+
label={t('modal.security.country', 'Country')}
|
|
316
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 ? '[WARNING] Yes' : 'No'}
|
|
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 ? '[WARNING] Yes' : 'No'}
|
|
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
|
-
Location and Security Analysis
|
|
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
|
-
Technical Details
|
|
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>
|
|
@@ -1,20 +1,24 @@
|
|
|
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
|
|
20
24
|
const userId = document?.documentId || documentId;
|
|
@@ -53,14 +57,14 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
|
|
|
53
57
|
if (response.data?.success) {
|
|
54
58
|
toggleNotification({
|
|
55
59
|
type: 'success',
|
|
56
|
-
message: 'All sessions terminated successfully',
|
|
60
|
+
message: t('notifications.success.terminatedAll', 'All sessions terminated successfully'),
|
|
57
61
|
});
|
|
58
62
|
setSessions([]);
|
|
59
63
|
}
|
|
60
64
|
} catch (error) {
|
|
61
65
|
toggleNotification({
|
|
62
66
|
type: 'warning',
|
|
63
|
-
message: 'Failed to terminate sessions',
|
|
67
|
+
message: t('notifications.error.terminateAll', 'Failed to terminate sessions'),
|
|
64
68
|
});
|
|
65
69
|
console.error('[SessionInfoPanel] Logout all error:', error);
|
|
66
70
|
} finally {
|
|
@@ -81,7 +85,9 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
|
|
|
81
85
|
|
|
82
86
|
toggleNotification({
|
|
83
87
|
type: 'success',
|
|
84
|
-
message: newBlockedStatus
|
|
88
|
+
message: newBlockedStatus
|
|
89
|
+
? t('notifications.success.blocked', 'User blocked successfully')
|
|
90
|
+
: t('notifications.success.unblocked', 'User unblocked successfully'),
|
|
85
91
|
});
|
|
86
92
|
|
|
87
93
|
if (newBlockedStatus) {
|
|
@@ -91,7 +97,7 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
|
|
|
91
97
|
} catch (error) {
|
|
92
98
|
toggleNotification({
|
|
93
99
|
type: 'warning',
|
|
94
|
-
message: 'Failed to update user status',
|
|
100
|
+
message: t('notifications.error.block', 'Failed to update user status'),
|
|
95
101
|
});
|
|
96
102
|
console.error('[SessionInfoPanel] Toggle block error:', error);
|
|
97
103
|
} finally {
|
|
@@ -112,10 +118,10 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
|
|
|
112
118
|
|
|
113
119
|
if (loading) {
|
|
114
120
|
return {
|
|
115
|
-
title: 'Session Info',
|
|
121
|
+
title: t('panel.title', 'Session Info'),
|
|
116
122
|
content: (
|
|
117
123
|
<Box padding={4} background="neutral0">
|
|
118
|
-
<Typography variant="pi" textColor="neutral600">Loading
|
|
124
|
+
<Typography variant="pi" textColor="neutral600">{t('panel.loading', 'Loading...')}</Typography>
|
|
119
125
|
</Box>
|
|
120
126
|
),
|
|
121
127
|
};
|
|
@@ -124,7 +130,7 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
|
|
|
124
130
|
const isOnline = sessions.length > 0;
|
|
125
131
|
|
|
126
132
|
return {
|
|
127
|
-
title: 'Session Info',
|
|
133
|
+
title: t('panel.title', 'Session Info'),
|
|
128
134
|
content: (
|
|
129
135
|
<Box style={{ width: '100%' }}>
|
|
130
136
|
<Flex direction="column" gap={4} alignItems="stretch">
|
|
@@ -145,10 +151,10 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
|
|
|
145
151
|
size="M"
|
|
146
152
|
style={{ fontSize: '14px', padding: '6px 12px' }}
|
|
147
153
|
>
|
|
148
|
-
{isOnline ? 'ACTIVE' : 'OFFLINE'}
|
|
154
|
+
{isOnline ? t('panel.status.active', 'ACTIVE') : t('panel.status.offline', 'OFFLINE')}
|
|
149
155
|
</Badge>
|
|
150
156
|
<Typography variant="omega" fontWeight="semiBold" textColor={isOnline ? 'success700' : 'neutral700'}>
|
|
151
|
-
{sessions.
|
|
157
|
+
{t('panel.sessions.count', '{count} active session{count, plural, one {} other {s}}', { count: sessions.length })}
|
|
152
158
|
</Typography>
|
|
153
159
|
</Flex>
|
|
154
160
|
</Box>
|
|
@@ -161,10 +167,10 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
|
|
|
161
167
|
hasRadius
|
|
162
168
|
>
|
|
163
169
|
<Typography variant="omega" fontWeight="semiBold" textColor="danger700" marginBottom={1}>
|
|
164
|
-
User is blocked
|
|
170
|
+
{t('panel.blocked.title', 'User is blocked')}
|
|
165
171
|
</Typography>
|
|
166
172
|
<Typography variant="pi" textColor="danger600">
|
|
167
|
-
Authentication disabled
|
|
173
|
+
{t('panel.blocked.description', 'Authentication disabled')}
|
|
168
174
|
</Typography>
|
|
169
175
|
</Box>
|
|
170
176
|
)}
|
|
@@ -177,7 +183,7 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
|
|
|
177
183
|
letterSpacing: '0.5px',
|
|
178
184
|
fontSize: '12px'
|
|
179
185
|
}}>
|
|
180
|
-
Active Sessions
|
|
186
|
+
{t('panel.sessions.title', 'Active Sessions')}
|
|
181
187
|
</Typography>
|
|
182
188
|
|
|
183
189
|
{sessions.slice(0, 5).map((session) => {
|
|
@@ -219,7 +225,7 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
|
|
|
219
225
|
textColor="neutral0"
|
|
220
226
|
size="S"
|
|
221
227
|
>
|
|
222
|
-
Active
|
|
228
|
+
{t('panel.sessions.active', 'Active')}
|
|
223
229
|
</Badge>
|
|
224
230
|
|
|
225
231
|
{/* Browser & OS */}
|
|
@@ -253,7 +259,10 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
|
|
|
253
259
|
{/* Show minutes since last activity */}
|
|
254
260
|
{session.minutesSinceActive !== undefined && session.minutesSinceActive < 60 && (
|
|
255
261
|
<Typography variant="pi" textColor="success600" fontWeight="semiBold">
|
|
256
|
-
|
|
262
|
+
{session.minutesSinceActive === 0
|
|
263
|
+
? t('panel.sessions.activeNow', 'Active now')
|
|
264
|
+
: t('panel.sessions.activeAgo', 'Active {minutes} min ago', { minutes: session.minutesSinceActive })
|
|
265
|
+
}
|
|
257
266
|
</Typography>
|
|
258
267
|
)}
|
|
259
268
|
</Flex>
|
|
@@ -264,7 +273,7 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
|
|
|
264
273
|
{sessions.length > 5 && (
|
|
265
274
|
<Box padding={3} background="primary100" hasRadius textAlign="center">
|
|
266
275
|
<Typography variant="pi" textColor="primary600" fontWeight="semiBold">
|
|
267
|
-
|
|
276
|
+
{t('panel.sessions.more', '+{count} more session{count, plural, one {} other {s}}', { count: sessions.length - 5 })}
|
|
268
277
|
</Typography>
|
|
269
278
|
</Box>
|
|
270
279
|
)}
|
|
@@ -288,10 +297,10 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
|
|
|
288
297
|
💤
|
|
289
298
|
</Typography>
|
|
290
299
|
<Typography variant="omega" fontWeight="semiBold" textColor="neutral700">
|
|
291
|
-
No active sessions
|
|
300
|
+
{t('panel.empty.title', 'No active sessions')}
|
|
292
301
|
</Typography>
|
|
293
302
|
<Typography variant="pi" textColor="neutral500" style={{ fontSize: '13px' }}>
|
|
294
|
-
User has not logged in yet
|
|
303
|
+
{t('panel.empty.description', 'User has not logged in yet')}
|
|
295
304
|
</Typography>
|
|
296
305
|
</Flex>
|
|
297
306
|
</Box>
|
|
@@ -306,7 +315,7 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
|
|
|
306
315
|
letterSpacing: '0.5px',
|
|
307
316
|
fontSize: '12px'
|
|
308
317
|
}}>
|
|
309
|
-
Actions
|
|
318
|
+
{t('panel.actions.title', 'Actions')}
|
|
310
319
|
</Typography>
|
|
311
320
|
|
|
312
321
|
<Button
|
|
@@ -335,7 +344,7 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
|
|
|
335
344
|
}
|
|
336
345
|
}}
|
|
337
346
|
>
|
|
338
|
-
Terminate All Sessions
|
|
347
|
+
{t('panel.actions.terminateAll', 'Terminate All Sessions')}
|
|
339
348
|
</Button>
|
|
340
349
|
|
|
341
350
|
<Button
|
|
@@ -364,7 +373,7 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
|
|
|
364
373
|
}
|
|
365
374
|
}}
|
|
366
375
|
>
|
|
367
|
-
{isBlocked ? 'Unblock User' : 'Block User'}
|
|
376
|
+
{isBlocked ? t('panel.actions.unblockUser', 'Unblock User') : t('panel.actions.blockUser', 'Block User')}
|
|
368
377
|
</Button>
|
|
369
378
|
</Flex>
|
|
370
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`,
|