strapi-plugin-magic-sessionmanager 4.2.4 → 4.2.6
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 +0 -2
- 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,151 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react';
|
|
2
|
-
import { Box, Typography, Flex, Badge } from '@strapi/design-system';
|
|
3
|
-
import { useFetchClient } from '@strapi/strapi/admin';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Session Info Card - shows user's session status and history
|
|
7
|
-
* Injected into Content Manager edit view for Users
|
|
8
|
-
*/
|
|
9
|
-
const SessionInfoCard = ({ id, model }) => {
|
|
10
|
-
const { get } = useFetchClient();
|
|
11
|
-
const [sessions, setSessions] = useState([]);
|
|
12
|
-
const [loading, setLoading] = useState(true);
|
|
13
|
-
const [isUserModel, setIsUserModel] = useState(false);
|
|
14
|
-
|
|
15
|
-
useEffect(() => {
|
|
16
|
-
// Only show for User content type
|
|
17
|
-
if (model !== 'plugin::users-permissions.user') {
|
|
18
|
-
setIsUserModel(false);
|
|
19
|
-
setLoading(false);
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
setIsUserModel(true);
|
|
24
|
-
|
|
25
|
-
// Fetch user's sessions
|
|
26
|
-
const fetchSessions = async () => {
|
|
27
|
-
if (!id) {
|
|
28
|
-
setLoading(false);
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
try {
|
|
33
|
-
const { data } = await get(`/magic-sessionmanager/user/${id}/sessions`);
|
|
34
|
-
setSessions(data.data || []);
|
|
35
|
-
} catch (err) {
|
|
36
|
-
console.error('[SessionInfoCard] Error fetching sessions:', err);
|
|
37
|
-
} finally {
|
|
38
|
-
setLoading(false);
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
fetchSessions();
|
|
43
|
-
}, [id, model, get]);
|
|
44
|
-
|
|
45
|
-
// Don't render anything if not a User model
|
|
46
|
-
if (!isUserModel) {
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (loading) {
|
|
51
|
-
return (
|
|
52
|
-
<Box padding={4} background="neutral100" borderRadius="4px">
|
|
53
|
-
<Typography variant="sigma" textColor="neutral600">
|
|
54
|
-
Loading sessions...
|
|
55
|
-
</Typography>
|
|
56
|
-
</Box>
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const activeSessions = sessions.filter(s => s.isActive);
|
|
61
|
-
const isOnline = activeSessions.length > 0;
|
|
62
|
-
|
|
63
|
-
return (
|
|
64
|
-
<Box
|
|
65
|
-
padding={4}
|
|
66
|
-
background="neutral0"
|
|
67
|
-
shadow="tableShadow"
|
|
68
|
-
borderRadius="4px"
|
|
69
|
-
marginBottom={4}
|
|
70
|
-
>
|
|
71
|
-
<Flex direction="column" gap={3}>
|
|
72
|
-
{/* Header */}
|
|
73
|
-
<Flex justifyContent="space-between" alignItems="center">
|
|
74
|
-
<Typography variant="sigma" textColor="neutral600" textTransform="uppercase">
|
|
75
|
-
Session Status
|
|
76
|
-
</Typography>
|
|
77
|
-
{isOnline ? (
|
|
78
|
-
<Badge active backgroundColor="success500">
|
|
79
|
-
Online
|
|
80
|
-
</Badge>
|
|
81
|
-
) : (
|
|
82
|
-
<Badge backgroundColor="neutral150">
|
|
83
|
-
Offline
|
|
84
|
-
</Badge>
|
|
85
|
-
)}
|
|
86
|
-
</Flex>
|
|
87
|
-
|
|
88
|
-
{/* Stats */}
|
|
89
|
-
<Box>
|
|
90
|
-
<Typography variant="omega" fontWeight="bold">
|
|
91
|
-
Active Sessions: {activeSessions.length}
|
|
92
|
-
</Typography>
|
|
93
|
-
<Typography variant="pi" textColor="neutral600">
|
|
94
|
-
Total Sessions: {sessions.length}
|
|
95
|
-
</Typography>
|
|
96
|
-
</Box>
|
|
97
|
-
|
|
98
|
-
{/* Active Sessions List */}
|
|
99
|
-
{activeSessions.length > 0 && (
|
|
100
|
-
<Box>
|
|
101
|
-
<Typography variant="pi" fontWeight="bold" marginBottom={2}>
|
|
102
|
-
Current Sessions:
|
|
103
|
-
</Typography>
|
|
104
|
-
{activeSessions.slice(0, 3).map((session) => (
|
|
105
|
-
<Box
|
|
106
|
-
key={session.id}
|
|
107
|
-
padding={2}
|
|
108
|
-
background="neutral100"
|
|
109
|
-
borderRadius="4px"
|
|
110
|
-
marginBottom={2}
|
|
111
|
-
>
|
|
112
|
-
<Typography variant="pi" textColor="neutral800">
|
|
113
|
-
IP: {session.ipAddress}
|
|
114
|
-
</Typography>
|
|
115
|
-
<Typography variant="pi" textColor="neutral600" fontSize="11px">
|
|
116
|
-
{new Date(session.loginTime).toLocaleString()}
|
|
117
|
-
</Typography>
|
|
118
|
-
</Box>
|
|
119
|
-
))}
|
|
120
|
-
{activeSessions.length > 3 && (
|
|
121
|
-
<Typography variant="pi" textColor="neutral600">
|
|
122
|
-
+ {activeSessions.length - 3} more...
|
|
123
|
-
</Typography>
|
|
124
|
-
)}
|
|
125
|
-
</Box>
|
|
126
|
-
)}
|
|
127
|
-
|
|
128
|
-
{/* Last Activity */}
|
|
129
|
-
{sessions.length > 0 && (
|
|
130
|
-
<Box>
|
|
131
|
-
<Typography variant="pi" textColor="neutral600">
|
|
132
|
-
Last Activity: {new Date(sessions[0].lastActive).toLocaleString()}
|
|
133
|
-
</Typography>
|
|
134
|
-
</Box>
|
|
135
|
-
)}
|
|
136
|
-
|
|
137
|
-
{/* No sessions message */}
|
|
138
|
-
{sessions.length === 0 && (
|
|
139
|
-
<Box padding={2} background="neutral100" borderRadius="4px">
|
|
140
|
-
<Typography variant="pi" textColor="neutral600">
|
|
141
|
-
No session data available
|
|
142
|
-
</Typography>
|
|
143
|
-
</Box>
|
|
144
|
-
)}
|
|
145
|
-
</Flex>
|
|
146
|
-
</Box>
|
|
147
|
-
);
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
export default SessionInfoCard;
|
|
151
|
-
|
|
@@ -1,385 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react';
|
|
2
|
-
import { useIntl } from 'react-intl';
|
|
3
|
-
import { Box, Typography, Flex, Button, Badge, Divider } from '@strapi/design-system';
|
|
4
|
-
import { Check, Cross, Monitor, Phone, Server, Clock } from '@strapi/icons';
|
|
5
|
-
import { useFetchClient, useNotification } from '@strapi/strapi/admin';
|
|
6
|
-
import parseUserAgent from '../utils/parseUserAgent';
|
|
7
|
-
import { getTranslation } from '../utils/getTranslation';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Session Info Panel - Native Strapi design
|
|
11
|
-
* Clean, professional sidebar panel for Content Manager
|
|
12
|
-
*/
|
|
13
|
-
const SessionInfoPanel = ({ documentId, model, document }) => {
|
|
14
|
-
const { formatMessage } = useIntl();
|
|
15
|
-
const [sessions, setSessions] = useState([]);
|
|
16
|
-
const [loading, setLoading] = useState(true);
|
|
17
|
-
const [isBlocked, setIsBlocked] = useState(false);
|
|
18
|
-
const [actionLoading, setActionLoading] = useState(false);
|
|
19
|
-
const { get, post: postRequest } = useFetchClient();
|
|
20
|
-
const { toggleNotification } = useNotification();
|
|
21
|
-
const t = (id, defaultMessage, values) => formatMessage({ id: getTranslation(id), defaultMessage }, values);
|
|
22
|
-
|
|
23
|
-
// Strapi v5: Use documentId (string UUID) instead of numeric id
|
|
24
|
-
const userId = document?.documentId || documentId;
|
|
25
|
-
|
|
26
|
-
useEffect(() => {
|
|
27
|
-
if (model !== 'plugin::users-permissions.user' || !userId) {
|
|
28
|
-
setLoading(false);
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const fetchData = async () => {
|
|
33
|
-
try {
|
|
34
|
-
const { data } = await get(`/magic-sessionmanager/user/${userId}/sessions`);
|
|
35
|
-
// Filter by truly active (not just isActive, but also within timeout)
|
|
36
|
-
const activeSessions = (data.data || []).filter(s => s.isTrulyActive);
|
|
37
|
-
setSessions(activeSessions);
|
|
38
|
-
|
|
39
|
-
setIsBlocked(document?.blocked || false);
|
|
40
|
-
} catch (err) {
|
|
41
|
-
console.error('[SessionInfoPanel] Error:', err);
|
|
42
|
-
} finally {
|
|
43
|
-
setLoading(false);
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
fetchData();
|
|
48
|
-
}, [userId, model, document, get]);
|
|
49
|
-
|
|
50
|
-
const handleLogoutAll = async () => {
|
|
51
|
-
if (!userId) return;
|
|
52
|
-
|
|
53
|
-
setActionLoading(true);
|
|
54
|
-
try {
|
|
55
|
-
const response = await postRequest(`/magic-sessionmanager/user/${userId}/terminate-all`);
|
|
56
|
-
|
|
57
|
-
if (response.data?.success) {
|
|
58
|
-
toggleNotification({
|
|
59
|
-
type: 'success',
|
|
60
|
-
message: t('notifications.success.terminatedAll', 'All sessions terminated successfully'),
|
|
61
|
-
});
|
|
62
|
-
setSessions([]);
|
|
63
|
-
}
|
|
64
|
-
} catch (error) {
|
|
65
|
-
toggleNotification({
|
|
66
|
-
type: 'warning',
|
|
67
|
-
message: t('notifications.error.terminateAll', 'Failed to terminate sessions'),
|
|
68
|
-
});
|
|
69
|
-
console.error('[SessionInfoPanel] Logout all error:', error);
|
|
70
|
-
} finally {
|
|
71
|
-
setActionLoading(false);
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
const handleToggleBlock = async () => {
|
|
76
|
-
if (!userId) return;
|
|
77
|
-
|
|
78
|
-
setActionLoading(true);
|
|
79
|
-
try {
|
|
80
|
-
const response = await postRequest(`/magic-sessionmanager/user/${userId}/toggle-block`);
|
|
81
|
-
|
|
82
|
-
if (response.data?.success) {
|
|
83
|
-
const newBlockedStatus = response.data.blocked;
|
|
84
|
-
setIsBlocked(newBlockedStatus);
|
|
85
|
-
|
|
86
|
-
toggleNotification({
|
|
87
|
-
type: 'success',
|
|
88
|
-
message: newBlockedStatus
|
|
89
|
-
? t('notifications.success.blocked', 'User blocked successfully')
|
|
90
|
-
: t('notifications.success.unblocked', 'User unblocked successfully'),
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
if (newBlockedStatus) {
|
|
94
|
-
setSessions([]);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
} catch (error) {
|
|
98
|
-
toggleNotification({
|
|
99
|
-
type: 'warning',
|
|
100
|
-
message: t('notifications.error.block', 'Failed to update user status'),
|
|
101
|
-
});
|
|
102
|
-
console.error('[SessionInfoPanel] Toggle block error:', error);
|
|
103
|
-
} finally {
|
|
104
|
-
setActionLoading(false);
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
const getDeviceIcon = (deviceType) => {
|
|
109
|
-
if (deviceType === 'Mobile' || deviceType === 'Tablet') return Phone;
|
|
110
|
-
if (deviceType === 'Desktop' || deviceType === 'Laptop') return Monitor;
|
|
111
|
-
return Server;
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
// ONLY show for User content type - hide completely for others
|
|
115
|
-
if (model !== 'plugin::users-permissions.user') {
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (loading) {
|
|
120
|
-
return {
|
|
121
|
-
title: t('panel.title', 'Session Info'),
|
|
122
|
-
content: (
|
|
123
|
-
<Box padding={4} background="neutral0">
|
|
124
|
-
<Typography variant="pi" textColor="neutral600">{t('panel.loading', 'Loading...')}</Typography>
|
|
125
|
-
</Box>
|
|
126
|
-
),
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const isOnline = sessions.length > 0;
|
|
131
|
-
|
|
132
|
-
return {
|
|
133
|
-
title: t('panel.title', 'Session Info'),
|
|
134
|
-
content: (
|
|
135
|
-
<Box style={{ width: '100%' }}>
|
|
136
|
-
<Flex direction="column" gap={4} alignItems="stretch">
|
|
137
|
-
{/* Status Bar */}
|
|
138
|
-
<Box
|
|
139
|
-
padding={5}
|
|
140
|
-
background={isOnline ? 'success100' : 'neutral150'}
|
|
141
|
-
hasRadius
|
|
142
|
-
style={{
|
|
143
|
-
border: isOnline ? '1px solid #c6f6d5' : '1px solid #eaeaef',
|
|
144
|
-
transition: 'all 0.2s ease'
|
|
145
|
-
}}
|
|
146
|
-
>
|
|
147
|
-
<Flex direction="column" gap={3} alignItems="center">
|
|
148
|
-
<Badge
|
|
149
|
-
backgroundColor={isOnline ? 'success600' : 'neutral600'}
|
|
150
|
-
textColor="neutral0"
|
|
151
|
-
size="M"
|
|
152
|
-
style={{ fontSize: '14px', padding: '6px 12px' }}
|
|
153
|
-
>
|
|
154
|
-
{isOnline ? t('panel.status.active', 'ACTIVE') : t('panel.status.offline', 'OFFLINE')}
|
|
155
|
-
</Badge>
|
|
156
|
-
<Typography variant="omega" fontWeight="semiBold" textColor={isOnline ? 'success700' : 'neutral700'}>
|
|
157
|
-
{t('panel.sessions.count', '{count} active session{count, plural, one {} other {s}}', { count: sessions.length })}
|
|
158
|
-
</Typography>
|
|
159
|
-
</Flex>
|
|
160
|
-
</Box>
|
|
161
|
-
|
|
162
|
-
{/* User Blocked Warning */}
|
|
163
|
-
{isBlocked && (
|
|
164
|
-
<Box
|
|
165
|
-
padding={4}
|
|
166
|
-
background="danger100"
|
|
167
|
-
hasRadius
|
|
168
|
-
>
|
|
169
|
-
<Typography variant="omega" fontWeight="semiBold" textColor="danger700" marginBottom={1}>
|
|
170
|
-
{t('panel.blocked.title', 'User is blocked')}
|
|
171
|
-
</Typography>
|
|
172
|
-
<Typography variant="pi" textColor="danger600">
|
|
173
|
-
{t('panel.blocked.description', 'Authentication disabled')}
|
|
174
|
-
</Typography>
|
|
175
|
-
</Box>
|
|
176
|
-
)}
|
|
177
|
-
|
|
178
|
-
{/* Active Sessions List */}
|
|
179
|
-
{sessions.length > 0 ? (
|
|
180
|
-
<Flex direction="column" gap={3} alignItems="stretch">
|
|
181
|
-
<Typography variant="sigma" textColor="neutral600" textTransform="uppercase" style={{
|
|
182
|
-
textAlign: 'left',
|
|
183
|
-
letterSpacing: '0.5px',
|
|
184
|
-
fontSize: '12px'
|
|
185
|
-
}}>
|
|
186
|
-
{t('panel.sessions.title', 'Active Sessions')}
|
|
187
|
-
</Typography>
|
|
188
|
-
|
|
189
|
-
{sessions.slice(0, 5).map((session) => {
|
|
190
|
-
const deviceInfo = parseUserAgent(session.userAgent);
|
|
191
|
-
const DeviceIcon = getDeviceIcon(deviceInfo.device);
|
|
192
|
-
|
|
193
|
-
return (
|
|
194
|
-
<Box
|
|
195
|
-
key={session.id}
|
|
196
|
-
padding={4}
|
|
197
|
-
background="neutral0"
|
|
198
|
-
hasRadius
|
|
199
|
-
style={{
|
|
200
|
-
border: '1px solid #e3e8ef',
|
|
201
|
-
boxShadow: '0 1px 3px rgba(0, 0, 0, 0.04)',
|
|
202
|
-
transition: 'all 0.2s ease'
|
|
203
|
-
}}
|
|
204
|
-
onMouseEnter={(e) => {
|
|
205
|
-
e.currentTarget.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.08)';
|
|
206
|
-
e.currentTarget.style.borderColor = '#4945FF';
|
|
207
|
-
}}
|
|
208
|
-
onMouseLeave={(e) => {
|
|
209
|
-
e.currentTarget.style.boxShadow = '0 1px 3px rgba(0, 0, 0, 0.04)';
|
|
210
|
-
e.currentTarget.style.borderColor = '#e3e8ef';
|
|
211
|
-
}}
|
|
212
|
-
>
|
|
213
|
-
<Flex direction="column" gap={2} alignItems="flex-start">
|
|
214
|
-
{/* Device Name with Icon */}
|
|
215
|
-
<Flex gap={2} alignItems="center">
|
|
216
|
-
<DeviceIcon width="20px" height="20px" />
|
|
217
|
-
<Typography variant="omega" fontWeight="bold" textColor="neutral800">
|
|
218
|
-
{deviceInfo.device}
|
|
219
|
-
</Typography>
|
|
220
|
-
</Flex>
|
|
221
|
-
|
|
222
|
-
{/* Status Badge */}
|
|
223
|
-
<Badge
|
|
224
|
-
backgroundColor="success600"
|
|
225
|
-
textColor="neutral0"
|
|
226
|
-
size="S"
|
|
227
|
-
>
|
|
228
|
-
{t('panel.sessions.active', 'Active')}
|
|
229
|
-
</Badge>
|
|
230
|
-
|
|
231
|
-
{/* Browser & OS */}
|
|
232
|
-
<Typography variant="pi" textColor="neutral600">
|
|
233
|
-
{deviceInfo.browser} on {deviceInfo.os}
|
|
234
|
-
</Typography>
|
|
235
|
-
|
|
236
|
-
<Divider />
|
|
237
|
-
|
|
238
|
-
{/* IP Address */}
|
|
239
|
-
<Flex gap={2} alignItems="center">
|
|
240
|
-
<Server width="14px" height="14px" />
|
|
241
|
-
<Typography variant="pi" textColor="neutral600">
|
|
242
|
-
{session.ipAddress}
|
|
243
|
-
</Typography>
|
|
244
|
-
</Flex>
|
|
245
|
-
|
|
246
|
-
{/* Login Time */}
|
|
247
|
-
<Flex gap={2} alignItems="center">
|
|
248
|
-
<Clock width="14px" height="14px" />
|
|
249
|
-
<Typography variant="pi" textColor="neutral600">
|
|
250
|
-
{new Date(session.loginTime).toLocaleString('en-US', {
|
|
251
|
-
month: 'short',
|
|
252
|
-
day: 'numeric',
|
|
253
|
-
hour: '2-digit',
|
|
254
|
-
minute: '2-digit'
|
|
255
|
-
})}
|
|
256
|
-
</Typography>
|
|
257
|
-
</Flex>
|
|
258
|
-
|
|
259
|
-
{/* Show minutes since last activity */}
|
|
260
|
-
{session.minutesSinceActive !== undefined && session.minutesSinceActive < 60 && (
|
|
261
|
-
<Typography variant="pi" textColor="success600" fontWeight="semiBold">
|
|
262
|
-
{session.minutesSinceActive === 0
|
|
263
|
-
? t('panel.sessions.activeNow', 'Active now')
|
|
264
|
-
: t('panel.sessions.activeAgo', 'Active {minutes} min ago', { minutes: session.minutesSinceActive })
|
|
265
|
-
}
|
|
266
|
-
</Typography>
|
|
267
|
-
)}
|
|
268
|
-
</Flex>
|
|
269
|
-
</Box>
|
|
270
|
-
);
|
|
271
|
-
})}
|
|
272
|
-
|
|
273
|
-
{sessions.length > 5 && (
|
|
274
|
-
<Box padding={3} background="primary100" hasRadius textAlign="center">
|
|
275
|
-
<Typography variant="pi" textColor="primary600" fontWeight="semiBold">
|
|
276
|
-
{t('panel.sessions.more', '+{count} more session{count, plural, one {} other {s}}', { count: sessions.length - 5 })}
|
|
277
|
-
</Typography>
|
|
278
|
-
</Box>
|
|
279
|
-
)}
|
|
280
|
-
</Flex>
|
|
281
|
-
) : (
|
|
282
|
-
<Box
|
|
283
|
-
padding={6}
|
|
284
|
-
background="neutral100"
|
|
285
|
-
hasRadius
|
|
286
|
-
style={{
|
|
287
|
-
border: '1px dashed #dcdce4',
|
|
288
|
-
textAlign: 'center'
|
|
289
|
-
}}
|
|
290
|
-
>
|
|
291
|
-
<Flex direction="column" alignItems="center" gap={2}>
|
|
292
|
-
<Typography
|
|
293
|
-
variant="pi"
|
|
294
|
-
textColor="neutral600"
|
|
295
|
-
style={{ fontSize: '32px', marginBottom: '8px' }}
|
|
296
|
-
>
|
|
297
|
-
💤
|
|
298
|
-
</Typography>
|
|
299
|
-
<Typography variant="omega" fontWeight="semiBold" textColor="neutral700">
|
|
300
|
-
{t('panel.empty.title', 'No active sessions')}
|
|
301
|
-
</Typography>
|
|
302
|
-
<Typography variant="pi" textColor="neutral500" style={{ fontSize: '13px' }}>
|
|
303
|
-
{t('panel.empty.description', 'User has not logged in yet')}
|
|
304
|
-
</Typography>
|
|
305
|
-
</Flex>
|
|
306
|
-
</Box>
|
|
307
|
-
)}
|
|
308
|
-
|
|
309
|
-
{/* Action Buttons - Always at the bottom */}
|
|
310
|
-
<Divider />
|
|
311
|
-
|
|
312
|
-
<Flex direction="column" gap={3} alignItems="stretch">
|
|
313
|
-
<Typography variant="sigma" textColor="neutral600" textTransform="uppercase" style={{
|
|
314
|
-
textAlign: 'left',
|
|
315
|
-
letterSpacing: '0.5px',
|
|
316
|
-
fontSize: '12px'
|
|
317
|
-
}}>
|
|
318
|
-
{t('panel.actions.title', 'Actions')}
|
|
319
|
-
</Typography>
|
|
320
|
-
|
|
321
|
-
<Button
|
|
322
|
-
variant="secondary"
|
|
323
|
-
size="M"
|
|
324
|
-
fullWidth
|
|
325
|
-
onClick={handleLogoutAll}
|
|
326
|
-
disabled={actionLoading || sessions.length === 0}
|
|
327
|
-
startIcon={<Cross />}
|
|
328
|
-
style={{
|
|
329
|
-
border: '1px solid #dc2626',
|
|
330
|
-
color: '#dc2626',
|
|
331
|
-
backgroundColor: 'transparent',
|
|
332
|
-
transition: 'all 0.2s ease',
|
|
333
|
-
}}
|
|
334
|
-
onMouseEnter={(e) => {
|
|
335
|
-
if (!actionLoading && sessions.length > 0) {
|
|
336
|
-
e.currentTarget.style.backgroundColor = '#dc2626';
|
|
337
|
-
e.currentTarget.style.color = 'white';
|
|
338
|
-
}
|
|
339
|
-
}}
|
|
340
|
-
onMouseLeave={(e) => {
|
|
341
|
-
if (!actionLoading && sessions.length > 0) {
|
|
342
|
-
e.currentTarget.style.backgroundColor = 'transparent';
|
|
343
|
-
e.currentTarget.style.color = '#dc2626';
|
|
344
|
-
}
|
|
345
|
-
}}
|
|
346
|
-
>
|
|
347
|
-
{t('panel.actions.terminateAll', 'Terminate All Sessions')}
|
|
348
|
-
</Button>
|
|
349
|
-
|
|
350
|
-
<Button
|
|
351
|
-
variant="secondary"
|
|
352
|
-
size="M"
|
|
353
|
-
fullWidth
|
|
354
|
-
onClick={handleToggleBlock}
|
|
355
|
-
disabled={actionLoading}
|
|
356
|
-
startIcon={isBlocked ? <Check /> : <Cross />}
|
|
357
|
-
style={{
|
|
358
|
-
border: isBlocked ? '1px solid #16a34a' : '1px solid #dc2626',
|
|
359
|
-
color: isBlocked ? '#16a34a' : '#dc2626',
|
|
360
|
-
backgroundColor: 'transparent',
|
|
361
|
-
transition: 'all 0.2s ease',
|
|
362
|
-
}}
|
|
363
|
-
onMouseEnter={(e) => {
|
|
364
|
-
if (!actionLoading) {
|
|
365
|
-
e.currentTarget.style.backgroundColor = isBlocked ? '#16a34a' : '#dc2626';
|
|
366
|
-
e.currentTarget.style.color = 'white';
|
|
367
|
-
}
|
|
368
|
-
}}
|
|
369
|
-
onMouseLeave={(e) => {
|
|
370
|
-
if (!actionLoading) {
|
|
371
|
-
e.currentTarget.style.backgroundColor = 'transparent';
|
|
372
|
-
e.currentTarget.style.color = isBlocked ? '#16a34a' : '#dc2626';
|
|
373
|
-
}
|
|
374
|
-
}}
|
|
375
|
-
>
|
|
376
|
-
{isBlocked ? t('panel.actions.unblockUser', 'Unblock User') : t('panel.actions.blockUser', 'Block User')}
|
|
377
|
-
</Button>
|
|
378
|
-
</Flex>
|
|
379
|
-
</Flex>
|
|
380
|
-
</Box>
|
|
381
|
-
),
|
|
382
|
-
};
|
|
383
|
-
};
|
|
384
|
-
|
|
385
|
-
export default SessionInfoPanel;
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export { default as Initializer } from './Initializer';
|
|
2
|
-
export { default as PluginIcon } from './PluginIcon';
|
|
3
|
-
export { default as SessionInfoCard } from './SessionInfoCard';
|
|
4
|
-
export { default as SessionInfoPanel } from './SessionInfoPanel';
|
|
5
|
-
export { default as OnlineUsersWidget } from './OnlineUsersWidget';
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react';
|
|
2
|
-
import { useFetchClient } from '@strapi/strapi/admin';
|
|
3
|
-
import pluginId from '../pluginId';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Hook to check license status
|
|
7
|
-
* Returns: { isPremium, loading, error }
|
|
8
|
-
*/
|
|
9
|
-
export const useLicense = () => {
|
|
10
|
-
const { get } = useFetchClient();
|
|
11
|
-
const [isPremium, setIsPremium] = useState(false);
|
|
12
|
-
const [isAdvanced, setIsAdvanced] = useState(false);
|
|
13
|
-
const [isEnterprise, setIsEnterprise] = useState(false);
|
|
14
|
-
const [loading, setLoading] = useState(true);
|
|
15
|
-
const [error, setError] = useState(null);
|
|
16
|
-
const [licenseData, setLicenseData] = useState(null);
|
|
17
|
-
|
|
18
|
-
useEffect(() => {
|
|
19
|
-
checkLicense();
|
|
20
|
-
|
|
21
|
-
// Auto-refresh every 1 hour to detect license changes (silent background check)
|
|
22
|
-
const interval = setInterval(() => {
|
|
23
|
-
checkLicense(true); // Silent refresh - user merkt nichts
|
|
24
|
-
}, 60 * 60 * 1000); // 1 hour
|
|
25
|
-
|
|
26
|
-
return () => clearInterval(interval);
|
|
27
|
-
}, []);
|
|
28
|
-
|
|
29
|
-
const checkLicense = async (silent = false) => {
|
|
30
|
-
if (!silent) {
|
|
31
|
-
setLoading(true);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
const response = await get(`/${pluginId}/license/status`);
|
|
36
|
-
|
|
37
|
-
// Debug logging with plugin name
|
|
38
|
-
if (!silent) {
|
|
39
|
-
console.log('[magic-sessionmanager/useLicense] Full API Response:', response.data);
|
|
40
|
-
console.log('[magic-sessionmanager/useLicense] License Details:', {
|
|
41
|
-
valid: response.data?.valid,
|
|
42
|
-
demo: response.data?.demo,
|
|
43
|
-
licenseKey: response.data?.data?.licenseKey?.substring(0, 13) + '...',
|
|
44
|
-
email: response.data?.data?.email,
|
|
45
|
-
features: response.data?.data?.features,
|
|
46
|
-
rawFeaturePremium: response.data?.data?.features?.premium,
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Check if license is valid AND has premium feature enabled
|
|
51
|
-
const isValid = response.data?.valid || false;
|
|
52
|
-
const hasPremiumFeature = response.data?.data?.features?.premium || false;
|
|
53
|
-
const hasAdvancedFeature = response.data?.data?.features?.advanced || false;
|
|
54
|
-
const hasEnterpriseFeature = response.data?.data?.features?.enterprise || false;
|
|
55
|
-
const newIsPremium = isValid && hasPremiumFeature;
|
|
56
|
-
const newIsAdvanced = isValid && hasAdvancedFeature;
|
|
57
|
-
const newIsEnterprise = isValid && hasEnterpriseFeature;
|
|
58
|
-
|
|
59
|
-
// Log with plugin name
|
|
60
|
-
if ((newIsPremium !== isPremium || !silent) && !silent) {
|
|
61
|
-
console.log(`[magic-sessionmanager/useLicense] Premium Status: ${newIsPremium} (valid: ${isValid}, featurePremium: ${hasPremiumFeature})`);
|
|
62
|
-
if (!newIsPremium && isValid) {
|
|
63
|
-
console.warn('[magic-sessionmanager/useLicense] [WARN] License is valid but Premium feature is not enabled!');
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
setIsPremium(newIsPremium);
|
|
68
|
-
setIsAdvanced(newIsAdvanced);
|
|
69
|
-
setIsEnterprise(newIsEnterprise);
|
|
70
|
-
setLicenseData(response.data?.data || null);
|
|
71
|
-
setError(null);
|
|
72
|
-
} catch (err) {
|
|
73
|
-
if (!silent) {
|
|
74
|
-
console.error('[useLicense] Error checking license:', err);
|
|
75
|
-
}
|
|
76
|
-
setIsPremium(false);
|
|
77
|
-
setIsAdvanced(false);
|
|
78
|
-
setIsEnterprise(false);
|
|
79
|
-
setLicenseData(null);
|
|
80
|
-
setError(err);
|
|
81
|
-
} finally {
|
|
82
|
-
if (!silent) {
|
|
83
|
-
setLoading(false);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
return {
|
|
89
|
-
isPremium,
|
|
90
|
-
isAdvanced,
|
|
91
|
-
isEnterprise,
|
|
92
|
-
loading,
|
|
93
|
-
error,
|
|
94
|
-
licenseData,
|
|
95
|
-
features: {
|
|
96
|
-
premium: isPremium,
|
|
97
|
-
advanced: isAdvanced,
|
|
98
|
-
enterprise: isEnterprise,
|
|
99
|
-
},
|
|
100
|
-
refetch: checkLicense
|
|
101
|
-
};
|
|
102
|
-
};
|
|
103
|
-
|