@ebowwa/crm 0.1.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.
- package/README.md +174 -0
- package/dist/cli/commands/activities.d.ts +11 -0
- package/dist/cli/commands/activities.d.ts.map +1 -0
- package/dist/cli/commands/activities.js +427 -0
- package/dist/cli/commands/activities.js.map +1 -0
- package/dist/cli/commands/contacts.d.ts +11 -0
- package/dist/cli/commands/contacts.d.ts.map +1 -0
- package/dist/cli/commands/contacts.js +458 -0
- package/dist/cli/commands/contacts.js.map +1 -0
- package/dist/cli/commands/deals.d.ts +11 -0
- package/dist/cli/commands/deals.d.ts.map +1 -0
- package/dist/cli/commands/deals.js +498 -0
- package/dist/cli/commands/deals.js.map +1 -0
- package/dist/cli/commands/media.d.ts +11 -0
- package/dist/cli/commands/media.d.ts.map +1 -0
- package/dist/cli/commands/media.js +417 -0
- package/dist/cli/commands/media.js.map +1 -0
- package/dist/cli/commands/search.d.ts +11 -0
- package/dist/cli/commands/search.d.ts.map +1 -0
- package/dist/cli/commands/search.js +346 -0
- package/dist/cli/commands/search.js.map +1 -0
- package/dist/cli/index.d.ts +13 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +173 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/repl.d.ts +15 -0
- package/dist/cli/repl.d.ts.map +1 -0
- package/dist/cli/repl.js +318 -0
- package/dist/cli/repl.js.map +1 -0
- package/dist/cli/utils/config.d.ts +91 -0
- package/dist/cli/utils/config.d.ts.map +1 -0
- package/dist/cli/utils/config.js +212 -0
- package/dist/cli/utils/config.js.map +1 -0
- package/dist/cli/utils/output.d.ts +136 -0
- package/dist/cli/utils/output.d.ts.map +1 -0
- package/dist/cli/utils/output.js +323 -0
- package/dist/cli/utils/output.js.map +1 -0
- package/dist/cli/utils/prompt.d.ts +81 -0
- package/dist/cli/utils/prompt.d.ts.map +1 -0
- package/dist/cli/utils/prompt.js +341 -0
- package/dist/cli/utils/prompt.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +8 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +32 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/schemas.d.ts +3050 -0
- package/dist/core/schemas.d.ts.map +1 -0
- package/dist/core/schemas.js +667 -0
- package/dist/core/schemas.js.map +1 -0
- package/dist/core/types.d.ts +597 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +8 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +14 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +11 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/server.d.ts +13 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +18 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/storage/client.d.ts +109 -0
- package/dist/mcp/storage/client.d.ts.map +1 -0
- package/dist/mcp/storage/client.js +355 -0
- package/dist/mcp/storage/client.js.map +1 -0
- package/dist/mcp/storage/index.d.ts +7 -0
- package/dist/mcp/storage/index.d.ts.map +1 -0
- package/dist/mcp/storage/index.js +6 -0
- package/dist/mcp/storage/index.js.map +1 -0
- package/dist/mcp/storage/types.d.ts +44 -0
- package/dist/mcp/storage/types.d.ts.map +1 -0
- package/dist/mcp/storage/types.js +35 -0
- package/dist/mcp/storage/types.js.map +1 -0
- package/dist/mcp/tools/definitions.d.ts +16 -0
- package/dist/mcp/tools/definitions.d.ts.map +1 -0
- package/dist/mcp/tools/definitions.js +914 -0
- package/dist/mcp/tools/definitions.js.map +1 -0
- package/dist/mcp/tools/handlers.d.ts +50 -0
- package/dist/mcp/tools/handlers.d.ts.map +1 -0
- package/dist/mcp/tools/handlers.js +760 -0
- package/dist/mcp/tools/handlers.js.map +1 -0
- package/dist/mcp/tools/index.d.ts +7 -0
- package/dist/mcp/tools/index.d.ts.map +1 -0
- package/dist/mcp/tools/index.js +6 -0
- package/dist/mcp/tools/index.js.map +1 -0
- package/dist/mcp/tools/types.d.ts +314 -0
- package/dist/mcp/tools/types.d.ts.map +1 -0
- package/dist/mcp/tools/types.js +5 -0
- package/dist/mcp/tools/types.js.map +1 -0
- package/dist/mcp/transports/stdio.d.ts +27 -0
- package/dist/mcp/transports/stdio.d.ts.map +1 -0
- package/dist/mcp/transports/stdio.js +237 -0
- package/dist/mcp/transports/stdio.js.map +1 -0
- package/dist/telemetry/index.d.ts +58 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +109 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/telemetry/logger.d.ts +116 -0
- package/dist/telemetry/logger.d.ts.map +1 -0
- package/dist/telemetry/logger.js +256 -0
- package/dist/telemetry/logger.js.map +1 -0
- package/dist/telemetry/metrics.d.ts +115 -0
- package/dist/telemetry/metrics.d.ts.map +1 -0
- package/dist/telemetry/metrics.js +292 -0
- package/dist/telemetry/metrics.js.map +1 -0
- package/dist/telemetry/tracer.d.ts +227 -0
- package/dist/telemetry/tracer.d.ts.map +1 -0
- package/dist/telemetry/tracer.js +355 -0
- package/dist/telemetry/tracer.js.map +1 -0
- package/dist/web/app.d.ts +2 -0
- package/dist/web/app.d.ts.map +1 -0
- package/dist/web/app.js +115 -0
- package/dist/web/app.js.map +1 -0
- package/dist/web/components/ContactList.d.ts +3 -0
- package/dist/web/components/ContactList.d.ts.map +1 -0
- package/dist/web/components/ContactList.js +262 -0
- package/dist/web/components/ContactList.js.map +1 -0
- package/dist/web/components/Dashboard.d.ts +3 -0
- package/dist/web/components/Dashboard.d.ts.map +1 -0
- package/dist/web/components/Dashboard.js +158 -0
- package/dist/web/components/Dashboard.js.map +1 -0
- package/dist/web/components/DealPipeline.d.ts +3 -0
- package/dist/web/components/DealPipeline.d.ts.map +1 -0
- package/dist/web/components/DealPipeline.js +306 -0
- package/dist/web/components/DealPipeline.js.map +1 -0
- package/dist/web/index.d.ts +2 -0
- package/dist/web/index.d.ts.map +1 -0
- package/dist/web/index.js +269 -0
- package/dist/web/index.js.map +1 -0
- package/dist/web/types.d.ts +75 -0
- package/dist/web/types.d.ts.map +1 -0
- package/dist/web/types.js +3 -0
- package/dist/web/types.js.map +1 -0
- package/native/index.d.ts +571 -0
- package/native/index.js +687 -0
- package/package.json +105 -0
- package/src/cli/commands/activities.ts +543 -0
- package/src/cli/commands/contacts.ts +563 -0
- package/src/cli/commands/deals.ts +637 -0
- package/src/cli/commands/media.ts +521 -0
- package/src/cli/commands/search.ts +426 -0
- package/src/cli/index.ts +203 -0
- package/src/cli/repl.ts +379 -0
- package/src/cli/utils/config.ts +299 -0
- package/src/cli/utils/output.ts +386 -0
- package/src/cli/utils/prompt.ts +444 -0
- package/src/cli.ts +11 -0
- package/src/core/index.ts +184 -0
- package/src/core/schemas.ts +770 -0
- package/src/core/types.ts +969 -0
- package/src/index.ts +8 -0
- package/src/mcp/index.ts +17 -0
- package/src/mcp/server.ts +26 -0
- package/src/mcp/storage/client.ts +408 -0
- package/src/mcp/storage/index.ts +7 -0
- package/src/mcp/storage/types.ts +72 -0
- package/src/mcp/tools/definitions.ts +961 -0
- package/src/mcp/tools/handlers.ts +805 -0
- package/src/mcp/tools/index.ts +7 -0
- package/src/mcp/tools/types.ts +390 -0
- package/src/mcp/transports/stdio.ts +225 -0
- package/src/telemetry/index.ts +131 -0
- package/src/telemetry/logger.ts +318 -0
- package/src/telemetry/metrics.ts +393 -0
- package/src/telemetry/tracer.ts +487 -0
- package/src/web/api/activities.ts +41 -0
- package/src/web/api/contacts.ts +114 -0
- package/src/web/api/deals.ts +108 -0
- package/src/web/api/media.ts +98 -0
- package/src/web/app.tsx +143 -0
- package/src/web/components/ActivityFeed.tsx +195 -0
- package/src/web/components/ContactList.tsx +340 -0
- package/src/web/components/Dashboard.tsx +214 -0
- package/src/web/components/DealPipeline.tsx +405 -0
- package/src/web/components/MediaGallery.tsx +334 -0
- package/src/web/index.html +14 -0
- package/src/web/index.ts +326 -0
- package/src/web/styles/main.css +180 -0
- package/src/web/types.ts +311 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metrics module for CRM system
|
|
3
|
+
*
|
|
4
|
+
* Provides a simple metrics collection system with counters, gauges, and histograms.
|
|
5
|
+
* Supports exporting metrics in Prometheus format.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type MetricType = 'counter' | 'gauge' | 'histogram';
|
|
9
|
+
|
|
10
|
+
export interface MetricLabel {
|
|
11
|
+
[key: string]: string | number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface MetricValue {
|
|
15
|
+
value: number;
|
|
16
|
+
timestamp: number;
|
|
17
|
+
labels: MetricLabel;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface MetricDefinition {
|
|
21
|
+
name: string;
|
|
22
|
+
type: MetricType;
|
|
23
|
+
description: string;
|
|
24
|
+
unit?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface HistogramOptions {
|
|
28
|
+
buckets?: number[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface CounterMetric extends MetricDefinition {
|
|
32
|
+
type: 'counter';
|
|
33
|
+
values: MetricValue[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface GaugeMetric extends MetricDefinition {
|
|
37
|
+
type: 'gauge';
|
|
38
|
+
values: MetricValue[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface HistogramMetric extends MetricDefinition {
|
|
42
|
+
type: 'histogram';
|
|
43
|
+
values: {
|
|
44
|
+
sum: number;
|
|
45
|
+
count: number;
|
|
46
|
+
buckets: { boundary: number; count: number }[];
|
|
47
|
+
timestamp: number;
|
|
48
|
+
labels: MetricLabel;
|
|
49
|
+
}[];
|
|
50
|
+
options: HistogramOptions;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
type Metric = CounterMetric | GaugeMetric | HistogramMetric;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Default histogram buckets (in seconds for latency)
|
|
57
|
+
*/
|
|
58
|
+
const DEFAULT_BUCKETS = [0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10];
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Metrics registry for collecting and exporting metrics
|
|
62
|
+
*/
|
|
63
|
+
export class MetricsRegistry {
|
|
64
|
+
private readonly metrics: Map<string, Metric> = new Map();
|
|
65
|
+
private readonly prefix: string;
|
|
66
|
+
|
|
67
|
+
constructor(prefix: string = 'crm_') {
|
|
68
|
+
this.prefix = prefix;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Register a counter metric
|
|
73
|
+
*/
|
|
74
|
+
registerCounter(name: string, description: string, unit?: string): void {
|
|
75
|
+
const metricName = this.prefix + name;
|
|
76
|
+
if (this.metrics.has(metricName)) {
|
|
77
|
+
throw new Error(`Metric ${metricName} already registered`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
this.metrics.set(metricName, {
|
|
81
|
+
name: metricName,
|
|
82
|
+
type: 'counter',
|
|
83
|
+
description,
|
|
84
|
+
unit,
|
|
85
|
+
values: [],
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Register a gauge metric
|
|
91
|
+
*/
|
|
92
|
+
registerGauge(name: string, description: string, unit?: string): void {
|
|
93
|
+
const metricName = this.prefix + name;
|
|
94
|
+
if (this.metrics.has(metricName)) {
|
|
95
|
+
throw new Error(`Metric ${metricName} already registered`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
this.metrics.set(metricName, {
|
|
99
|
+
name: metricName,
|
|
100
|
+
type: 'gauge',
|
|
101
|
+
description,
|
|
102
|
+
unit,
|
|
103
|
+
values: [],
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Register a histogram metric
|
|
109
|
+
*/
|
|
110
|
+
registerHistogram(
|
|
111
|
+
name: string,
|
|
112
|
+
description: string,
|
|
113
|
+
unit?: string,
|
|
114
|
+
options?: HistogramOptions
|
|
115
|
+
): void {
|
|
116
|
+
const metricName = this.prefix + name;
|
|
117
|
+
if (this.metrics.has(metricName)) {
|
|
118
|
+
throw new Error(`Metric ${metricName} already registered`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
this.metrics.set(metricName, {
|
|
122
|
+
name: metricName,
|
|
123
|
+
type: 'histogram',
|
|
124
|
+
description,
|
|
125
|
+
unit,
|
|
126
|
+
options: { buckets: options?.buckets ?? DEFAULT_BUCKETS },
|
|
127
|
+
values: [],
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Increment a counter
|
|
133
|
+
*/
|
|
134
|
+
increment(name: string, value: number = 1, labels: MetricLabel = {}): void {
|
|
135
|
+
const metricName = this.prefix + name;
|
|
136
|
+
const metric = this.metrics.get(metricName);
|
|
137
|
+
|
|
138
|
+
if (!metric) {
|
|
139
|
+
throw new Error(`Metric ${metricName} not found`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (metric.type !== 'counter') {
|
|
143
|
+
throw new Error(`Metric ${metricName} is not a counter`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const existingIndex = metric.values.findIndex(v =>
|
|
147
|
+
this.labelsMatch(v.labels, labels)
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
if (existingIndex >= 0) {
|
|
151
|
+
metric.values[existingIndex].value += value;
|
|
152
|
+
metric.values[existingIndex].timestamp = Date.now();
|
|
153
|
+
} else {
|
|
154
|
+
metric.values.push({
|
|
155
|
+
value,
|
|
156
|
+
timestamp: Date.now(),
|
|
157
|
+
labels,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Decrement a counter (useful for error tracking)
|
|
164
|
+
*/
|
|
165
|
+
decrement(name: string, value: number = 1, labels: MetricLabel = {}): void {
|
|
166
|
+
this.increment(name, -value, labels);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Set a gauge value
|
|
171
|
+
*/
|
|
172
|
+
gauge(name: string, value: number, labels: MetricLabel = {}): void {
|
|
173
|
+
const metricName = this.prefix + name;
|
|
174
|
+
const metric = this.metrics.get(metricName);
|
|
175
|
+
|
|
176
|
+
if (!metric) {
|
|
177
|
+
throw new Error(`Metric ${metricName} not found`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (metric.type !== 'gauge') {
|
|
181
|
+
throw new Error(`Metric ${metricName} is not a gauge`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const existingIndex = metric.values.findIndex(v =>
|
|
185
|
+
this.labelsMatch(v.labels, labels)
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
if (existingIndex >= 0) {
|
|
189
|
+
metric.values[existingIndex].value = value;
|
|
190
|
+
metric.values[existingIndex].timestamp = Date.now();
|
|
191
|
+
} else {
|
|
192
|
+
metric.values.push({
|
|
193
|
+
value,
|
|
194
|
+
timestamp: Date.now(),
|
|
195
|
+
labels,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Observe a value for histogram
|
|
202
|
+
*/
|
|
203
|
+
observe(name: string, value: number, labels: MetricLabel = {}): void {
|
|
204
|
+
const metricName = this.prefix + name;
|
|
205
|
+
const metric = this.metrics.get(metricName);
|
|
206
|
+
|
|
207
|
+
if (!metric) {
|
|
208
|
+
throw new Error(`Metric ${metricName} not found`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (metric.type !== 'histogram') {
|
|
212
|
+
throw new Error(`Metric ${metricName} is not a histogram`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const existingIndex = metric.values.findIndex(v =>
|
|
216
|
+
this.labelsMatch(v.labels, labels)
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
if (existingIndex >= 0) {
|
|
220
|
+
const existing = metric.values[existingIndex];
|
|
221
|
+
existing.sum += value;
|
|
222
|
+
existing.count += 1;
|
|
223
|
+
|
|
224
|
+
for (const bucket of existing.buckets) {
|
|
225
|
+
if (value <= bucket.boundary) {
|
|
226
|
+
bucket.count += 1;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
existing.timestamp = Date.now();
|
|
230
|
+
} else {
|
|
231
|
+
const buckets = metric.options.buckets ?? DEFAULT_BUCKETS;
|
|
232
|
+
metric.values.push({
|
|
233
|
+
sum: value,
|
|
234
|
+
count: 1,
|
|
235
|
+
buckets: buckets.map(boundary => ({
|
|
236
|
+
boundary,
|
|
237
|
+
count: value <= boundary ? 1 : 0,
|
|
238
|
+
})),
|
|
239
|
+
timestamp: Date.now(),
|
|
240
|
+
labels,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Create a timer that records duration in a histogram
|
|
247
|
+
*/
|
|
248
|
+
startTimer(name: string, labels: MetricLabel = {}): () => number {
|
|
249
|
+
const start = process.hrtime.bigint();
|
|
250
|
+
|
|
251
|
+
return () => {
|
|
252
|
+
const end = process.hrtime.bigint();
|
|
253
|
+
const durationSeconds = Number(end - start) / 1e9;
|
|
254
|
+
this.observe(name, durationSeconds, labels);
|
|
255
|
+
return durationSeconds;
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Get all metrics
|
|
261
|
+
*/
|
|
262
|
+
getMetrics(): Metric[] {
|
|
263
|
+
return Array.from(this.metrics.values());
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Export metrics in Prometheus text format
|
|
268
|
+
*/
|
|
269
|
+
exportPrometheus(): string {
|
|
270
|
+
const lines: string[] = [];
|
|
271
|
+
|
|
272
|
+
for (const metric of this.metrics.values()) {
|
|
273
|
+
// Add help and type comments
|
|
274
|
+
lines.push(`# HELP ${metric.name} ${metric.description}`);
|
|
275
|
+
lines.push(`# TYPE ${metric.name} ${metric.type}`);
|
|
276
|
+
|
|
277
|
+
if (metric.type === 'counter' || metric.type === 'gauge') {
|
|
278
|
+
for (const value of metric.values) {
|
|
279
|
+
const labelStr = this.formatLabels(value.labels);
|
|
280
|
+
lines.push(`${metric.name}${labelStr} ${value.value}`);
|
|
281
|
+
}
|
|
282
|
+
} else if (metric.type === 'histogram') {
|
|
283
|
+
for (const value of metric.values) {
|
|
284
|
+
const labelStr = this.formatLabels(value.labels);
|
|
285
|
+
|
|
286
|
+
// Bucket counts
|
|
287
|
+
for (const bucket of value.buckets) {
|
|
288
|
+
const bucketLabels = { ...value.labels, le: bucket.boundary.toString() };
|
|
289
|
+
const bucketLabelStr = this.formatLabels(bucketLabels);
|
|
290
|
+
lines.push(`${metric.name}_bucket${bucketLabelStr} ${bucket.count}`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// +Inf bucket (same as count)
|
|
294
|
+
const infLabels = { ...value.labels, le: '+Inf' };
|
|
295
|
+
const infLabelStr = this.formatLabels(infLabels);
|
|
296
|
+
lines.push(`${metric.name}_bucket${infLabelStr} ${value.count}`);
|
|
297
|
+
|
|
298
|
+
// Sum and count
|
|
299
|
+
lines.push(`${metric.name}_sum${labelStr} ${value.sum}`);
|
|
300
|
+
lines.push(`${metric.name}_count${labelStr} ${value.count}`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
lines.push(''); // Empty line between metrics
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return lines.join('\n');
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Reset all metrics
|
|
312
|
+
*/
|
|
313
|
+
reset(): void {
|
|
314
|
+
for (const metric of this.metrics.values()) {
|
|
315
|
+
if (metric.type === 'histogram') {
|
|
316
|
+
metric.values = [];
|
|
317
|
+
} else {
|
|
318
|
+
metric.values = [];
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Get metric by name
|
|
325
|
+
*/
|
|
326
|
+
getMetric(name: string): Metric | undefined {
|
|
327
|
+
return this.metrics.get(this.prefix + name);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
private labelsMatch(a: MetricLabel, b: MetricLabel): boolean {
|
|
331
|
+
const keysA = Object.keys(a).sort();
|
|
332
|
+
const keysB = Object.keys(b).sort();
|
|
333
|
+
|
|
334
|
+
if (keysA.length !== keysB.length) return false;
|
|
335
|
+
|
|
336
|
+
return keysA.every((key, i) => {
|
|
337
|
+
if (key !== keysB[i]) return false;
|
|
338
|
+
return a[key] === b[key];
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
private formatLabels(labels: MetricLabel): string {
|
|
343
|
+
const entries = Object.entries(labels);
|
|
344
|
+
if (entries.length === 0) return '';
|
|
345
|
+
|
|
346
|
+
const formatted = entries.map(([key, value]) => {
|
|
347
|
+
const escapedValue = String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
348
|
+
return `${key}="${escapedValue}"`;
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
return `{${formatted.join(',')}}`;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Default metrics registry
|
|
357
|
+
*/
|
|
358
|
+
export const metrics = new MetricsRegistry('crm_');
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Pre-registered CRM metrics
|
|
362
|
+
*/
|
|
363
|
+
export function initializeCrmMetrics(registry: MetricsRegistry = metrics): void {
|
|
364
|
+
// Contact metrics
|
|
365
|
+
registry.registerCounter('contacts_created_total', 'Total number of contacts created');
|
|
366
|
+
registry.registerCounter('contacts_updated_total', 'Total number of contacts updated');
|
|
367
|
+
registry.registerCounter('contacts_deleted_total', 'Total number of contacts deleted');
|
|
368
|
+
registry.registerGauge('contacts_active_count', 'Current number of active contacts');
|
|
369
|
+
|
|
370
|
+
// Deal metrics
|
|
371
|
+
registry.registerCounter('deals_created_total', 'Total number of deals created');
|
|
372
|
+
registry.registerCounter('deals_won_total', 'Total number of deals won');
|
|
373
|
+
registry.registerCounter('deals_lost_total', 'Total number of deals lost');
|
|
374
|
+
registry.registerGauge('deals_active_count', 'Current number of active deals');
|
|
375
|
+
registry.registerGauge('deals_pipeline_value', 'Total value of deals in pipeline', 'USD');
|
|
376
|
+
|
|
377
|
+
// Activity metrics
|
|
378
|
+
registry.registerCounter('activities_created_total', 'Total number of activities created');
|
|
379
|
+
registry.registerCounter('activities_completed_total', 'Total number of activities completed');
|
|
380
|
+
registry.registerGauge('activities_pending_count', 'Current number of pending activities');
|
|
381
|
+
|
|
382
|
+
// API metrics
|
|
383
|
+
registry.registerHistogram('api_request_duration_seconds', 'API request duration in seconds');
|
|
384
|
+
registry.registerCounter('api_requests_total', 'Total number of API requests');
|
|
385
|
+
registry.registerCounter('api_errors_total', 'Total number of API errors');
|
|
386
|
+
|
|
387
|
+
// Search metrics
|
|
388
|
+
registry.registerHistogram('search_duration_seconds', 'Search query duration in seconds');
|
|
389
|
+
registry.registerCounter('search_queries_total', 'Total number of search queries');
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Initialize default metrics
|
|
393
|
+
initializeCrmMetrics();
|