@creezio/observability 0.22.0

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.
Files changed (209) hide show
  1. package/README.md +328 -0
  2. package/dist/.creezio-src-hash.json +4 -0
  3. package/dist/api-endpoints/http-routes.d.ts +11 -0
  4. package/dist/api-endpoints/http-routes.d.ts.map +1 -0
  5. package/dist/api-endpoints/http-routes.js +11 -0
  6. package/dist/api-endpoints/http-routes.js.map +1 -0
  7. package/dist/api-endpoints/index.d.ts +4 -0
  8. package/dist/api-endpoints/index.d.ts.map +1 -0
  9. package/dist/api-endpoints/index.js +3 -0
  10. package/dist/api-endpoints/index.js.map +1 -0
  11. package/dist/api-endpoints/registry.d.ts +66 -0
  12. package/dist/api-endpoints/registry.d.ts.map +1 -0
  13. package/dist/api-endpoints/registry.js +102 -0
  14. package/dist/api-endpoints/registry.js.map +1 -0
  15. package/dist/api-mount.d.ts +7 -0
  16. package/dist/api-mount.d.ts.map +1 -0
  17. package/dist/api-mount.js +114 -0
  18. package/dist/api-mount.js.map +1 -0
  19. package/dist/helpers.d.ts +23 -0
  20. package/dist/helpers.d.ts.map +1 -0
  21. package/dist/helpers.js +36 -0
  22. package/dist/helpers.js.map +1 -0
  23. package/dist/index.d.ts +35 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +24 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/memory-store.d.ts +3 -0
  28. package/dist/memory-store.d.ts.map +1 -0
  29. package/dist/memory-store.js +113 -0
  30. package/dist/memory-store.js.map +1 -0
  31. package/dist/ops/emit.d.ts +8 -0
  32. package/dist/ops/emit.d.ts.map +1 -0
  33. package/dist/ops/emit.js +15 -0
  34. package/dist/ops/emit.js.map +1 -0
  35. package/dist/ops/fleet-activity.d.ts +50 -0
  36. package/dist/ops/fleet-activity.d.ts.map +1 -0
  37. package/dist/ops/fleet-activity.js +123 -0
  38. package/dist/ops/fleet-activity.js.map +1 -0
  39. package/dist/ops/fleet-agent.d.ts +102 -0
  40. package/dist/ops/fleet-agent.d.ts.map +1 -0
  41. package/dist/ops/fleet-agent.js +352 -0
  42. package/dist/ops/fleet-agent.js.map +1 -0
  43. package/dist/ops/fleet-samples.d.ts +33 -0
  44. package/dist/ops/fleet-samples.d.ts.map +1 -0
  45. package/dist/ops/fleet-samples.js +223 -0
  46. package/dist/ops/fleet-samples.js.map +1 -0
  47. package/dist/ops/journal.d.ts +56 -0
  48. package/dist/ops/journal.d.ts.map +1 -0
  49. package/dist/ops/journal.js +307 -0
  50. package/dist/ops/journal.js.map +1 -0
  51. package/dist/ops/rules.d.ts +16 -0
  52. package/dist/ops/rules.d.ts.map +1 -0
  53. package/dist/ops/rules.js +98 -0
  54. package/dist/ops/rules.js.map +1 -0
  55. package/dist/ops/types.d.ts +69 -0
  56. package/dist/ops/types.d.ts.map +1 -0
  57. package/dist/ops/types.js +119 -0
  58. package/dist/ops/types.js.map +1 -0
  59. package/dist/request-logs/config.d.ts +19 -0
  60. package/dist/request-logs/config.d.ts.map +1 -0
  61. package/dist/request-logs/config.js +36 -0
  62. package/dist/request-logs/config.js.map +1 -0
  63. package/dist/request-logs/http-routes.d.ts +8 -0
  64. package/dist/request-logs/http-routes.d.ts.map +1 -0
  65. package/dist/request-logs/http-routes.js +29 -0
  66. package/dist/request-logs/http-routes.js.map +1 -0
  67. package/dist/request-logs/index.d.ts +6 -0
  68. package/dist/request-logs/index.d.ts.map +1 -0
  69. package/dist/request-logs/index.js +5 -0
  70. package/dist/request-logs/index.js.map +1 -0
  71. package/dist/request-logs/middleware.d.ts +11 -0
  72. package/dist/request-logs/middleware.d.ts.map +1 -0
  73. package/dist/request-logs/middleware.js +198 -0
  74. package/dist/request-logs/middleware.js.map +1 -0
  75. package/dist/request-logs/request-logs.d.ts +60 -0
  76. package/dist/request-logs/request-logs.d.ts.map +1 -0
  77. package/dist/request-logs/request-logs.js +260 -0
  78. package/dist/request-logs/request-logs.js.map +1 -0
  79. package/dist/schema.d.ts +3 -0
  80. package/dist/schema.d.ts.map +1 -0
  81. package/dist/schema.js +21 -0
  82. package/dist/schema.js.map +1 -0
  83. package/dist/sqlite-driver.d.ts +16 -0
  84. package/dist/sqlite-driver.d.ts.map +1 -0
  85. package/dist/sqlite-driver.js +15 -0
  86. package/dist/sqlite-driver.js.map +1 -0
  87. package/dist/sqlite-store.d.ts +12 -0
  88. package/dist/sqlite-store.d.ts.map +1 -0
  89. package/dist/sqlite-store.js +176 -0
  90. package/dist/sqlite-store.js.map +1 -0
  91. package/dist/types.d.ts +67 -0
  92. package/dist/types.d.ts.map +1 -0
  93. package/dist/types.js +9 -0
  94. package/dist/types.js.map +1 -0
  95. package/dist/usage/adapters.d.ts +31 -0
  96. package/dist/usage/adapters.d.ts.map +1 -0
  97. package/dist/usage/adapters.js +37 -0
  98. package/dist/usage/adapters.js.map +1 -0
  99. package/dist/usage/http-routes.d.ts +23 -0
  100. package/dist/usage/http-routes.d.ts.map +1 -0
  101. package/dist/usage/http-routes.js +150 -0
  102. package/dist/usage/http-routes.js.map +1 -0
  103. package/dist/usage/index.d.ts +11 -0
  104. package/dist/usage/index.d.ts.map +1 -0
  105. package/dist/usage/index.js +7 -0
  106. package/dist/usage/index.js.map +1 -0
  107. package/dist/usage/ui-brand.d.ts +23 -0
  108. package/dist/usage/ui-brand.d.ts.map +1 -0
  109. package/dist/usage/ui-brand.js +18 -0
  110. package/dist/usage/ui-brand.js.map +1 -0
  111. package/dist/usage/usage-analytics-productivity.d.ts +73 -0
  112. package/dist/usage/usage-analytics-productivity.d.ts.map +1 -0
  113. package/dist/usage/usage-analytics-productivity.js +317 -0
  114. package/dist/usage/usage-analytics-productivity.js.map +1 -0
  115. package/dist/usage/usage-analytics-shared.d.ts +5 -0
  116. package/dist/usage/usage-analytics-shared.d.ts.map +1 -0
  117. package/dist/usage/usage-analytics-shared.js +20 -0
  118. package/dist/usage/usage-analytics-shared.js.map +1 -0
  119. package/dist/usage/usage-analytics.d.ts +127 -0
  120. package/dist/usage/usage-analytics.d.ts.map +1 -0
  121. package/dist/usage/usage-analytics.js +338 -0
  122. package/dist/usage/usage-analytics.js.map +1 -0
  123. package/dist-cjs/api-endpoints/http-routes.js +14 -0
  124. package/dist-cjs/api-endpoints/http-routes.js.map +1 -0
  125. package/dist-cjs/api-endpoints/index.js +10 -0
  126. package/dist-cjs/api-endpoints/index.js.map +1 -0
  127. package/dist-cjs/api-endpoints/registry.js +107 -0
  128. package/dist-cjs/api-endpoints/registry.js.map +1 -0
  129. package/dist-cjs/api-mount.js +117 -0
  130. package/dist-cjs/api-mount.js.map +1 -0
  131. package/dist-cjs/helpers.js +41 -0
  132. package/dist-cjs/helpers.js.map +1 -0
  133. package/dist-cjs/index.js +118 -0
  134. package/dist-cjs/index.js.map +1 -0
  135. package/dist-cjs/memory-store.js +119 -0
  136. package/dist-cjs/memory-store.js.map +1 -0
  137. package/dist-cjs/ops/emit.js +18 -0
  138. package/dist-cjs/ops/emit.js.map +1 -0
  139. package/dist-cjs/ops/fleet-activity.js +131 -0
  140. package/dist-cjs/ops/fleet-activity.js.map +1 -0
  141. package/dist-cjs/ops/fleet-agent.js +358 -0
  142. package/dist-cjs/ops/fleet-agent.js.map +1 -0
  143. package/dist-cjs/ops/fleet-samples.js +229 -0
  144. package/dist-cjs/ops/fleet-samples.js.map +1 -0
  145. package/dist-cjs/ops/journal.js +326 -0
  146. package/dist-cjs/ops/journal.js.map +1 -0
  147. package/dist-cjs/ops/rules.js +102 -0
  148. package/dist-cjs/ops/rules.js.map +1 -0
  149. package/dist-cjs/ops/types.js +126 -0
  150. package/dist-cjs/ops/types.js.map +1 -0
  151. package/dist-cjs/package.json +3 -0
  152. package/dist-cjs/request-logs/config.js +42 -0
  153. package/dist-cjs/request-logs/config.js.map +1 -0
  154. package/dist-cjs/request-logs/http-routes.js +32 -0
  155. package/dist-cjs/request-logs/http-routes.js.map +1 -0
  156. package/dist-cjs/request-logs/index.js +27 -0
  157. package/dist-cjs/request-logs/index.js.map +1 -0
  158. package/dist-cjs/request-logs/middleware.js +203 -0
  159. package/dist-cjs/request-logs/middleware.js.map +1 -0
  160. package/dist-cjs/request-logs/request-logs.js +277 -0
  161. package/dist-cjs/request-logs/request-logs.js.map +1 -0
  162. package/dist-cjs/schema.js +24 -0
  163. package/dist-cjs/schema.js.map +1 -0
  164. package/dist-cjs/sqlite-driver.js +18 -0
  165. package/dist-cjs/sqlite-driver.js.map +1 -0
  166. package/dist-cjs/sqlite-store.js +182 -0
  167. package/dist-cjs/sqlite-store.js.map +1 -0
  168. package/dist-cjs/types.js +12 -0
  169. package/dist-cjs/types.js.map +1 -0
  170. package/dist-cjs/usage/adapters.js +47 -0
  171. package/dist-cjs/usage/adapters.js.map +1 -0
  172. package/dist-cjs/usage/http-routes.js +154 -0
  173. package/dist-cjs/usage/http-routes.js.map +1 -0
  174. package/dist-cjs/usage/index.js +34 -0
  175. package/dist-cjs/usage/index.js.map +1 -0
  176. package/dist-cjs/usage/ui-brand.js +23 -0
  177. package/dist-cjs/usage/ui-brand.js.map +1 -0
  178. package/dist-cjs/usage/usage-analytics-productivity.js +321 -0
  179. package/dist-cjs/usage/usage-analytics-productivity.js.map +1 -0
  180. package/dist-cjs/usage/usage-analytics-shared.js +23 -0
  181. package/dist-cjs/usage/usage-analytics-shared.js.map +1 -0
  182. package/dist-cjs/usage/usage-analytics.js +353 -0
  183. package/dist-cjs/usage/usage-analytics.js.map +1 -0
  184. package/fleet-collector/AGENTS.md +7 -0
  185. package/fleet-collector/README.md +83 -0
  186. package/fleet-collector/configure-fleet-npm.sh +108 -0
  187. package/fleet-collector/env.mjs +174 -0
  188. package/fleet-collector/ops-api.mjs +455 -0
  189. package/fleet-collector/public/admin.html +696 -0
  190. package/fleet-collector/public/index.html +941 -0
  191. package/fleet-collector/server.mjs +564 -0
  192. package/fleet-collector/test-fleet-collector.mjs +413 -0
  193. package/fleet-collector/test-server-admin.mjs +475 -0
  194. package/package.json +109 -0
  195. package/scripts/test-api-endpoints-registry.mjs +60 -0
  196. package/ui/analytics-client.tsx +834 -0
  197. package/ui/analytics-productivity-panel.tsx +513 -0
  198. package/ui/api-endpoints-client.tsx +228 -0
  199. package/ui/index.ts +39 -0
  200. package/ui/primitives/badge.tsx +37 -0
  201. package/ui/primitives/button.tsx +44 -0
  202. package/ui/primitives/card.tsx +44 -0
  203. package/ui/primitives/cn.ts +6 -0
  204. package/ui/primitives/input.tsx +19 -0
  205. package/ui/primitives/tabs.tsx +51 -0
  206. package/ui/request-logs-client.tsx +327 -0
  207. package/ui/session-usage-analytics-provider.tsx +28 -0
  208. package/ui/usage-analytics-client.ts +479 -0
  209. package/ui/usage-analytics-provider.tsx +84 -0
@@ -0,0 +1,353 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatDuration = void 0;
4
+ exports.usageAnalyticsReady = usageAnalyticsReady;
5
+ exports.ensureUsageAnalyticsSchema = ensureUsageAnalyticsSchema;
6
+ exports.insertUsageEvents = insertUsageEvents;
7
+ exports.recordUsageEvent = recordUsageEvent;
8
+ exports.resolvePeriodFilters = resolvePeriodFilters;
9
+ exports.getUsageOverview = getUsageOverview;
10
+ exports.getUsageTimeline = getUsageTimeline;
11
+ exports.getTopPages = getTopPages;
12
+ exports.getTopClicks = getTopClicks;
13
+ exports.getUserStats = getUserStats;
14
+ exports.listUsageEvents = listUsageEvents;
15
+ exports.purgeUsageEvents = purgeUsageEvents;
16
+ /**
17
+ * Analytics d'usage — persistance SQLite + agrégations Admin.
18
+ * Événements UI (pages, clics, dwell) + actions serveur (agent IA…).
19
+ */
20
+ const adapters_js_1 = require("./adapters.js");
21
+ const usage_analytics_shared_js_1 = require("./usage-analytics-shared.js");
22
+ Object.defineProperty(exports, "formatDuration", { enumerable: true, get: function () { return usage_analytics_shared_js_1.formatDuration; } });
23
+ let schemaReady = false;
24
+ function usageAnalyticsReady() {
25
+ return (0, adapters_js_1.uaTableExists)("usage_events");
26
+ }
27
+ /** Crée la table si absente (dev / avant migration Electron). */
28
+ function ensureUsageAnalyticsSchema() {
29
+ if (schemaReady && usageAnalyticsReady())
30
+ return;
31
+ const db = (0, adapters_js_1.uaGetWriteDb)();
32
+ db.exec(`
33
+ CREATE TABLE IF NOT EXISTS usage_events (
34
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
35
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
36
+ event_type TEXT NOT NULL,
37
+ category TEXT NOT NULL DEFAULT 'ui',
38
+ label TEXT,
39
+ path TEXT,
40
+ referrer_path TEXT,
41
+ user_id TEXT,
42
+ username TEXT,
43
+ user_kind TEXT NOT NULL DEFAULT 'human',
44
+ user_role TEXT,
45
+ session_id TEXT,
46
+ duration_ms INTEGER,
47
+ meta_json TEXT,
48
+ user_agent TEXT,
49
+ surface TEXT NOT NULL DEFAULT 'crm'
50
+ );
51
+ CREATE INDEX IF NOT EXISTS idx_usage_events_created
52
+ ON usage_events(created_at);
53
+ CREATE INDEX IF NOT EXISTS idx_usage_events_user_created
54
+ ON usage_events(user_id, created_at);
55
+ CREATE INDEX IF NOT EXISTS idx_usage_events_type_created
56
+ ON usage_events(event_type, created_at);
57
+ CREATE INDEX IF NOT EXISTS idx_usage_events_path_created
58
+ ON usage_events(path, created_at);
59
+ CREATE INDEX IF NOT EXISTS idx_usage_events_kind_created
60
+ ON usage_events(user_kind, created_at);
61
+ CREATE INDEX IF NOT EXISTS idx_usage_events_session
62
+ ON usage_events(session_id, created_at);
63
+ `);
64
+ schemaReady = true;
65
+ }
66
+ function normalizeKind(v) {
67
+ const s = String(v || "").toLowerCase();
68
+ if (s === "ai")
69
+ return "ai";
70
+ if (s === "system")
71
+ return "system";
72
+ if (s === "human")
73
+ return "human";
74
+ return "unknown";
75
+ }
76
+ function clampStr(v, max) {
77
+ if (v == null)
78
+ return null;
79
+ const s = String(v).trim();
80
+ if (!s)
81
+ return null;
82
+ return s.slice(0, max);
83
+ }
84
+ function periodBounds(period, now = new Date()) {
85
+ const to = now.toISOString();
86
+ const d = new Date(now);
87
+ if (period === "day")
88
+ d.setUTCDate(d.getUTCDate() - 1);
89
+ else if (period === "week")
90
+ d.setUTCDate(d.getUTCDate() - 7);
91
+ else if (period === "month")
92
+ d.setUTCDate(d.getUTCDate() - 30);
93
+ else
94
+ d.setUTCFullYear(d.getUTCFullYear() - 1);
95
+ return { from: d.toISOString(), to };
96
+ }
97
+ function buildWhere(filters) {
98
+ const parts = ["1=1"];
99
+ const params = [];
100
+ if (filters.from) {
101
+ parts.push("created_at >= ?");
102
+ params.push(filters.from);
103
+ }
104
+ if (filters.to) {
105
+ parts.push("created_at <= ?");
106
+ params.push(filters.to);
107
+ }
108
+ if (filters.userId) {
109
+ parts.push("user_id = ?");
110
+ params.push(filters.userId);
111
+ }
112
+ if (filters.kind && filters.kind !== "all") {
113
+ parts.push("user_kind = ?");
114
+ params.push(filters.kind);
115
+ }
116
+ if (filters.eventType) {
117
+ parts.push("event_type = ?");
118
+ params.push(filters.eventType);
119
+ }
120
+ if (filters.path) {
121
+ parts.push("path LIKE ?");
122
+ params.push(`${filters.path}%`);
123
+ }
124
+ if (filters.q) {
125
+ parts.push("(label LIKE ? OR path LIKE ? OR username LIKE ? OR event_type LIKE ?)");
126
+ const like = `%${filters.q}%`;
127
+ params.push(like, like, like, like);
128
+ }
129
+ return { sql: parts.join(" AND "), params };
130
+ }
131
+ function insertUsageEvents(events) {
132
+ if (!events.length)
133
+ return 0;
134
+ ensureUsageAnalyticsSchema();
135
+ const db = (0, adapters_js_1.uaGetWriteDb)();
136
+ const stmt = db.prepare(`
137
+ INSERT INTO usage_events (
138
+ created_at, event_type, category, label, path, referrer_path,
139
+ user_id, username, user_kind, user_role, session_id,
140
+ duration_ms, meta_json, user_agent, surface
141
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
142
+ `);
143
+ const now = new Date().toISOString();
144
+ let inserted = 0;
145
+ const insertRows = (rows) => {
146
+ for (const e of rows) {
147
+ const eventType = clampStr(e.eventType, 80);
148
+ if (!eventType)
149
+ continue;
150
+ const duration = typeof e.durationMs === "number" && Number.isFinite(e.durationMs) && e.durationMs >= 0
151
+ ? Math.min(Math.round(e.durationMs), 86_400_000)
152
+ : null;
153
+ stmt.run(clampStr(e.createdAt, 40) || now, eventType, clampStr(e.category, 40) || "ui", clampStr(e.label, 240), clampStr(e.path, 500), clampStr(e.referrerPath, 500), clampStr(e.userId, 80), clampStr(e.username, 120), normalizeKind(e.userKind), clampStr(e.userRole, 40), clampStr(e.sessionId, 80), duration, e.meta ? JSON.stringify(e.meta).slice(0, 4000) : null, clampStr(e.userAgent, 300), clampStr(e.surface, 40) || "crm");
154
+ inserted += 1;
155
+ }
156
+ };
157
+ if (db.transaction)
158
+ db.transaction(insertRows)(events);
159
+ else
160
+ insertRows(events);
161
+ return inserted;
162
+ }
163
+ function recordUsageEvent(event) {
164
+ try {
165
+ insertUsageEvents([event]);
166
+ }
167
+ catch {
168
+ /* ne jamais casser le flux métier */
169
+ }
170
+ }
171
+ function resolvePeriodFilters(period, from, to) {
172
+ const p = ["day", "week", "month", "year"].includes(period)
173
+ ? period
174
+ : "week";
175
+ if (from || to) {
176
+ return { from: from || undefined, to: to || undefined };
177
+ }
178
+ return periodBounds(p);
179
+ }
180
+ function getUsageOverview(filters) {
181
+ ensureUsageAnalyticsSchema();
182
+ const { sql, params } = buildWhere(filters);
183
+ const totals = (0, adapters_js_1.uaQueryOne)(`SELECT
184
+ COUNT(*) AS events,
185
+ SUM(CASE WHEN event_type = 'page.view' THEN 1 ELSE 0 END) AS page_views,
186
+ SUM(CASE WHEN event_type = 'ui.click' THEN 1 ELSE 0 END) AS clicks,
187
+ COUNT(DISTINCT session_id) AS sessions,
188
+ COUNT(DISTINCT user_id) AS active_users,
189
+ COUNT(DISTINCT CASE WHEN user_kind = 'human' THEN user_id END) AS active_humans,
190
+ COUNT(DISTINCT CASE WHEN user_kind = 'ai' THEN user_id END) AS active_ai,
191
+ COALESCE(SUM(CASE WHEN duration_ms IS NOT NULL THEN duration_ms ELSE 0 END), 0) AS total_time_ms
192
+ FROM usage_events
193
+ WHERE ${sql}`, params);
194
+ const byKind = (0, adapters_js_1.uaQueryAll)(`SELECT
195
+ user_kind AS kind,
196
+ COUNT(*) AS events,
197
+ COUNT(DISTINCT user_id) AS users,
198
+ COALESCE(SUM(CASE WHEN duration_ms IS NOT NULL THEN duration_ms ELSE 0 END), 0) AS time_ms
199
+ FROM usage_events
200
+ WHERE ${sql}
201
+ GROUP BY user_kind
202
+ ORDER BY events DESC`, params);
203
+ const sessions = totals?.sessions ?? 0;
204
+ const totalTimeMs = totals?.total_time_ms ?? 0;
205
+ return {
206
+ period: { from: filters.from || "", to: filters.to || "" },
207
+ totals: {
208
+ events: totals?.events ?? 0,
209
+ pageViews: totals?.page_views ?? 0,
210
+ clicks: totals?.clicks ?? 0,
211
+ sessions,
212
+ activeUsers: totals?.active_users ?? 0,
213
+ activeHumans: totals?.active_humans ?? 0,
214
+ activeAi: totals?.active_ai ?? 0,
215
+ totalTimeMs,
216
+ avgSessionTimeMs: sessions > 0 ? Math.round(totalTimeMs / sessions) : 0,
217
+ },
218
+ byKind: byKind.map((r) => ({
219
+ kind: r.kind,
220
+ events: r.events,
221
+ users: r.users,
222
+ timeMs: r.time_ms,
223
+ })),
224
+ };
225
+ }
226
+ function bucketExpr(period) {
227
+ if (period === "day")
228
+ return `strftime('%Y-%m-%d %H:00', created_at)`;
229
+ if (period === "year")
230
+ return `strftime('%Y-%m', created_at)`;
231
+ return `strftime('%Y-%m-%d', created_at)`;
232
+ }
233
+ function getUsageTimeline(filters, period = "week") {
234
+ ensureUsageAnalyticsSchema();
235
+ const { sql, params } = buildWhere(filters);
236
+ const bucket = bucketExpr(period);
237
+ return (0, adapters_js_1.uaQueryAll)(`SELECT
238
+ ${bucket} AS bucket,
239
+ COUNT(*) AS events,
240
+ SUM(CASE WHEN event_type = 'page.view' THEN 1 ELSE 0 END) AS page_views,
241
+ SUM(CASE WHEN event_type = 'ui.click' THEN 1 ELSE 0 END) AS clicks,
242
+ SUM(CASE WHEN user_kind = 'human' THEN 1 ELSE 0 END) AS humans,
243
+ SUM(CASE WHEN user_kind = 'ai' THEN 1 ELSE 0 END) AS ai,
244
+ COALESCE(SUM(CASE WHEN duration_ms IS NOT NULL THEN duration_ms ELSE 0 END), 0) AS time_ms
245
+ FROM usage_events
246
+ WHERE ${sql}
247
+ GROUP BY bucket
248
+ ORDER BY bucket ASC`, params).map((r) => ({
249
+ bucket: r.bucket,
250
+ events: r.events,
251
+ pageViews: r.page_views,
252
+ clicks: r.clicks,
253
+ humans: r.humans,
254
+ ai: r.ai,
255
+ timeMs: r.time_ms,
256
+ }));
257
+ }
258
+ function getTopPages(filters, limit = 25) {
259
+ ensureUsageAnalyticsSchema();
260
+ const { sql, params } = buildWhere(filters);
261
+ return (0, adapters_js_1.uaQueryAll)(`SELECT
262
+ COALESCE(path, '(inconnu)') AS path,
263
+ SUM(CASE WHEN event_type = 'page.view' THEN 1 ELSE 0 END) AS views,
264
+ COUNT(DISTINCT user_id) AS unique_users,
265
+ COALESCE(AVG(CASE WHEN event_type IN ('page.hide','page.dwell') AND duration_ms IS NOT NULL THEN duration_ms END), 0) AS avg_time_ms,
266
+ COALESCE(SUM(CASE WHEN duration_ms IS NOT NULL THEN duration_ms ELSE 0 END), 0) AS total_time_ms
267
+ FROM usage_events
268
+ WHERE ${sql} AND path IS NOT NULL AND path != ''
269
+ GROUP BY path
270
+ HAVING views > 0
271
+ ORDER BY views DESC
272
+ LIMIT ?`, [...params, Math.min(Math.max(limit, 1), 200)]).map((r) => ({
273
+ path: r.path,
274
+ views: r.views,
275
+ uniqueUsers: r.unique_users,
276
+ avgTimeMs: Math.round(r.avg_time_ms || 0),
277
+ totalTimeMs: r.total_time_ms,
278
+ }));
279
+ }
280
+ function getTopClicks(filters, limit = 25) {
281
+ ensureUsageAnalyticsSchema();
282
+ const { sql, params } = buildWhere(filters);
283
+ return (0, adapters_js_1.uaQueryAll)(`SELECT
284
+ COALESCE(NULLIF(TRIM(label), ''), '(sans label)') AS label,
285
+ COUNT(*) AS clicks,
286
+ COUNT(DISTINCT user_id) AS unique_users,
287
+ COUNT(DISTINCT path) AS paths
288
+ FROM usage_events
289
+ WHERE ${sql} AND event_type = 'ui.click'
290
+ GROUP BY label
291
+ ORDER BY clicks DESC
292
+ LIMIT ?`, [...params, Math.min(Math.max(limit, 1), 200)]).map((r) => ({
293
+ label: r.label,
294
+ clicks: r.clicks,
295
+ uniqueUsers: r.unique_users,
296
+ paths: r.paths,
297
+ }));
298
+ }
299
+ function getUserStats(filters, limit = 50) {
300
+ ensureUsageAnalyticsSchema();
301
+ const { sql, params } = buildWhere(filters);
302
+ return (0, adapters_js_1.uaQueryAll)(`SELECT
303
+ COALESCE(user_id, 'anonymous') AS user_id,
304
+ COALESCE(MAX(username), user_id, 'anonymous') AS username,
305
+ MAX(user_kind) AS kind,
306
+ MAX(user_role) AS role,
307
+ COUNT(*) AS events,
308
+ SUM(CASE WHEN event_type = 'page.view' THEN 1 ELSE 0 END) AS page_views,
309
+ SUM(CASE WHEN event_type = 'ui.click' THEN 1 ELSE 0 END) AS clicks,
310
+ COUNT(DISTINCT session_id) AS sessions,
311
+ COALESCE(SUM(CASE WHEN duration_ms IS NOT NULL THEN duration_ms ELSE 0 END), 0) AS time_ms,
312
+ MAX(created_at) AS last_seen
313
+ FROM usage_events
314
+ WHERE ${sql}
315
+ GROUP BY COALESCE(user_id, 'anonymous')
316
+ ORDER BY events DESC
317
+ LIMIT ?`, [...params, Math.min(Math.max(limit, 1), 200)]).map((r) => ({
318
+ userId: r.user_id,
319
+ username: r.username,
320
+ kind: r.kind,
321
+ role: r.role,
322
+ events: r.events,
323
+ pageViews: r.page_views,
324
+ clicks: r.clicks,
325
+ sessions: r.sessions,
326
+ timeMs: r.time_ms,
327
+ lastSeen: r.last_seen,
328
+ }));
329
+ }
330
+ function listUsageEvents(filters) {
331
+ ensureUsageAnalyticsSchema();
332
+ const { sql, params } = buildWhere(filters);
333
+ const limit = Math.min(Math.max(filters.limit ?? 100, 1), 500);
334
+ const offset = Math.max(filters.offset ?? 0, 0);
335
+ const total = (0, adapters_js_1.uaQueryOne)(`SELECT COUNT(*) AS c FROM usage_events WHERE ${sql}`, params)?.c ??
336
+ 0;
337
+ const events = (0, adapters_js_1.uaQueryAll)(`SELECT * FROM usage_events WHERE ${sql} ORDER BY created_at DESC, id DESC LIMIT ? OFFSET ?`, [...params, limit, offset]);
338
+ return { events, total };
339
+ }
340
+ function purgeUsageEvents(opts = {}) {
341
+ ensureUsageAnalyticsSchema();
342
+ const db = (0, adapters_js_1.uaGetWriteDb)();
343
+ if (opts.all) {
344
+ const info = db.prepare(`DELETE FROM usage_events`).run();
345
+ return info.changes;
346
+ }
347
+ if (opts.before) {
348
+ const info = db.prepare(`DELETE FROM usage_events WHERE created_at < ?`).run(opts.before);
349
+ return info.changes;
350
+ }
351
+ return 0;
352
+ }
353
+ //# sourceMappingURL=usage-analytics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage-analytics.js","sourceRoot":"","sources":["../../src/usage/usage-analytics.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ # AGENTS — observability/fleet-collector
2
+
3
+ Service collector flotte (bin `creezio-fleet-collector`).
4
+
5
+ - Config / env : voir [README.md](./README.md)
6
+ - Package parent : [../README.md](../README.md) · [../AGENTS.md](../AGENTS.md)
7
+ - Ne pas coupler au métier d’une marque ; payloads anonymisés / agrégats
@@ -0,0 +1,83 @@
1
+ # Fleet Collector — `@creezio/observability`
2
+
3
+ Service ops autonome en Node pur (loopback HTTP + store JSON), **SoT kit**
4
+ — 0 domaine marque hardcodé. Le dossier porte le collector télémétrie :
5
+
6
+ | Fichier | Service | Câblé en prod |
7
+ |---|---|---|
8
+ | `server.mjs` (+ `ops-api.mjs`, `env.mjs`) | Collector télémétrie (ingest heartbeat/crash/bundle + cockpit ops) — **SoT ici** | Service dédié (ex. legacy `:8665`) |
9
+
10
+ **Backend flotte / agent hôte** : la SoT vit dans
11
+ [`packages/fleet`](../../fleet/README.md) (`@creezio/fleet`, TS strict —
12
+ P2.b 0.15.0). Les 7 wrappers `.mjs` de compat de ce dossier et le bin npm
13
+ `creezio-server-admin` ont été **retirés en 0.19.0**. Les images embarquent
14
+ `packages/fleet/dist` (CMD `node_modules/@creezio/fleet/dist/bin/*-main.js`,
15
+ contexte stagé fail-closed).
16
+
17
+ **Piège prod** : le code flotte est **copié dans les images** au build —
18
+ après toute modif de `packages/fleet/src`, `npm run build:packages` puis
19
+ re-runner `creezio server-docker admin up …` / `agent up …` (sinon nouvelles
20
+ routes → 404 silencieux).
21
+
22
+ ## Lancement
23
+
24
+ ```bash
25
+ # Depuis le kit
26
+ CREEZIO_FLEET_INGEST_TOKEN=… \
27
+ CREEZIO_FLEET_OPS_USER=ops \
28
+ CREEZIO_FLEET_OPS_PASS=… \
29
+ CREEZIO_FLEET_UI_TITLE="TempoFlow Fleet" \
30
+ CREEZIO_FLEET_TUNNEL_SUFFIX=tempoflow.fr \
31
+ FLEET_PUBLIC_DOMAIN=fleet.tempoflow.fr \
32
+ npm run fleet-collector -w @creezio/observability
33
+
34
+ # Ou binaire
35
+ npx creezio-fleet-collector
36
+ ```
37
+
38
+ Marques (package npm installé) :
39
+
40
+ ```bash
41
+ node node_modules/@creezio/observability/fleet-collector/server.mjs
42
+ ```
43
+
44
+ ## Env (neutre + dual-read legacy)
45
+
46
+ | Variable | Alias legacy | Rôle |
47
+ |----------|--------------|------|
48
+ | `CREEZIO_FLEET_PORT` / `FLEET_PORT` | `TF2_FLEET_PORT`, `CERTIVAN_FLEET_PORT` | Listen (défaut 8665) |
49
+ | `CREEZIO_FLEET_INGEST_TOKEN` / `FLEET_INGEST_TOKEN` | `TF2_*`, `CERTIVAN_*` | Path ingest `/i-<token>/…` |
50
+ | `CREEZIO_FLEET_OPS_USER` / `_PASS` / `_TOKEN` | idem | Auth ops Basic / Bearer |
51
+ | `CREEZIO_FLEET_DIR` / `FLEET_DIR` | idem | State JSON |
52
+ | `FLEET_PUBLIC_DOMAIN` / `CREEZIO_FLEET_DOMAIN` | — | Domaine public (docs / NPM) |
53
+ | `CREEZIO_FLEET_TUNNEL_SUFFIX` | — | Fallback hostname `${slug}.${suffix}` |
54
+ | `CREEZIO_FLEET_UI_TITLE` / `_MARK` / `_HOME_TITLE` / `_REALM` | — | Branding UI |
55
+ | `CREEZIO_FLEET_UI_EXTRAS_TITLE` | — | Carte extras (ex. « Dossiers VASP ») |
56
+ | `CREEZIO_FLEET_UI_ETAT_LABELS` | — | JSON labels états extras |
57
+
58
+ ## Endpoints
59
+
60
+ - Ingest : `POST /i-<token>/heartbeat|crash|bundle`, `GET/POST …/commands`
61
+ - Ops : `/` UI + `/ops/api/*` (Basic / Bearer)
62
+
63
+ ## Extras métier (marques)
64
+
65
+ Le collector accepte `dossierStats` opaque dans le heartbeat (agrégation + UI
66
+ si présent). L’émission métier reste marque — ex. Certivan
67
+ `electron/fleet-dossier-samples.ts` via `getHeartbeatExtras`.
68
+
69
+ ## Tests
70
+
71
+ ```bash
72
+ npm run test:fleet-collector -w @creezio/observability
73
+ # ou
74
+ node packages/observability/fleet-collector/test-fleet-collector.mjs
75
+ ```
76
+
77
+ ## DNS / NPM (VPS)
78
+
79
+ ```bash
80
+ FLEET_PUBLIC_DOMAIN=fleet.example.com FLEET_PORT=8665 \
81
+ CF_ENV=/path/to/.cloudflare.env \
82
+ bash packages/observability/fleet-collector/configure-fleet-npm.sh
83
+ ```
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env bash
2
+ # DNS Cloudflare + NPM proxy host pour le cockpit fleet (domaine injecté).
3
+ #
4
+ # Requis :
5
+ # FLEET_PUBLIC_DOMAIN (ex. fleet.example.com)
6
+ # FLEET_PORT (ex. 8665)
7
+ # CF_ENV chemin .env Cloudflare (token + zone)
8
+ # Optionnel :
9
+ # NPM_CONTAINER, NPM_API, WP_ENV, PROV_SCRIPTS, VPS_IP, FORWARD_HOST
10
+ set -euo pipefail
11
+
12
+ : "${FLEET_PUBLIC_DOMAIN:?set FLEET_PUBLIC_DOMAIN}"
13
+ : "${FLEET_PORT:?set FLEET_PORT}"
14
+ : "${CF_ENV:?set CF_ENV (cloudflare env file)}"
15
+
16
+ NPM_CONTAINER="${NPM_CONTAINER:-nginx-proxy-manager}"
17
+ NPM_API="${NPM_API:-http://127.0.0.1:81/api}"
18
+ WP_ENV="${WP_ENV:-/opt/docker/wp-provisioner/.env}"
19
+ PROV_SCRIPTS="${PROV_SCRIPTS:-/opt/docker/wp-provisioner/scripts}"
20
+ CF_API="https://api.cloudflare.com/client/v4"
21
+
22
+ DOMAIN="$FLEET_PUBLIC_DOMAIN"
23
+ PORT="$FLEET_PORT"
24
+ FORWARD_HOST="${FORWARD_HOST:-127.0.0.1}"
25
+ VPS_IP="${VPS_IP:-104.168.10.36}"
26
+
27
+ set -a; . "$WP_ENV"; . "$CF_ENV"; set +a
28
+ : "${NPM_EMAIL:?}"; : "${NPM_PASSWORD:?}"; : "${CF_API_TOKEN:?}"; : "${CF_ZONE_ID:?}"
29
+
30
+ echo "==> DNS A $DOMAIN → $VPS_IP (proxied)"
31
+ EXIST_DNS=$(curl -sS -H "Authorization: Bearer $CF_API_TOKEN" \
32
+ "$CF_API/zones/$CF_ZONE_ID/dns_records?name=$DOMAIN&type=A" | jq -r '.result[0].id // empty')
33
+ DNS_BODY=$(jq -nc --arg n "$DOMAIN" --arg ip "$VPS_IP" \
34
+ '{type:"A",name:$n,content:$ip,ttl:1,proxied:true,comment:"Creezio fleet cockpit"}')
35
+ if [ -n "$EXIST_DNS" ]; then
36
+ curl -sS -X PUT -H "Authorization: Bearer $CF_API_TOKEN" -H "Content-Type: application/json" \
37
+ -d "$DNS_BODY" "$CF_API/zones/$CF_ZONE_ID/dns_records/$EXIST_DNS" | jq -e '.success==true' >/dev/null
38
+ echo " DNS mis à jour ($EXIST_DNS)"
39
+ else
40
+ curl -sS -X POST -H "Authorization: Bearer $CF_API_TOKEN" -H "Content-Type: application/json" \
41
+ -d "$DNS_BODY" "$CF_API/zones/$CF_ZONE_ID/dns_records" | jq -e '.success==true' >/dev/null
42
+ echo " DNS créé"
43
+ fi
44
+
45
+ echo "==> Authentification NPM"
46
+ TOKEN=$(docker exec "$NPM_CONTAINER" curl -sS -X POST "$NPM_API/tokens" \
47
+ -H "Content-Type: application/json" \
48
+ -d "$(jq -nc --arg i "$NPM_EMAIL" --arg s "$NPM_PASSWORD" '{identity:$i,secret:$s}')" \
49
+ | sed -n 's/.*"token":"\([^"]*\)".*/\1/p')
50
+ [ -n "$TOKEN" ] || { echo "ERREUR: token NPM vide"; exit 1; }
51
+
52
+ echo "==> Proxy host $DOMAIN -> $FORWARD_HOST:$PORT"
53
+ EXIST=$(docker exec "$NPM_CONTAINER" curl -sS -H "Authorization: Bearer $TOKEN" "$NPM_API/nginx/proxy-hosts" \
54
+ | jq -r --arg d "$DOMAIN" '(if type=="array" then . else [] end)[]|select((.domain_names//[])|index($d)!=null)|.id' | head -1)
55
+ if [ -z "$EXIST" ] || [ "$EXIST" = "null" ]; then
56
+ PAYLOAD=$(jq -nc --arg d "$DOMAIN" --arg fh "$FORWARD_HOST" --argjson p "$PORT" \
57
+ '{domain_names:[$d],forward_scheme:"http",forward_host:$fh,forward_port:$p,
58
+ certificate_id:0,ssl_forced:false,hsts_enabled:false,
59
+ hsts_subdomains:false,http2_support:true,block_exploits:true,caching_enabled:false,
60
+ allow_websocket_upgrade:true,advanced_config:"",enabled:true,
61
+ meta:{letsencrypt_agree:false,dns_challenge:false},locations:[]}')
62
+ HOST_ID=$(docker exec "$NPM_CONTAINER" curl -sS -X POST "$NPM_API/nginx/proxy-hosts" \
63
+ -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d "$PAYLOAD" | jq -r '.id // empty')
64
+ [ -n "$HOST_ID" ] || { echo "ERREUR: création proxy host échouée"; exit 1; }
65
+ echo " proxy host créé (id=$HOST_ID)"
66
+ else
67
+ HOST_ID="$EXIST"
68
+ echo " proxy host déjà présent (id=$HOST_ID)"
69
+ fi
70
+
71
+ echo "==> Certificat Cloudflare Origin pour $DOMAIN"
72
+ TMP=$(mktemp -d); trap 'rm -rf "$TMP"' EXIT
73
+ cat > "$TMP/openssl.cnf" <<EOF
74
+ [ req ]
75
+ default_bits = 2048
76
+ prompt = no
77
+ default_md = sha256
78
+ distinguished_name = dn
79
+ req_extensions = v3_req
80
+ [ dn ]
81
+ CN = $DOMAIN
82
+ [ v3_req ]
83
+ subjectAltName = @alt_names
84
+ [ alt_names ]
85
+ DNS.1 = $DOMAIN
86
+ EOF
87
+ openssl req -new -newkey rsa:2048 -nodes -keyout "$TMP/origin.key" -out "$TMP/origin.csr" -config "$TMP/openssl.cnf" >/dev/null 2>&1
88
+ CF_BODY=$(jq -n --rawfile csr "$TMP/origin.csr" --arg d "$DOMAIN" \
89
+ '{hostnames:[$d],requested_validity:5475,request_type:"origin-rsa",csr:$csr,requested_bundle:"false"}')
90
+ CF_RESP=$(curl -sS -X POST "$CF_API/certificates" -H "Authorization: Bearer $CF_API_TOKEN" -H "Content-Type: application/json" -d "$CF_BODY")
91
+ echo "$CF_RESP" | jq -e '.success==true' >/dev/null 2>&1 || { echo "ERREUR Cloudflare Origin CA: $(echo "$CF_RESP" | jq -c '.errors // .' | head -c 600)"; exit 1; }
92
+ CERT_PEM=$(echo "$CF_RESP" | jq -r '.result.certificate')
93
+
94
+ echo "==> Upload TLS NPM"
95
+ export DOM="$DOMAIN"
96
+ export FRONT_DOM=""
97
+ export FRONT_DEV_DOM=""
98
+ export EMAIL="$NPM_EMAIL"
99
+ export PASS="$NPM_PASSWORD"
100
+ export CERT_B64="$(printf '%s' "$CERT_PEM" | base64 | tr -d '\n')"
101
+ export KEY_B64="$(base64 < "$TMP/origin.key" | tr -d '\n')"
102
+ RESULT=$(bash "$PROV_SCRIPTS/n8n-npm-tls-install.sh" 2>&1 | grep -E '^\{.*\}' | tail -1)
103
+ echo " $RESULT"
104
+ echo "$RESULT" | jq -e '.ok==true' >/dev/null 2>&1 || { echo "ERREUR install TLS NPM"; exit 1; }
105
+
106
+ echo "=================================================="
107
+ echo " OK -> https://$DOMAIN/"
108
+ echo "=================================================="