@agent-analytics/paperclip-live-analytics-plugin 0.1.0 → 0.1.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/dist/manifest.js +51 -0
- package/dist/ui/index.js +578 -0
- package/dist/worker.js +1165 -0
- package/package.json +13 -8
- package/dist/assets/index-dyBLLxDx.css +0 -1
- package/dist/index.html +0 -14
- package/index.html +0 -13
- package/paperclip-plugin.manifest.json +0 -36
- package/src/shared/agent-analytics-client.js +0 -204
- package/src/shared/constants.js +0 -41
- package/src/shared/defaults.js +0 -73
- package/src/shared/live-state.js +0 -384
- package/src/ui/App.jsx +0 -87
- package/src/ui/demo-data.js +0 -238
- package/src/ui/main.jsx +0 -11
- package/src/ui/paperclip-bridge.js +0 -126
- package/src/ui/styles.css +0 -483
- package/src/ui/surfaces/PageSurface.jsx +0 -218
- package/src/ui/surfaces/SettingsSurface.jsx +0 -236
- package/src/ui/surfaces/WidgetSurface.jsx +0 -37
- package/src/worker/index.js +0 -36
- package/src/worker/paperclip.js +0 -37
- package/src/worker/service.js +0 -535
- package/src/worker/state.js +0 -58
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
function formatRelativeTime(timestamp) {
|
|
2
|
-
if (!timestamp) return 'No updates yet';
|
|
3
|
-
const seconds = Math.max(0, Math.round((Date.now() - timestamp) / 1000));
|
|
4
|
-
if (seconds < 5) return 'Updated just now';
|
|
5
|
-
if (seconds < 60) return `Updated ${seconds}s ago`;
|
|
6
|
-
const minutes = Math.round(seconds / 60);
|
|
7
|
-
return `Updated ${minutes}m ago`;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function CountryPulse({ liveState }) {
|
|
11
|
-
const hotCountry = liveState.world.hotCountry || 'World';
|
|
12
|
-
return (
|
|
13
|
-
<section className="aa-panel aa-world-panel">
|
|
14
|
-
<div className="aa-panel-header">
|
|
15
|
-
<div>
|
|
16
|
-
<p className="aa-kicker">World / Country View</p>
|
|
17
|
-
<h2>Supporting geography, not dashboard wallpaper.</h2>
|
|
18
|
-
</div>
|
|
19
|
-
<div className="aa-world-hot">{hotCountry}</div>
|
|
20
|
-
</div>
|
|
21
|
-
<div className="aa-world-grid">
|
|
22
|
-
<div className="aa-globe">
|
|
23
|
-
<div className="aa-globe-ring aa-globe-ring-one" />
|
|
24
|
-
<div className="aa-globe-ring aa-globe-ring-two" />
|
|
25
|
-
<div className="aa-globe-ring aa-globe-ring-three" />
|
|
26
|
-
<div className="aa-globe-core">
|
|
27
|
-
<span>Live</span>
|
|
28
|
-
</div>
|
|
29
|
-
</div>
|
|
30
|
-
<div className="aa-country-list">
|
|
31
|
-
{liveState.world.countries.map((country) => (
|
|
32
|
-
<div className="aa-country-row" key={country.country}>
|
|
33
|
-
<div>
|
|
34
|
-
<strong>{country.country}</strong>
|
|
35
|
-
<span>{country.visitors} visitors</span>
|
|
36
|
-
</div>
|
|
37
|
-
<div className="aa-country-bar">
|
|
38
|
-
<div
|
|
39
|
-
className="aa-country-bar-fill"
|
|
40
|
-
style={{ width: `${Math.min(100, (country.events / Math.max(1, liveState.metrics.eventsPerMinute)) * 100)}%` }}
|
|
41
|
-
/>
|
|
42
|
-
</div>
|
|
43
|
-
</div>
|
|
44
|
-
))}
|
|
45
|
-
</div>
|
|
46
|
-
</div>
|
|
47
|
-
</section>
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function EvidenceColumn({ liveState }) {
|
|
52
|
-
return (
|
|
53
|
-
<section className="aa-panel">
|
|
54
|
-
<div className="aa-panel-header">
|
|
55
|
-
<div>
|
|
56
|
-
<p className="aa-kicker">Operator Evidence</p>
|
|
57
|
-
<h2>Fast feed, top pages, and why the pulse changed.</h2>
|
|
58
|
-
</div>
|
|
59
|
-
</div>
|
|
60
|
-
<div className="aa-evidence-grid">
|
|
61
|
-
<div className="aa-mini-panel">
|
|
62
|
-
<h3>Top Pages</h3>
|
|
63
|
-
{liveState.evidence.topPages.map((page) => (
|
|
64
|
-
<div className="aa-mini-row" key={page.path}>
|
|
65
|
-
<span>{page.path}</span>
|
|
66
|
-
<strong>{page.visitors}</strong>
|
|
67
|
-
</div>
|
|
68
|
-
))}
|
|
69
|
-
</div>
|
|
70
|
-
<div className="aa-mini-panel">
|
|
71
|
-
<h3>Top Events</h3>
|
|
72
|
-
{liveState.evidence.topEvents.map((event) => (
|
|
73
|
-
<div className="aa-mini-row" key={event.event}>
|
|
74
|
-
<span>{event.event}</span>
|
|
75
|
-
<strong>{event.count}</strong>
|
|
76
|
-
</div>
|
|
77
|
-
))}
|
|
78
|
-
</div>
|
|
79
|
-
</div>
|
|
80
|
-
<div className="aa-feed">
|
|
81
|
-
{liveState.evidence.recentEvents.map((event) => (
|
|
82
|
-
<div className="aa-feed-row" key={event.id}>
|
|
83
|
-
<div>
|
|
84
|
-
<strong>{event.event}</strong>
|
|
85
|
-
<span>{event.assetLabel || 'Unmapped asset'} · {event.path || 'no path'} · {event.country || '??'}</span>
|
|
86
|
-
</div>
|
|
87
|
-
<time>{formatRelativeTime(event.timestamp)}</time>
|
|
88
|
-
</div>
|
|
89
|
-
))}
|
|
90
|
-
</div>
|
|
91
|
-
</section>
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function AssetCard({ asset, onSnooze, basePath }) {
|
|
96
|
-
return (
|
|
97
|
-
<article className={`aa-asset-card aa-asset-card-${asset.kind}`}>
|
|
98
|
-
<div className="aa-asset-topline">
|
|
99
|
-
<div>
|
|
100
|
-
<p className="aa-kicker">{asset.kind}</p>
|
|
101
|
-
<h3>{asset.label}</h3>
|
|
102
|
-
</div>
|
|
103
|
-
<span className={`aa-status-pill aa-status-${asset.status}`}>{asset.status}</span>
|
|
104
|
-
</div>
|
|
105
|
-
|
|
106
|
-
<div className="aa-asset-metrics">
|
|
107
|
-
<div>
|
|
108
|
-
<span>Visitors</span>
|
|
109
|
-
<strong>{asset.activeVisitors}</strong>
|
|
110
|
-
</div>
|
|
111
|
-
<div>
|
|
112
|
-
<span>Sessions</span>
|
|
113
|
-
<strong>{asset.activeSessions}</strong>
|
|
114
|
-
</div>
|
|
115
|
-
<div>
|
|
116
|
-
<span>Events / min</span>
|
|
117
|
-
<strong>{asset.eventsPerMinute}</strong>
|
|
118
|
-
</div>
|
|
119
|
-
</div>
|
|
120
|
-
|
|
121
|
-
<div className="aa-asset-details">
|
|
122
|
-
<div>
|
|
123
|
-
<span className="aa-label">Project</span>
|
|
124
|
-
<strong>{asset.agentAnalyticsProject}</strong>
|
|
125
|
-
</div>
|
|
126
|
-
<div>
|
|
127
|
-
<span className="aa-label">Hot country</span>
|
|
128
|
-
<strong>{asset.lastHotCountry || 'Waiting for stream'}</strong>
|
|
129
|
-
</div>
|
|
130
|
-
<div>
|
|
131
|
-
<span className="aa-label">Updated</span>
|
|
132
|
-
<strong>{formatRelativeTime(asset.lastUpdatedAt)}</strong>
|
|
133
|
-
</div>
|
|
134
|
-
</div>
|
|
135
|
-
|
|
136
|
-
<div className="aa-asset-evidence">
|
|
137
|
-
<div>
|
|
138
|
-
<span className="aa-label">Top page</span>
|
|
139
|
-
<strong>{asset.topPages[0]?.path || 'No pages yet'}</strong>
|
|
140
|
-
</div>
|
|
141
|
-
<div>
|
|
142
|
-
<span className="aa-label">Top event</span>
|
|
143
|
-
<strong>{asset.topEvents[0]?.event || 'No events yet'}</strong>
|
|
144
|
-
</div>
|
|
145
|
-
</div>
|
|
146
|
-
|
|
147
|
-
<div className="aa-asset-actions">
|
|
148
|
-
{asset.paperclipProjectId ? (
|
|
149
|
-
<a className="aa-button aa-button-secondary" href={`${basePath || ''}/projects/${asset.paperclipProjectId}`}>
|
|
150
|
-
Open mapped project
|
|
151
|
-
</a>
|
|
152
|
-
) : (
|
|
153
|
-
<span className="aa-muted-note">No Paperclip project linked yet</span>
|
|
154
|
-
)}
|
|
155
|
-
<button className="aa-button aa-button-ghost" onClick={() => onSnooze(asset.assetKey)}>
|
|
156
|
-
Snooze 30m
|
|
157
|
-
</button>
|
|
158
|
-
</div>
|
|
159
|
-
</article>
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export function PageSurface({ liveState, onSnooze, basePath = '' }) {
|
|
164
|
-
return (
|
|
165
|
-
<div className="aa-page-shell">
|
|
166
|
-
<header className="aa-hero">
|
|
167
|
-
<div>
|
|
168
|
-
<p className="aa-kicker">Agent Analytics Live</p>
|
|
169
|
-
<h1>Ambient pulse for the company, backed by raw evidence.</h1>
|
|
170
|
-
<p className="aa-hero-copy">{liveState.connection.detail}</p>
|
|
171
|
-
</div>
|
|
172
|
-
<div className="aa-hero-status">
|
|
173
|
-
<span className={`aa-status-pill aa-status-${liveState.connection.status}`}>{liveState.connection.label}</span>
|
|
174
|
-
<p>{liveState.account?.email || 'No connected account'}</p>
|
|
175
|
-
</div>
|
|
176
|
-
</header>
|
|
177
|
-
|
|
178
|
-
<section className="aa-metric-grid">
|
|
179
|
-
<div className="aa-metric-card">
|
|
180
|
-
<span>Active visitors</span>
|
|
181
|
-
<strong>{liveState.metrics.activeVisitors}</strong>
|
|
182
|
-
</div>
|
|
183
|
-
<div className="aa-metric-card">
|
|
184
|
-
<span>Active sessions</span>
|
|
185
|
-
<strong>{liveState.metrics.activeSessions}</strong>
|
|
186
|
-
</div>
|
|
187
|
-
<div className="aa-metric-card">
|
|
188
|
-
<span>Events / min</span>
|
|
189
|
-
<strong>{liveState.metrics.eventsPerMinute}</strong>
|
|
190
|
-
</div>
|
|
191
|
-
<div className="aa-metric-card">
|
|
192
|
-
<span>Visible assets</span>
|
|
193
|
-
<strong>{liveState.metrics.assetsVisible}</strong>
|
|
194
|
-
</div>
|
|
195
|
-
</section>
|
|
196
|
-
|
|
197
|
-
<div className="aa-main-grid">
|
|
198
|
-
<CountryPulse liveState={liveState} />
|
|
199
|
-
<EvidenceColumn liveState={liveState} />
|
|
200
|
-
</div>
|
|
201
|
-
|
|
202
|
-
<section className="aa-assets-section">
|
|
203
|
-
<div className="aa-panel-header">
|
|
204
|
-
<div>
|
|
205
|
-
<p className="aa-kicker">Mapped Assets</p>
|
|
206
|
-
<h2>Each card ties live movement back to an owned company asset.</h2>
|
|
207
|
-
</div>
|
|
208
|
-
</div>
|
|
209
|
-
<div className="aa-asset-grid">
|
|
210
|
-
{liveState.assets.map((asset) => (
|
|
211
|
-
<AssetCard key={asset.assetKey} asset={asset} onSnooze={onSnooze} basePath={basePath} />
|
|
212
|
-
))}
|
|
213
|
-
</div>
|
|
214
|
-
</section>
|
|
215
|
-
</div>
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
|
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
|
|
3
|
-
const EMPTY_MAPPING = {
|
|
4
|
-
assetKey: '',
|
|
5
|
-
label: '',
|
|
6
|
-
kind: 'website',
|
|
7
|
-
paperclipProjectId: '',
|
|
8
|
-
agentAnalyticsProject: '',
|
|
9
|
-
primaryHostname: '',
|
|
10
|
-
enabled: true,
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
function MappingRow({ mapping, onRemove }) {
|
|
14
|
-
return (
|
|
15
|
-
<div className="aa-settings-row">
|
|
16
|
-
<div>
|
|
17
|
-
<strong>{mapping.label}</strong>
|
|
18
|
-
<span>{mapping.kind} · {mapping.agentAnalyticsProject}</span>
|
|
19
|
-
</div>
|
|
20
|
-
<button className="aa-button aa-button-ghost" onClick={() => onRemove(mapping.assetKey)}>
|
|
21
|
-
Remove
|
|
22
|
-
</button>
|
|
23
|
-
</div>
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function SettingsSurface({
|
|
28
|
-
settingsData,
|
|
29
|
-
onStartAuth,
|
|
30
|
-
onCompleteAuth,
|
|
31
|
-
onReconnect,
|
|
32
|
-
onDisconnect,
|
|
33
|
-
onSaveSettings,
|
|
34
|
-
onUpsertMapping,
|
|
35
|
-
onRemoveMapping,
|
|
36
|
-
}) {
|
|
37
|
-
const [formState, setFormState] = useState(() => ({
|
|
38
|
-
agentAnalyticsBaseUrl: settingsData.settings.agentAnalyticsBaseUrl,
|
|
39
|
-
liveWindowSeconds: settingsData.settings.liveWindowSeconds,
|
|
40
|
-
pollIntervalSeconds: settingsData.settings.pollIntervalSeconds,
|
|
41
|
-
pluginEnabled: settingsData.settings.pluginEnabled,
|
|
42
|
-
}));
|
|
43
|
-
const [mappingForm, setMappingForm] = useState(EMPTY_MAPPING);
|
|
44
|
-
const [exchangeCode, setExchangeCode] = useState('');
|
|
45
|
-
|
|
46
|
-
return (
|
|
47
|
-
<div className="aa-settings-shell">
|
|
48
|
-
<section className="aa-panel">
|
|
49
|
-
<div className="aa-panel-header">
|
|
50
|
-
<div>
|
|
51
|
-
<p className="aa-kicker">Connection</p>
|
|
52
|
-
<h2>Login-first auth, worker-held tokens.</h2>
|
|
53
|
-
</div>
|
|
54
|
-
<span className={`aa-status-pill aa-status-${settingsData.auth.status}`}>{settingsData.auth.status}</span>
|
|
55
|
-
</div>
|
|
56
|
-
|
|
57
|
-
<div className="aa-settings-grid">
|
|
58
|
-
<div className="aa-settings-stack">
|
|
59
|
-
<div className="aa-settings-row">
|
|
60
|
-
<div>
|
|
61
|
-
<strong>Connected account</strong>
|
|
62
|
-
<span>{settingsData.auth.accountSummary?.email || 'Not connected'}</span>
|
|
63
|
-
</div>
|
|
64
|
-
{settingsData.auth.connected ? (
|
|
65
|
-
<button className="aa-button aa-button-secondary" onClick={onDisconnect}>Disconnect</button>
|
|
66
|
-
) : (
|
|
67
|
-
<button className="aa-button aa-button-primary" onClick={onStartAuth}>Start login</button>
|
|
68
|
-
)}
|
|
69
|
-
</div>
|
|
70
|
-
|
|
71
|
-
{settingsData.auth.pendingAuthRequest ? (
|
|
72
|
-
<div className="aa-auth-box">
|
|
73
|
-
<label>
|
|
74
|
-
Approval URL
|
|
75
|
-
<a href={settingsData.auth.pendingAuthRequest.authorizeUrl} target="_blank" rel="noreferrer">
|
|
76
|
-
{settingsData.auth.pendingAuthRequest.authorizeUrl}
|
|
77
|
-
</a>
|
|
78
|
-
</label>
|
|
79
|
-
<label>
|
|
80
|
-
Finish code
|
|
81
|
-
<input value={exchangeCode} onChange={(event) => setExchangeCode(event.target.value)} placeholder="Paste finish code" />
|
|
82
|
-
</label>
|
|
83
|
-
<div className="aa-inline-actions">
|
|
84
|
-
<button className="aa-button aa-button-primary" onClick={() => onCompleteAuth(settingsData.auth.pendingAuthRequest.authRequestId, exchangeCode)}>
|
|
85
|
-
Complete login
|
|
86
|
-
</button>
|
|
87
|
-
<button className="aa-button aa-button-ghost" onClick={onReconnect}>Refresh session</button>
|
|
88
|
-
</div>
|
|
89
|
-
</div>
|
|
90
|
-
) : null}
|
|
91
|
-
</div>
|
|
92
|
-
|
|
93
|
-
<div className="aa-mini-panel">
|
|
94
|
-
<h3>Discovered projects</h3>
|
|
95
|
-
{(settingsData.discoveredProjects || []).map((project) => (
|
|
96
|
-
<div className="aa-mini-row" key={project.id || project.name}>
|
|
97
|
-
<span>{project.name}</span>
|
|
98
|
-
<strong>{project.allowed_origins || '*'}</strong>
|
|
99
|
-
</div>
|
|
100
|
-
))}
|
|
101
|
-
</div>
|
|
102
|
-
</div>
|
|
103
|
-
</section>
|
|
104
|
-
|
|
105
|
-
<section className="aa-panel">
|
|
106
|
-
<div className="aa-panel-header">
|
|
107
|
-
<div>
|
|
108
|
-
<p className="aa-kicker">Rollout Controls</p>
|
|
109
|
-
<h2>Keep the live window short and the poll cadence explicit.</h2>
|
|
110
|
-
</div>
|
|
111
|
-
</div>
|
|
112
|
-
|
|
113
|
-
<div className="aa-form-grid">
|
|
114
|
-
<label>
|
|
115
|
-
Agent Analytics base URL
|
|
116
|
-
<input
|
|
117
|
-
value={formState.agentAnalyticsBaseUrl}
|
|
118
|
-
onChange={(event) => setFormState((current) => ({ ...current, agentAnalyticsBaseUrl: event.target.value }))}
|
|
119
|
-
/>
|
|
120
|
-
</label>
|
|
121
|
-
<label>
|
|
122
|
-
Live window seconds
|
|
123
|
-
<input
|
|
124
|
-
type="number"
|
|
125
|
-
value={formState.liveWindowSeconds}
|
|
126
|
-
onChange={(event) => setFormState((current) => ({ ...current, liveWindowSeconds: Number(event.target.value) }))}
|
|
127
|
-
/>
|
|
128
|
-
</label>
|
|
129
|
-
<label>
|
|
130
|
-
Poll interval seconds
|
|
131
|
-
<input
|
|
132
|
-
type="number"
|
|
133
|
-
value={formState.pollIntervalSeconds}
|
|
134
|
-
onChange={(event) => setFormState((current) => ({ ...current, pollIntervalSeconds: Number(event.target.value) }))}
|
|
135
|
-
/>
|
|
136
|
-
</label>
|
|
137
|
-
<label className="aa-checkbox">
|
|
138
|
-
<input
|
|
139
|
-
type="checkbox"
|
|
140
|
-
checked={formState.pluginEnabled}
|
|
141
|
-
onChange={(event) => setFormState((current) => ({ ...current, pluginEnabled: event.target.checked }))}
|
|
142
|
-
/>
|
|
143
|
-
Plugin enabled
|
|
144
|
-
</label>
|
|
145
|
-
</div>
|
|
146
|
-
|
|
147
|
-
<div className="aa-inline-actions">
|
|
148
|
-
<button className="aa-button aa-button-primary" onClick={() => onSaveSettings(formState)}>
|
|
149
|
-
Save controls
|
|
150
|
-
</button>
|
|
151
|
-
<button className="aa-button aa-button-ghost" onClick={onReconnect}>
|
|
152
|
-
Revalidate connection
|
|
153
|
-
</button>
|
|
154
|
-
</div>
|
|
155
|
-
</section>
|
|
156
|
-
|
|
157
|
-
<section className="aa-panel">
|
|
158
|
-
<div className="aa-panel-header">
|
|
159
|
-
<div>
|
|
160
|
-
<p className="aa-kicker">Asset Mapping</p>
|
|
161
|
-
<h2>Explicit Paperclip asset to Agent Analytics project links.</h2>
|
|
162
|
-
</div>
|
|
163
|
-
</div>
|
|
164
|
-
|
|
165
|
-
<div className="aa-form-grid">
|
|
166
|
-
<label>
|
|
167
|
-
Asset key
|
|
168
|
-
<input value={mappingForm.assetKey} onChange={(event) => setMappingForm((current) => ({ ...current, assetKey: event.target.value }))} />
|
|
169
|
-
</label>
|
|
170
|
-
<label>
|
|
171
|
-
Label
|
|
172
|
-
<input value={mappingForm.label} onChange={(event) => setMappingForm((current) => ({ ...current, label: event.target.value }))} />
|
|
173
|
-
</label>
|
|
174
|
-
<label>
|
|
175
|
-
Kind
|
|
176
|
-
<select value={mappingForm.kind} onChange={(event) => setMappingForm((current) => ({ ...current, kind: event.target.value }))}>
|
|
177
|
-
<option value="website">website</option>
|
|
178
|
-
<option value="docs">docs</option>
|
|
179
|
-
<option value="app">app</option>
|
|
180
|
-
<option value="api">api</option>
|
|
181
|
-
<option value="other">other</option>
|
|
182
|
-
</select>
|
|
183
|
-
</label>
|
|
184
|
-
<label>
|
|
185
|
-
Paperclip project ID
|
|
186
|
-
<input value={mappingForm.paperclipProjectId} onChange={(event) => setMappingForm((current) => ({ ...current, paperclipProjectId: event.target.value }))} />
|
|
187
|
-
</label>
|
|
188
|
-
<label>
|
|
189
|
-
Agent Analytics project
|
|
190
|
-
<input value={mappingForm.agentAnalyticsProject} onChange={(event) => setMappingForm((current) => ({ ...current, agentAnalyticsProject: event.target.value }))} />
|
|
191
|
-
</label>
|
|
192
|
-
<label>
|
|
193
|
-
Primary hostname
|
|
194
|
-
<input value={mappingForm.primaryHostname} onChange={(event) => setMappingForm((current) => ({ ...current, primaryHostname: event.target.value }))} />
|
|
195
|
-
</label>
|
|
196
|
-
<label className="aa-checkbox">
|
|
197
|
-
<input
|
|
198
|
-
type="checkbox"
|
|
199
|
-
checked={mappingForm.enabled}
|
|
200
|
-
onChange={(event) => setMappingForm((current) => ({ ...current, enabled: event.target.checked }))}
|
|
201
|
-
/>
|
|
202
|
-
Enabled
|
|
203
|
-
</label>
|
|
204
|
-
</div>
|
|
205
|
-
|
|
206
|
-
<div className="aa-inline-actions">
|
|
207
|
-
<button
|
|
208
|
-
className="aa-button aa-button-primary"
|
|
209
|
-
onClick={() => {
|
|
210
|
-
onUpsertMapping(mappingForm);
|
|
211
|
-
setMappingForm(EMPTY_MAPPING);
|
|
212
|
-
}}
|
|
213
|
-
>
|
|
214
|
-
Save mapping
|
|
215
|
-
</button>
|
|
216
|
-
</div>
|
|
217
|
-
|
|
218
|
-
<div className="aa-settings-stack">
|
|
219
|
-
{settingsData.settings.monitoredAssets.map((mapping) => (
|
|
220
|
-
<MappingRow key={mapping.assetKey} mapping={mapping} onRemove={onRemoveMapping} />
|
|
221
|
-
))}
|
|
222
|
-
</div>
|
|
223
|
-
</section>
|
|
224
|
-
|
|
225
|
-
{settingsData.validation.warnings.length > 0 ? (
|
|
226
|
-
<section className="aa-panel aa-panel-warning">
|
|
227
|
-
<h3>Warnings</h3>
|
|
228
|
-
{settingsData.validation.warnings.map((warning) => (
|
|
229
|
-
<p key={warning}>{warning}</p>
|
|
230
|
-
))}
|
|
231
|
-
</section>
|
|
232
|
-
) : null}
|
|
233
|
-
</div>
|
|
234
|
-
);
|
|
235
|
-
}
|
|
236
|
-
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
export function WidgetSurface({ widget, fullPageHref = '?surface=page' }) {
|
|
2
|
-
return (
|
|
3
|
-
<section className="aa-widget">
|
|
4
|
-
<div className="aa-widget-header">
|
|
5
|
-
<div>
|
|
6
|
-
<p className="aa-kicker">Live Status</p>
|
|
7
|
-
<h2>{widget.connection.label}</h2>
|
|
8
|
-
</div>
|
|
9
|
-
<span className={`aa-status-pill aa-status-${widget.connection.status}`}>{widget.tier || 'tier unknown'}</span>
|
|
10
|
-
</div>
|
|
11
|
-
|
|
12
|
-
<div className="aa-widget-metrics">
|
|
13
|
-
<div>
|
|
14
|
-
<span>Visitors</span>
|
|
15
|
-
<strong>{widget.metrics.activeVisitors}</strong>
|
|
16
|
-
</div>
|
|
17
|
-
<div>
|
|
18
|
-
<span>Sessions</span>
|
|
19
|
-
<strong>{widget.metrics.activeSessions}</strong>
|
|
20
|
-
</div>
|
|
21
|
-
<div>
|
|
22
|
-
<span>EPM</span>
|
|
23
|
-
<strong>{widget.metrics.eventsPerMinute}</strong>
|
|
24
|
-
</div>
|
|
25
|
-
</div>
|
|
26
|
-
|
|
27
|
-
<div className="aa-widget-footer">
|
|
28
|
-
<div>
|
|
29
|
-
<span className="aa-label">Top asset</span>
|
|
30
|
-
<strong>{widget.topAsset?.label || 'No live assets yet'}</strong>
|
|
31
|
-
</div>
|
|
32
|
-
<a className="aa-button aa-button-secondary" href={fullPageHref}>Open full live page</a>
|
|
33
|
-
</div>
|
|
34
|
-
</section>
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
|
package/src/worker/index.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { ACTION_KEYS, DATA_KEYS, PLUGIN_DISPLAY_NAME, PLUGIN_ID } from '../shared/constants.js';
|
|
2
|
-
import { PaperclipLiveAnalyticsService } from './service.js';
|
|
3
|
-
|
|
4
|
-
export const manifest = {
|
|
5
|
-
id: PLUGIN_ID,
|
|
6
|
-
displayName: PLUGIN_DISPLAY_NAME,
|
|
7
|
-
entrypoints: {
|
|
8
|
-
worker: './src/worker/index.js',
|
|
9
|
-
ui: './dist',
|
|
10
|
-
},
|
|
11
|
-
data: DATA_KEYS,
|
|
12
|
-
actions: ACTION_KEYS,
|
|
13
|
-
surfaces: ['page', 'dashboardWidget', 'settingsPage'],
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export async function setup(ctx) {
|
|
17
|
-
const service = new PaperclipLiveAnalyticsService(ctx);
|
|
18
|
-
await service.register();
|
|
19
|
-
|
|
20
|
-
return {
|
|
21
|
-
async shutdown() {
|
|
22
|
-
await service.shutdown();
|
|
23
|
-
},
|
|
24
|
-
async health() {
|
|
25
|
-
return {
|
|
26
|
-
ok: true,
|
|
27
|
-
plugin: PLUGIN_ID,
|
|
28
|
-
};
|
|
29
|
-
},
|
|
30
|
-
async onConfigChange({ companyId }) {
|
|
31
|
-
await service.ensureLiveState(companyId, { forceSync: true });
|
|
32
|
-
},
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export default manifest;
|
package/src/worker/paperclip.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { LIVE_STREAM_CHANNEL } from '../shared/constants.js';
|
|
2
|
-
|
|
3
|
-
export async function registerDataHandler(ctx, key, handler) {
|
|
4
|
-
if (ctx?.data?.register) {
|
|
5
|
-
return ctx.data.register(key, handler);
|
|
6
|
-
}
|
|
7
|
-
if (ctx?.registerData) {
|
|
8
|
-
return ctx.registerData(key, handler);
|
|
9
|
-
}
|
|
10
|
-
throw new Error(`Paperclip data registration is unavailable for key "${key}"`);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export async function registerActionHandler(ctx, key, handler) {
|
|
14
|
-
if (ctx?.actions?.register) {
|
|
15
|
-
return ctx.actions.register(key, handler);
|
|
16
|
-
}
|
|
17
|
-
if (ctx?.registerAction) {
|
|
18
|
-
return ctx.registerAction(key, handler);
|
|
19
|
-
}
|
|
20
|
-
throw new Error(`Paperclip action registration is unavailable for key "${key}"`);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export async function openLiveChannel(ctx, companyId) {
|
|
24
|
-
if (!ctx?.streams?.open) return;
|
|
25
|
-
await ctx.streams.open(LIVE_STREAM_CHANNEL, companyId);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export async function emitLiveState(ctx, companyId, payload) {
|
|
29
|
-
if (!ctx?.streams?.emit) return;
|
|
30
|
-
await ctx.streams.emit(LIVE_STREAM_CHANNEL, payload);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export async function closeLiveChannel(ctx, companyId) {
|
|
34
|
-
if (!ctx?.streams?.close) return;
|
|
35
|
-
await ctx.streams.close(LIVE_STREAM_CHANNEL, companyId);
|
|
36
|
-
}
|
|
37
|
-
|