@strapi-community/plugin-io 5.2.0 → 5.3.1
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 +23 -21
- package/dist/_chunks/{LivePresencePanel-2U3I3yL0.js → LivePresencePanel-BkeWL4kq.js} +1 -1
- package/dist/_chunks/{LivePresencePanel-CIFG_05s.mjs → LivePresencePanel-D_vzQr4B.mjs} +1 -1
- package/dist/_chunks/{MonitoringPage-9f4Gzd2X.js → MonitoringPage-CYGqkzva.js} +1 -1
- package/dist/_chunks/{MonitoringPage-Bbkoh6ih.mjs → MonitoringPage-DKfhYUgU.mjs} +1 -1
- package/dist/_chunks/OnlineEditorsWidget-Bf8hfVha.js +341 -0
- package/dist/_chunks/OnlineEditorsWidget-RcYLxQke.mjs +339 -0
- package/dist/_chunks/{SettingsPage-CsRazf0j.js → SettingsPage-0k9qPAJZ.js} +1 -1
- package/dist/_chunks/{SettingsPage-Btz_5MuC.mjs → SettingsPage-Qi0iMaWc.mjs} +1 -1
- package/dist/_chunks/{index-Dof_eA3e.mjs → index-Bw7WjN5H.mjs} +17 -4
- package/dist/_chunks/{index-BEZDDgvZ.js → index-DVNfszio.js} +17 -4
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/server/index.js +137 -7
- package/dist/server/index.mjs +137 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -898,13 +898,17 @@ Authorization: Bearer <admin-jwt>
|
|
|
898
898
|
|
|
899
899
|
The plugin provides a full admin interface for configuration and monitoring.
|
|
900
900
|
|
|
901
|
-
### Dashboard
|
|
901
|
+
### Dashboard Widgets
|
|
902
902
|
|
|
903
903
|
> **Requires Strapi v5.13+**
|
|
904
904
|
|
|
905
|
-
After installation,
|
|
905
|
+
After installation, live statistics widgets appear on your Strapi admin homepage:
|
|
906
906
|
|
|
907
|
-
|
|
907
|
+
#### Socket.IO Statistics Widget
|
|
908
|
+
|
|
909
|
+

|
|
910
|
+
|
|
911
|
+
**Shows:**
|
|
908
912
|
- Live connection status (pulsing indicator when active)
|
|
909
913
|
- Active connections count
|
|
910
914
|
- Active rooms count
|
|
@@ -913,12 +917,24 @@ After installation, a live statistics widget appears on your Strapi admin homepa
|
|
|
913
917
|
|
|
914
918
|
Updates automatically every 5 seconds.
|
|
915
919
|
|
|
920
|
+
#### Who's Online Widget
|
|
921
|
+
|
|
922
|
+

|
|
923
|
+
|
|
924
|
+
**Shows:**
|
|
925
|
+
- List of currently online admin users
|
|
926
|
+
- User avatars with role badges
|
|
927
|
+
- Online status and last activity
|
|
928
|
+
- Quick access to view all activity
|
|
929
|
+
|
|
916
930
|
### Settings Page
|
|
917
931
|
|
|
918
932
|
Navigate to **Settings > Socket.IO > Settings** for visual configuration:
|
|
919
933
|
|
|
920
934
|
**Path:** `/admin/settings/io/settings`
|
|
921
935
|
|
|
936
|
+

|
|
937
|
+
|
|
922
938
|
#### General Settings
|
|
923
939
|
- Enable/disable the plugin
|
|
924
940
|
- Configure CORS origins
|
|
@@ -956,6 +972,8 @@ Navigate to **Settings > Socket.IO > Monitoring** for live statistics:
|
|
|
956
972
|
|
|
957
973
|
**Path:** `/admin/settings/io/monitoring`
|
|
958
974
|
|
|
975
|
+

|
|
976
|
+
|
|
959
977
|
- View active connections with user details
|
|
960
978
|
- See event logs in real-time
|
|
961
979
|
- Monitor performance metrics (events/second)
|
|
@@ -971,6 +989,8 @@ When editing content in the Content Manager, a **Live Presence** panel appears i
|
|
|
971
989
|
- **Active Editors** - List of other users editing the same content
|
|
972
990
|
- **Typing Indicator** - Shows when someone is typing and in which field
|
|
973
991
|
|
|
992
|
+

|
|
993
|
+
|
|
974
994
|
**How It Works:**
|
|
975
995
|
|
|
976
996
|
1. When you open a content entry, the panel connects via Socket.IO
|
|
@@ -978,24 +998,6 @@ When editing content in the Content Manager, a **Live Presence** panel appears i
|
|
|
978
998
|
3. Typing in any field broadcasts a typing indicator to others
|
|
979
999
|
4. When you leave, others are notified
|
|
980
1000
|
|
|
981
|
-
**Example Display:**
|
|
982
|
-
|
|
983
|
-
```
|
|
984
|
-
+-----------------------------+
|
|
985
|
-
| Live Presence |
|
|
986
|
-
+-----------------------------+
|
|
987
|
-
| [*] Live |
|
|
988
|
-
| Real-time sync active |
|
|
989
|
-
+-----------------------------+
|
|
990
|
-
| ALSO EDITING (1) |
|
|
991
|
-
| +-------------------------+ |
|
|
992
|
-
| | SA Sarah Admin | |
|
|
993
|
-
| | Typing in: title | |
|
|
994
|
-
| | [Typing...] | |
|
|
995
|
-
| +-------------------------+ |
|
|
996
|
-
+-----------------------------+
|
|
997
|
-
```
|
|
998
|
-
|
|
999
1001
|
---
|
|
1000
1002
|
|
|
1001
1003
|
## Monitoring Service
|
|
@@ -7,7 +7,7 @@ const designSystem = require("@strapi/design-system");
|
|
|
7
7
|
const admin = require("@strapi/strapi/admin");
|
|
8
8
|
const styled = require("styled-components");
|
|
9
9
|
const socket_ioClient = require("socket.io-client");
|
|
10
|
-
const index$1 = require("./index-
|
|
10
|
+
const index$1 = require("./index-DVNfszio.js");
|
|
11
11
|
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
12
12
|
const styled__default = /* @__PURE__ */ _interopDefault(styled);
|
|
13
13
|
const pulse = styled.keyframes`
|
|
@@ -5,7 +5,7 @@ import { Flex } from "@strapi/design-system";
|
|
|
5
5
|
import { useFetchClient } from "@strapi/strapi/admin";
|
|
6
6
|
import styled, { css, keyframes } from "styled-components";
|
|
7
7
|
import { io } from "socket.io-client";
|
|
8
|
-
import { P as PLUGIN_ID } from "./index-
|
|
8
|
+
import { P as PLUGIN_ID } from "./index-Bw7WjN5H.mjs";
|
|
9
9
|
const pulse = keyframes`
|
|
10
10
|
0%, 100% {
|
|
11
11
|
box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.2);
|
|
@@ -6,7 +6,7 @@ const admin = require("@strapi/strapi/admin");
|
|
|
6
6
|
const styled = require("styled-components");
|
|
7
7
|
const designSystem = require("@strapi/design-system");
|
|
8
8
|
const index = require("./index-DkTxsEqL.js");
|
|
9
|
-
const index$1 = require("./index-
|
|
9
|
+
const index$1 = require("./index-DVNfszio.js");
|
|
10
10
|
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
11
11
|
const styled__default = /* @__PURE__ */ _interopDefault(styled);
|
|
12
12
|
const UsersIcon = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", strokeWidth: 1.5, stroke: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M15 19.128a9.38 9.38 0 0 0 2.625.372 9.337 9.337 0 0 0 4.121-.952 4.125 4.125 0 0 0-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 0 1 8.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0 1 11.964-3.07M12 6.375a3.375 3.375 0 1 1-6.75 0 3.375 3.375 0 0 1 6.75 0Zm8.25 2.25a2.625 2.625 0 1 1-5.25 0 2.625 2.625 0 0 1 5.25 0Z" }) });
|
|
@@ -4,7 +4,7 @@ import { useFetchClient, useNotification } from "@strapi/strapi/admin";
|
|
|
4
4
|
import styled, { css, keyframes } from "styled-components";
|
|
5
5
|
import { Box, Loader, Flex, Field, TextInput, Badge, Typography } from "@strapi/design-system";
|
|
6
6
|
import { u as useIntl } from "./index-CEh8vkxY.mjs";
|
|
7
|
-
import { P as PLUGIN_ID } from "./index-
|
|
7
|
+
import { P as PLUGIN_ID } from "./index-Bw7WjN5H.mjs";
|
|
8
8
|
const UsersIcon = () => /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", strokeWidth: 1.5, stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M15 19.128a9.38 9.38 0 0 0 2.625.372 9.337 9.337 0 0 0 4.121-.952 4.125 4.125 0 0 0-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 0 1 8.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0 1 11.964-3.07M12 6.375a3.375 3.375 0 1 1-6.75 0 3.375 3.375 0 0 1 6.75 0Zm8.25 2.25a2.625 2.625 0 1 1-5.25 0 2.625 2.625 0 0 1 5.25 0Z" }) });
|
|
9
9
|
const BoltIcon = () => /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", strokeWidth: 1.5, stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "m3.75 13.5 10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75Z" }) });
|
|
10
10
|
const ChartBarIcon = () => /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", strokeWidth: 1.5, stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 0 1 3 19.875v-6.75ZM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V8.625ZM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V4.125Z" }) });
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const React = require("react");
|
|
5
|
+
const designSystem = require("@strapi/design-system");
|
|
6
|
+
const icons = require("@strapi/icons");
|
|
7
|
+
const admin = require("@strapi/strapi/admin");
|
|
8
|
+
const styled = require("styled-components");
|
|
9
|
+
const socket_ioClient = require("socket.io-client");
|
|
10
|
+
const index = require("./index-DVNfszio.js");
|
|
11
|
+
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
12
|
+
const styled__default = /* @__PURE__ */ _interopDefault(styled);
|
|
13
|
+
const pulse = styled.keyframes`
|
|
14
|
+
0%, 100% { opacity: 1; }
|
|
15
|
+
50% { opacity: 0.5; }
|
|
16
|
+
`;
|
|
17
|
+
const WidgetContainer = styled__default.default(designSystem.Box)`
|
|
18
|
+
padding: 0;
|
|
19
|
+
position: relative;
|
|
20
|
+
`;
|
|
21
|
+
const HeaderContainer = styled__default.default(designSystem.Flex)`
|
|
22
|
+
justify-content: space-between;
|
|
23
|
+
align-items: center;
|
|
24
|
+
margin-bottom: ${({ theme }) => theme.spaces[3]};
|
|
25
|
+
padding-bottom: ${({ theme }) => theme.spaces[2]};
|
|
26
|
+
border-bottom: 1px solid ${({ theme }) => theme.colors.neutral150};
|
|
27
|
+
`;
|
|
28
|
+
const LiveDot = styled__default.default.span`
|
|
29
|
+
display: inline-block;
|
|
30
|
+
width: 8px;
|
|
31
|
+
height: 8px;
|
|
32
|
+
border-radius: 50%;
|
|
33
|
+
background: ${({ theme, $connected }) => $connected ? theme.colors.success500 : theme.colors.neutral400};
|
|
34
|
+
margin-right: ${({ theme }) => theme.spaces[2]};
|
|
35
|
+
animation: ${({ $connected }) => $connected ? pulse : "none"} 2s ease-in-out infinite;
|
|
36
|
+
`;
|
|
37
|
+
const CountBadge = styled__default.default.span`
|
|
38
|
+
display: inline-flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
justify-content: center;
|
|
41
|
+
min-width: 24px;
|
|
42
|
+
height: 24px;
|
|
43
|
+
padding: 0 8px;
|
|
44
|
+
background: ${({ theme, $active }) => $active ? theme.colors.primary100 : theme.colors.neutral100};
|
|
45
|
+
color: ${({ theme, $active }) => $active ? theme.colors.primary700 : theme.colors.neutral600};
|
|
46
|
+
border-radius: 12px;
|
|
47
|
+
font-size: 12px;
|
|
48
|
+
font-weight: 600;
|
|
49
|
+
`;
|
|
50
|
+
const UserList = styled__default.default.div`
|
|
51
|
+
display: flex;
|
|
52
|
+
flex-direction: column;
|
|
53
|
+
gap: ${({ theme }) => theme.spaces[2]};
|
|
54
|
+
max-height: 280px;
|
|
55
|
+
overflow-y: auto;
|
|
56
|
+
`;
|
|
57
|
+
const UserCard = styled__default.default.div`
|
|
58
|
+
display: flex;
|
|
59
|
+
align-items: flex-start;
|
|
60
|
+
gap: ${({ theme }) => theme.spaces[3]};
|
|
61
|
+
padding: ${({ theme }) => theme.spaces[3]};
|
|
62
|
+
background: ${({ theme }) => theme.colors.neutral0};
|
|
63
|
+
border: 1px solid ${({ theme }) => theme.colors.neutral150};
|
|
64
|
+
border-radius: ${({ theme }) => theme.borderRadius};
|
|
65
|
+
transition: all 0.2s ease;
|
|
66
|
+
|
|
67
|
+
&:hover {
|
|
68
|
+
border-color: ${({ theme }) => theme.colors.primary200};
|
|
69
|
+
box-shadow: 0 2px 8px rgba(73, 69, 255, 0.08);
|
|
70
|
+
}
|
|
71
|
+
`;
|
|
72
|
+
const AVATAR_COLORS = [
|
|
73
|
+
"linear-gradient(135deg, #4945ff 0%, #7b79ff 100%)",
|
|
74
|
+
"linear-gradient(135deg, #3b82f6 0%, #60a5fa 100%)",
|
|
75
|
+
"linear-gradient(135deg, #10b981 0%, #34d399 100%)",
|
|
76
|
+
"linear-gradient(135deg, #f59e0b 0%, #fbbf24 100%)",
|
|
77
|
+
"linear-gradient(135deg, #ef4444 0%, #f87171 100%)",
|
|
78
|
+
"linear-gradient(135deg, #8b5cf6 0%, #a78bfa 100%)"
|
|
79
|
+
];
|
|
80
|
+
const UserAvatar = styled__default.default.div`
|
|
81
|
+
width: 40px;
|
|
82
|
+
height: 40px;
|
|
83
|
+
border-radius: 50%;
|
|
84
|
+
background: ${({ $colorIndex }) => AVATAR_COLORS[$colorIndex % AVATAR_COLORS.length]};
|
|
85
|
+
color: white;
|
|
86
|
+
font-size: 14px;
|
|
87
|
+
font-weight: 700;
|
|
88
|
+
display: flex;
|
|
89
|
+
align-items: center;
|
|
90
|
+
justify-content: center;
|
|
91
|
+
flex-shrink: 0;
|
|
92
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
93
|
+
`;
|
|
94
|
+
const UserInfo = styled__default.default.div`
|
|
95
|
+
flex: 1;
|
|
96
|
+
min-width: 0;
|
|
97
|
+
`;
|
|
98
|
+
const UserName = styled__default.default.div`
|
|
99
|
+
font-size: 14px;
|
|
100
|
+
font-weight: 600;
|
|
101
|
+
color: ${({ theme }) => theme.colors.neutral800};
|
|
102
|
+
white-space: nowrap;
|
|
103
|
+
overflow: hidden;
|
|
104
|
+
text-overflow: ellipsis;
|
|
105
|
+
`;
|
|
106
|
+
const UserMeta = styled__default.default.div`
|
|
107
|
+
font-size: 12px;
|
|
108
|
+
color: ${({ theme }) => theme.colors.neutral500};
|
|
109
|
+
display: flex;
|
|
110
|
+
align-items: center;
|
|
111
|
+
gap: ${({ theme }) => theme.spaces[2]};
|
|
112
|
+
margin-top: 2px;
|
|
113
|
+
`;
|
|
114
|
+
const EditingBadge = styled__default.default.a`
|
|
115
|
+
display: inline-flex;
|
|
116
|
+
align-items: center;
|
|
117
|
+
gap: 4px;
|
|
118
|
+
font-size: 11px;
|
|
119
|
+
font-weight: 500;
|
|
120
|
+
color: ${({ theme }) => theme.colors.success700};
|
|
121
|
+
background: ${({ theme }) => theme.colors.success100};
|
|
122
|
+
padding: 4px 10px;
|
|
123
|
+
border-radius: 10px;
|
|
124
|
+
margin-top: ${({ theme }) => theme.spaces[1]};
|
|
125
|
+
word-break: break-all;
|
|
126
|
+
max-width: 100%;
|
|
127
|
+
text-decoration: none;
|
|
128
|
+
cursor: pointer;
|
|
129
|
+
transition: all 0.15s ease;
|
|
130
|
+
|
|
131
|
+
&:hover {
|
|
132
|
+
background: ${({ theme }) => theme.colors.success200};
|
|
133
|
+
color: ${({ theme }) => theme.colors.success800};
|
|
134
|
+
transform: translateY(-1px);
|
|
135
|
+
}
|
|
136
|
+
`;
|
|
137
|
+
const IdleBadge = styled__default.default.span`
|
|
138
|
+
display: inline-flex;
|
|
139
|
+
align-items: center;
|
|
140
|
+
gap: 4px;
|
|
141
|
+
font-size: 11px;
|
|
142
|
+
font-weight: 500;
|
|
143
|
+
color: ${({ theme }) => theme.colors.neutral600};
|
|
144
|
+
background: ${({ theme }) => theme.colors.neutral100};
|
|
145
|
+
padding: 2px 8px;
|
|
146
|
+
border-radius: 10px;
|
|
147
|
+
margin-top: ${({ theme }) => theme.spaces[1]};
|
|
148
|
+
`;
|
|
149
|
+
const EmptyState = styled__default.default.div`
|
|
150
|
+
display: flex;
|
|
151
|
+
flex-direction: column;
|
|
152
|
+
align-items: center;
|
|
153
|
+
justify-content: center;
|
|
154
|
+
text-align: center;
|
|
155
|
+
padding: ${({ theme }) => theme.spaces[8]} ${({ theme }) => theme.spaces[4]};
|
|
156
|
+
color: ${({ theme }) => theme.colors.neutral500};
|
|
157
|
+
min-height: 180px;
|
|
158
|
+
`;
|
|
159
|
+
const EmptyIcon = styled__default.default.div`
|
|
160
|
+
width: 64px;
|
|
161
|
+
height: 64px;
|
|
162
|
+
border-radius: 50%;
|
|
163
|
+
background: ${({ theme }) => theme.colors.neutral100};
|
|
164
|
+
display: flex;
|
|
165
|
+
align-items: center;
|
|
166
|
+
justify-content: center;
|
|
167
|
+
margin-bottom: ${({ theme }) => theme.spaces[3]};
|
|
168
|
+
`;
|
|
169
|
+
const LoadingContainer = styled__default.default.div`
|
|
170
|
+
display: flex;
|
|
171
|
+
align-items: center;
|
|
172
|
+
justify-content: center;
|
|
173
|
+
padding: ${({ theme }) => theme.spaces[6]};
|
|
174
|
+
`;
|
|
175
|
+
const FooterLink = styled__default.default.a`
|
|
176
|
+
font-size: 12px;
|
|
177
|
+
color: ${({ theme }) => theme.colors.primary600};
|
|
178
|
+
text-decoration: none;
|
|
179
|
+
|
|
180
|
+
&:hover {
|
|
181
|
+
text-decoration: underline;
|
|
182
|
+
}
|
|
183
|
+
`;
|
|
184
|
+
const getInitials = (user) => {
|
|
185
|
+
const first = (user.firstname?.[0] || user.username?.[0] || user.email?.[0] || "?").toUpperCase();
|
|
186
|
+
const last = (user.lastname?.[0] || "").toUpperCase();
|
|
187
|
+
return `${first}${last}`.trim() || "?";
|
|
188
|
+
};
|
|
189
|
+
const getDisplayName = (user) => {
|
|
190
|
+
if (user.firstname) {
|
|
191
|
+
return `${user.firstname} ${user.lastname || ""}`.trim();
|
|
192
|
+
}
|
|
193
|
+
return user.username || user.email || "Unknown";
|
|
194
|
+
};
|
|
195
|
+
const formatDuration = (seconds) => {
|
|
196
|
+
if (seconds < 60) return "just now";
|
|
197
|
+
if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
|
|
198
|
+
if (seconds < 86400) return `${Math.floor(seconds / 3600)}h`;
|
|
199
|
+
return `${Math.floor(seconds / 86400)}d`;
|
|
200
|
+
};
|
|
201
|
+
const OnlineEditorsWidget = () => {
|
|
202
|
+
const { get, post } = admin.useFetchClient();
|
|
203
|
+
const [data, setData] = React.useState(null);
|
|
204
|
+
const [loading, setLoading] = React.useState(true);
|
|
205
|
+
const [error, setError] = React.useState(null);
|
|
206
|
+
const [connected, setConnected] = React.useState(false);
|
|
207
|
+
const socketRef = React.useRef(null);
|
|
208
|
+
const fetchOnlineUsers = React.useCallback(async () => {
|
|
209
|
+
try {
|
|
210
|
+
const response = await get(`/${index.PLUGIN_ID}/online-users`);
|
|
211
|
+
setData(response.data?.data || response.data);
|
|
212
|
+
setError(null);
|
|
213
|
+
setLoading(false);
|
|
214
|
+
} catch (err) {
|
|
215
|
+
console.error("[plugin-io] Failed to fetch online users:", err);
|
|
216
|
+
setError(err.message);
|
|
217
|
+
setLoading(false);
|
|
218
|
+
}
|
|
219
|
+
}, [get]);
|
|
220
|
+
React.useEffect(() => {
|
|
221
|
+
let cancelled = false;
|
|
222
|
+
let socket = null;
|
|
223
|
+
const connectSocket = async () => {
|
|
224
|
+
try {
|
|
225
|
+
const { data: sessionData } = await post(`/${index.PLUGIN_ID}/presence/session`, {});
|
|
226
|
+
if (cancelled || !sessionData?.token) return;
|
|
227
|
+
const socketUrl = sessionData.wsUrl || `${window.location.protocol}//${window.location.host}`;
|
|
228
|
+
socket = socket_ioClient.io(socketUrl, {
|
|
229
|
+
path: sessionData.wsPath || "/socket.io",
|
|
230
|
+
transports: ["websocket", "polling"],
|
|
231
|
+
auth: {
|
|
232
|
+
token: sessionData.token,
|
|
233
|
+
strategy: "admin-jwt",
|
|
234
|
+
isAdmin: true
|
|
235
|
+
},
|
|
236
|
+
reconnection: true,
|
|
237
|
+
reconnectionAttempts: 3
|
|
238
|
+
});
|
|
239
|
+
socketRef.current = socket;
|
|
240
|
+
socket.on("connect", () => {
|
|
241
|
+
if (!cancelled) {
|
|
242
|
+
setConnected(true);
|
|
243
|
+
console.log(`[${index.PLUGIN_ID}] Dashboard presence connected`);
|
|
244
|
+
fetchOnlineUsers();
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
socket.on("disconnect", () => {
|
|
248
|
+
if (!cancelled) {
|
|
249
|
+
setConnected(false);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
socket.on("connect_error", (err) => {
|
|
253
|
+
console.warn(`[${index.PLUGIN_ID}] Dashboard socket error:`, err.message);
|
|
254
|
+
});
|
|
255
|
+
socket.on("presence:update", () => {
|
|
256
|
+
fetchOnlineUsers();
|
|
257
|
+
});
|
|
258
|
+
} catch (err) {
|
|
259
|
+
console.error("[plugin-io] Failed to connect dashboard socket:", err);
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
connectSocket();
|
|
263
|
+
return () => {
|
|
264
|
+
cancelled = true;
|
|
265
|
+
if (socket) {
|
|
266
|
+
socket.disconnect();
|
|
267
|
+
socketRef.current = null;
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
}, [post, fetchOnlineUsers]);
|
|
271
|
+
React.useEffect(() => {
|
|
272
|
+
const interval = setInterval(fetchOnlineUsers, 15e3);
|
|
273
|
+
return () => clearInterval(interval);
|
|
274
|
+
}, [fetchOnlineUsers]);
|
|
275
|
+
if (loading) {
|
|
276
|
+
return /* @__PURE__ */ jsxRuntime.jsx(LoadingContainer, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: "Loading..." }) });
|
|
277
|
+
}
|
|
278
|
+
if (error) {
|
|
279
|
+
return /* @__PURE__ */ jsxRuntime.jsx(EmptyState, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "danger600", children: [
|
|
280
|
+
"Failed to load: ",
|
|
281
|
+
error
|
|
282
|
+
] }) });
|
|
283
|
+
}
|
|
284
|
+
const users = data?.users || [];
|
|
285
|
+
const counts = data?.counts || { total: 0, editing: 0 };
|
|
286
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(WidgetContainer, { children: [
|
|
287
|
+
/* @__PURE__ */ jsxRuntime.jsxs(HeaderContainer, { children: [
|
|
288
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, children: [
|
|
289
|
+
/* @__PURE__ */ jsxRuntime.jsx(LiveDot, { $connected: connected }),
|
|
290
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "bold", textColor: "neutral800", children: "Who's Online" })
|
|
291
|
+
] }),
|
|
292
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
|
|
293
|
+
/* @__PURE__ */ jsxRuntime.jsxs(CountBadge, { $active: counts.editing > 0, title: "Users editing", children: [
|
|
294
|
+
/* @__PURE__ */ jsxRuntime.jsx(icons.Pencil, { width: "12", height: "12", style: { marginRight: 4 } }),
|
|
295
|
+
counts.editing
|
|
296
|
+
] }),
|
|
297
|
+
/* @__PURE__ */ jsxRuntime.jsxs(CountBadge, { title: "Total online", children: [
|
|
298
|
+
/* @__PURE__ */ jsxRuntime.jsx(icons.User, { width: "12", height: "12", style: { marginRight: 4 } }),
|
|
299
|
+
counts.total
|
|
300
|
+
] })
|
|
301
|
+
] })
|
|
302
|
+
] }),
|
|
303
|
+
users.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs(EmptyState, { children: [
|
|
304
|
+
/* @__PURE__ */ jsxRuntime.jsx(EmptyIcon, { children: /* @__PURE__ */ jsxRuntime.jsx(icons.User, { width: "28", height: "28", fill: "#a5a5ba" }) }),
|
|
305
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "semiBold", textColor: "neutral600", children: "No one else is online" }),
|
|
306
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", style: { marginTop: 4 }, children: "You're the only one here right now" })
|
|
307
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx(UserList, { children: users.map((userData, index2) => /* @__PURE__ */ jsxRuntime.jsxs(UserCard, { children: [
|
|
308
|
+
/* @__PURE__ */ jsxRuntime.jsx(UserAvatar, { $colorIndex: index2, children: getInitials(userData.user) }),
|
|
309
|
+
/* @__PURE__ */ jsxRuntime.jsxs(UserInfo, { children: [
|
|
310
|
+
/* @__PURE__ */ jsxRuntime.jsxs(UserName, { children: [
|
|
311
|
+
getDisplayName(userData.user),
|
|
312
|
+
userData.user.isAdmin && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { size: "S", style: { marginLeft: 8 }, children: "Admin" })
|
|
313
|
+
] }),
|
|
314
|
+
/* @__PURE__ */ jsxRuntime.jsxs(UserMeta, { children: [
|
|
315
|
+
/* @__PURE__ */ jsxRuntime.jsx(icons.Clock, { width: "12", height: "12" }),
|
|
316
|
+
"Online ",
|
|
317
|
+
formatDuration(userData.onlineFor)
|
|
318
|
+
] }),
|
|
319
|
+
userData.isEditing ? userData.editingEntities.map((entity, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
320
|
+
EditingBadge,
|
|
321
|
+
{
|
|
322
|
+
href: `/admin/content-manager/collection-types/${entity.uid}/${entity.documentId}`,
|
|
323
|
+
target: "_blank",
|
|
324
|
+
rel: "noopener noreferrer",
|
|
325
|
+
title: "Open in new tab",
|
|
326
|
+
children: [
|
|
327
|
+
/* @__PURE__ */ jsxRuntime.jsx(icons.Pencil, { width: "10", height: "10" }),
|
|
328
|
+
entity.contentTypeName,
|
|
329
|
+
" - ",
|
|
330
|
+
entity.documentId
|
|
331
|
+
]
|
|
332
|
+
},
|
|
333
|
+
idx
|
|
334
|
+
)) : /* @__PURE__ */ jsxRuntime.jsx(IdleBadge, { children: "Idle" })
|
|
335
|
+
] })
|
|
336
|
+
] }, userData.socketId)) }),
|
|
337
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "flex-end", marginTop: 3, children: /* @__PURE__ */ jsxRuntime.jsx(FooterLink, { href: "/admin/settings/io/monitoring", children: "View All Activity" }) })
|
|
338
|
+
] });
|
|
339
|
+
};
|
|
340
|
+
exports.OnlineEditorsWidget = OnlineEditorsWidget;
|
|
341
|
+
exports.default = OnlineEditorsWidget;
|