fluency-v8-components 1.6.0 → 1.6.2

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.
@@ -91,15 +91,27 @@ export default {
91
91
  acc[i] = field ? field : "(no name)";
92
92
  return acc;
93
93
  }, {});
94
+ const valueMap = data.values.reduce((acc, value, i) => {
95
+ acc[i] = value || 0;
96
+ return acc;
97
+ }, {});
98
+
99
+ const formatValue = (value) => {
100
+ if (this.unit === "Bytes (B)" || this.unit === "B") {
101
+ return dataCount(value) + "B";
102
+ }
103
+ if (this.unit === "Cost ($)") {
104
+ return costCount(value);
105
+ }
106
+ if (this.unit === "Percent (%)") {
107
+ return value.toFixed(2) + "%";
108
+ }
109
+ return humanCount(value);
110
+ };
94
111
 
95
112
  // Declare the tooltip mouseover functions
96
113
  const mouseoverEmpty = (event, d) => {
97
- const value =
98
- this.unit === "Bytes (B)" || this.unit === "B"
99
- ? dataCount(d.values) + "B"
100
- : this.unit === "Percent (%)"
101
- ? d.values.toFixed(2) + "%"
102
- : humanCount(d.values);
114
+ const value = formatValue(d.values);
103
115
  event.target.style.opacity = 0.4;
104
116
  d3.select("#" + this.id + "tooltip")
105
117
  .html(
@@ -163,7 +175,11 @@ export default {
163
175
  .attr("y", (d, i) => y(i))
164
176
  .attr("width", (d) => 0)
165
177
  .attr("height", y.bandwidth())
166
- .attr("fill", (d, i) => chartColors[i % chartColors.length])
178
+ .attr(
179
+ "fill",
180
+ (d, i) =>
181
+ `${chartColors[i % chartColors.length]}${this.theme === "dark" ? "CC" : "99"}`
182
+ )
167
183
  .style("opacity", 0.8);
168
184
  // Animation
169
185
  this.svg
@@ -185,7 +201,11 @@ export default {
185
201
  .attr("y", (d, i) => y(i))
186
202
  .attr("width", this.width - marginLeft)
187
203
  .attr("height", y.bandwidth())
188
- .attr("fill", (d, i) => chartColors[i % chartColors.length])
204
+ .attr(
205
+ "fill",
206
+ (d, i) =>
207
+ `${chartColors[i % chartColors.length]}${this.theme === "dark" ? "CC" : "99"}`
208
+ )
189
209
  .style("opacity", 0)
190
210
  .on("mouseover", mouseoverEmpty)
191
211
  .on("mouseout", mouseoutEmpty);
@@ -215,7 +235,7 @@ export default {
215
235
  .axisRight(y)
216
236
  .tickSize(0)
217
237
  .tickFormat((d) => {
218
- return domainMap[d];
238
+ return `${domainMap[d]} - ${formatValue(valueMap[d])}`;
219
239
  })
220
240
  )
221
241
  .call((g) => g.select(".domain").remove())
@@ -286,7 +286,7 @@ export default {
286
286
  .attr("stroke", (d) => {
287
287
  return (
288
288
  platformMetricsLabels[this.theme][d.action] ||
289
- chartColors[d.index % chartColors.length]
289
+ `${chartColors[d.index % chartColors.length]}${this.theme === "dark" ? "CC" : "99"}`
290
290
  );
291
291
  })
292
292
  .attr("stroke-width", 1.5)
@@ -330,7 +330,7 @@ export default {
330
330
  "fill",
331
331
  (d) =>
332
332
  platformMetricsLabels[this.theme][series.action] ||
333
- chartColors[d.index % chartColors.length]
333
+ `${chartColors[d.index % chartColors.length]}${this.theme === "dark" ? "CC" : "99"}`
334
334
  )
335
335
  .attr("r", 2.5)
336
336
  .attr("cx", (d) => x(d.slots))
@@ -384,7 +384,7 @@ export default {
384
384
  .style("fill", (d) => {
385
385
  return (
386
386
  platformMetricsLabels[this.theme][d.action] ||
387
- chartColors[d.index % chartColors.length]
387
+ `${chartColors[d.index % chartColors.length]}${this.theme === "dark" ? "CC" : "99"}`
388
388
  );
389
389
  })
390
390
  .on("mouseover", legendMouseover)
@@ -403,7 +403,7 @@ export default {
403
403
  .style("fill", (d) => {
404
404
  return (
405
405
  platformMetricsLabels[this.theme][d.action] ||
406
- chartColors[d.index % chartColors.length]
406
+ `${chartColors[d.index % chartColors.length]}${this.theme === "dark" ? "CC" : "99"}`
407
407
  );
408
408
  })
409
409
  .text((d) => d.action || "(no name)")
@@ -1,9 +1,11 @@
1
1
  <template>
2
- <div :id="id" class="float-left">
3
- <div :id="id + 'tooltip'" role="tooltip" class="tooltip"></div>
4
- </div>
5
- <div class="overflow-auto" :style="`width: ${width}px; overflow: auto`">
6
- <div :id="id + 'legend'"></div>
2
+ <div class="flex w-full flex-col items-center">
3
+ <div :id="id">
4
+ <div :id="id + 'tooltip'" role="tooltip" class="tooltip"></div>
5
+ </div>
6
+ <div class="overflow-auto" :style="`width: ${width}px; overflow: auto`">
7
+ <div :id="id + 'legend'"></div>
8
+ </div>
7
9
  </div>
8
10
  </template>
9
11
  <script>
@@ -57,6 +59,11 @@ export default {
57
59
  this.initializeChart();
58
60
  this.createChart();
59
61
  },
62
+ watch: {
63
+ theme() {
64
+ this.updateThemeStyles();
65
+ },
66
+ },
60
67
  methods: {
61
68
  initializeChart() {
62
69
  this.svg = d3
@@ -214,7 +221,9 @@ export default {
214
221
  return arc(d);
215
222
  })
216
223
  .attr("fill", (d, i) =>
217
- !total ? chartColors[0] : chartColors[i % chartColors.length]
224
+ !total
225
+ ? chartColors[0]
226
+ : `${chartColors[i % chartColors.length]}${handle.theme === "dark" ? "CC" : "99"}`
218
227
  )
219
228
  .style("opacity", 0.8)
220
229
  .style("fill-opacity", 0)
@@ -288,6 +297,24 @@ export default {
288
297
  exit.call((g) => g.style("opacity", 0).remove());
289
298
  }
290
299
  },
300
+ updateThemeStyles() {
301
+ if (!this.svg) {
302
+ return;
303
+ }
304
+ const total = this.data.reduce((acc, d) => acc + (d.values || 0), 0);
305
+ const textColor = this.theme === "dark" ? "white" : "black";
306
+ this.svg
307
+ .selectAll("polyline." + this.id + "labels")
308
+ .style("stroke", textColor);
309
+ this.svg.selectAll("text." + this.id + "labels").style("fill", textColor);
310
+ this.svg
311
+ .selectAll("path." + this.id + "pie")
312
+ .attr("fill", (d, i) =>
313
+ !total
314
+ ? chartColors[0]
315
+ : `${chartColors[i % chartColors.length]}${this.theme === "dark" ? "CC" : "99"}`
316
+ );
317
+ },
291
318
  },
292
319
  };
293
320
  </script>
@@ -271,7 +271,7 @@ export default {
271
271
  "fill",
272
272
  (d) =>
273
273
  costLabels[this.theme][d.key] ||
274
- chartColors[namesIndex.indexOf(d.key) % chartColors.length]
274
+ `${chartColors[namesIndex.indexOf(d.key) % chartColors.length]}${this.theme === "dark" ? "CC" : "99"}`
275
275
  )
276
276
  .selectAll("rect")
277
277
  .data((D) => D.map((d) => ((d.key = D.key), d)))
@@ -333,7 +333,8 @@ export default {
333
333
  .attr(
334
334
  "fill",
335
335
  (d, i) =>
336
- costLabels[this.theme][d.key] || chartColors[i % chartColors.length]
336
+ costLabels[this.theme][d.key] ||
337
+ `${chartColors[i % chartColors.length]}${this.theme === "dark" ? "CC" : "99"}`
337
338
  )
338
339
  .on("mouseover", legendMouseover)
339
340
  .on("mouseout", legendMouseout)
@@ -351,7 +352,8 @@ export default {
351
352
  .attr(
352
353
  "fill",
353
354
  (d, i) =>
354
- costLabels[this.theme][d.key] || chartColors[i % chartColors.length]
355
+ costLabels[this.theme][d.key] ||
356
+ `${chartColors[i % chartColors.length]}${this.theme === "dark" ? "CC" : "99"}`
355
357
  )
356
358
  .text((d, i) => adjustText(this.names[d[0].key], 700))
357
359
  .attr("font-weight", 500)
@@ -239,9 +239,12 @@ export default {
239
239
  instancesIndex.length +
240
240
  0.5
241
241
  ) ||
242
- chartColors[
243
- namesIndex.indexOf(d.key.split(":")[0]) % chartColors.length
244
- ]
242
+ `${
243
+ chartColors[
244
+ namesIndex.indexOf(d.key.split(":")[0]) %
245
+ chartColors.length
246
+ ]
247
+ }${this.theme === "dark" ? "CC" : "99"}`
245
248
  )
246
249
  .selectAll("rect")
247
250
  .data((D) => D.map((d) => ((d.key = D.key), d)))
@@ -363,7 +366,7 @@ export default {
363
366
  "fill",
364
367
  (d, i) =>
365
368
  platformMetricsLabels[this.theme][d] ||
366
- chartColors[i % chartColors.length]
369
+ `${chartColors[i % chartColors.length]}${this.theme === "dark" ? "CC" : "99"}`
367
370
  )
368
371
  .on("mouseover", legendMouseover)
369
372
  .on("mouseout", legendMouseout)
@@ -382,7 +385,7 @@ export default {
382
385
  "fill",
383
386
  (d, i) =>
384
387
  platformMetricsLabels[this.theme][d] ||
385
- chartColors[i % chartColors.length]
388
+ `${chartColors[i % chartColors.length]}${this.theme === "dark" ? "CC" : "99"}`
386
389
  )
387
390
  .text((d, i) => adjustText(d, 700))
388
391
  .attr("font-weight", 500)
@@ -0,0 +1,219 @@
1
+ <template>
2
+ <div class="col items-center">
3
+ <div class="text-center">
4
+ <span>{{ title }} Timeline ({{ total }} Events)</span>
5
+ </div>
6
+ <div ref="myplot" :class="['px-4', { 'pb-4': !props.flow }]"></div>
7
+ <div class="flex justify-end w-full mr-2">
8
+ <div ref="myplotLegend"></div>
9
+ </div>
10
+ </div>
11
+ </template>
12
+
13
+ <script setup>
14
+ import { ref, watch } from "vue";
15
+ import * as d3 from "d3";
16
+ import * as Plot from "@observablehq/plot";
17
+ import { dateLocalTime } from "@/utils/timeUtils";
18
+
19
+ // props
20
+ const props = defineProps({
21
+ title: String,
22
+ buckets: Array,
23
+ total: Number,
24
+ from: Number,
25
+ to: Number,
26
+ flow: {
27
+ type: Boolean,
28
+ default: false,
29
+ },
30
+ width: {
31
+ type: Number,
32
+ default: 1200,
33
+ },
34
+ height: {
35
+ type: Number,
36
+ default: 300,
37
+ },
38
+ theme: {
39
+ type: String,
40
+ default: "light",
41
+ },
42
+ /** Key on each bucket for the risk score value (y-axis). Default "risk_score". */
43
+ valueKey: {
44
+ type: String,
45
+ default: "risk_score",
46
+ },
47
+ });
48
+ // states
49
+ const myplot = ref(null);
50
+ const myplotLegend = ref(null);
51
+ // watch for changes in data
52
+ watch([() => props.buckets], () => {
53
+ plotGraph();
54
+ });
55
+ watch([() => props.theme], () => {
56
+ plotGraph();
57
+ });
58
+
59
+ // function defs
60
+ function plotGraph() {
61
+ if (!props.buckets) return;
62
+ const valueKey = props.valueKey;
63
+ let options = {
64
+ marginTop: 30,
65
+ marks: [
66
+ Plot.frame(),
67
+ Plot.text(["No data available!"], { frameAnchor: "middle" }),
68
+ ],
69
+ };
70
+ let marginLeft = 80;
71
+ let marginRight = 80;
72
+
73
+ if (props.buckets.length > 0) {
74
+ if (props.flow) {
75
+ const b = props.buckets;
76
+
77
+ // Left axis (flow) domain = max of rx/tx
78
+ const leftMax = Math.max(
79
+ 1,
80
+ d3.max(b, (d) => Math.max(d?.rxHistogram?.value ?? 0, d?.txHistogram?.value ?? 0)) ?? 0
81
+ );
82
+
83
+ // Right axis (risk score) domain
84
+ const rightMax = Math.max(
85
+ 1,
86
+ d3.max(b, (d) => d?.[valueKey] ?? 0) ?? 0
87
+ );
88
+
89
+ const k = leftMax / rightMax;
90
+ const rightTicks = d3.ticks(0, rightMax, 5);
91
+ const leftTicks = d3.ticks(0, leftMax, 5);
92
+
93
+ options = {
94
+ x: { type: "time", label: "Time", tickSpacing: 100 },
95
+ y: {
96
+ label: "Flow",
97
+ domain: [0, leftMax],
98
+ },
99
+ grid: true,
100
+ marks: [
101
+ Plot.lineY(b, {
102
+ x: "key",
103
+ y: (d) => d?.rxHistogram?.value ?? 0,
104
+ strokeWidth: 2.5,
105
+ curve: "catmull-rom",
106
+ stroke: "#22c55e",
107
+ }),
108
+ Plot.lineY(b, {
109
+ x: "key",
110
+ y: (d) => d?.txHistogram?.value ?? 0,
111
+ strokeWidth: 2.5,
112
+ curve: "catmull-rom",
113
+ stroke: "#eab308",
114
+ }),
115
+ Plot.dotY(b, {
116
+ x: "key",
117
+ y: (d) => (d?.[valueKey] ?? 0) * k,
118
+ stroke: "#71b6e0",
119
+ strokeWidth: 3,
120
+ }),
121
+ Plot.axisY({
122
+ anchor: "left",
123
+ ticks: leftTicks.map((t) => t),
124
+ tickFormat: (_, i) => d3.format("~s")(leftTicks[i]) + "B",
125
+ label: "Bytes",
126
+ }),
127
+ Plot.axisY({
128
+ anchor: "right",
129
+ ticks: rightTicks.map((t) => t * k),
130
+ tickFormat: (_, i) => d3.format("~s")(rightTicks[i]),
131
+ label: "Risk Score",
132
+ }),
133
+ Plot.tip(
134
+ b,
135
+ Plot.pointerX({
136
+ x: "key",
137
+ y: (d) => (d?.[valueKey] ?? 0) * k,
138
+ fill: props.theme === "light" ? "white" : "black",
139
+ title: (d) =>
140
+ `Time: ${dateLocalTime(d.key)}\n` +
141
+ `rx: ${d?.rxHistogram?.value ?? 0}B (~${d3.format(".0s")(d?.rxHistogram?.value)}B)\n` +
142
+ `tx: ${d?.txHistogram?.value ?? 0}B (~${d3.format(".0s")(d?.txHistogram?.value)}B)\n` +
143
+ `Risk Score: ${Math.round(d?.[valueKey] ?? 0)}`,
144
+ })
145
+ ),
146
+ ],
147
+ };
148
+ } else {
149
+ const dataMax = d3.max(props.buckets, (d) => d?.[valueKey] ?? 0) ?? 0;
150
+ const maxRisk = dataMax > 0 ? Math.max(1, dataMax) : 500;
151
+ const allZeros = dataMax === 0;
152
+ const tickFormat = d3.format("d");
153
+ const widestTickLabel = tickFormat(maxRisk).toString();
154
+ marginLeft = Math.max(marginLeft, 24 + widestTickLabel.length * 8);
155
+
156
+ options = {
157
+ x: {
158
+ type: "time",
159
+ label: "Time",
160
+ tickSpacing: 100,
161
+ },
162
+ y: {
163
+ label: "Risk Score",
164
+ domain: [0, maxRisk],
165
+ tickFormat,
166
+ ...(allZeros && { ticks: [0, 100, 200, 300, 400, 500] }),
167
+ },
168
+ grid: true,
169
+ marks: [
170
+ Plot.dotY(props.buckets, {
171
+ x: "key",
172
+ y: (d) => d?.[valueKey] ?? 0,
173
+ stroke: "#71b6e0",
174
+ strokeWidth: 3,
175
+ }),
176
+ Plot.tip(
177
+ props.buckets,
178
+ Plot.pointerX({
179
+ x: "key",
180
+ y: (d) => d?.[valueKey] ?? 0,
181
+ fill: props.theme === "light" ? "white" : "black",
182
+ title: (d) =>
183
+ `Time: ${dateLocalTime(d.key)}\nRisk Score: ${Math.round(d?.[valueKey] ?? 0)}`,
184
+ })
185
+ ),
186
+ ],
187
+ };
188
+ }
189
+ }
190
+
191
+ const plot = Plot.plot({
192
+ marginLeft,
193
+ marginRight,
194
+ width: props.width,
195
+ height: props.height,
196
+ marginBottom: 50,
197
+ inset: 20,
198
+ style: {
199
+ fontSize: "16px",
200
+ backgroundColor: props.theme === "light" ? "white" : "#1a202c",
201
+ },
202
+ ...options,
203
+ });
204
+ myplot.value.innerHTML = "";
205
+ myplot.value.appendChild(plot);
206
+ if (props.flow) {
207
+ const legend = Plot.legend({
208
+ color: {
209
+ type: "categorical",
210
+ domain: ["Received", "Sent", "Risk Score"],
211
+ range: ["#22c55e", "#eab308", "#71b6e0"],
212
+ },
213
+ swatchSize: 8,
214
+ });
215
+ myplotLegend.value.innerHTML = "";
216
+ myplotLegend.value.appendChild(legend);
217
+ }
218
+ }
219
+ </script>
@@ -119,6 +119,7 @@ export { default as StackedChartClustered } from "./charts/StackedChartClustered
119
119
  export { default as PieChart } from "./charts/PieChart.vue";
120
120
  export { default as ProgressChart } from "./charts/ProgressChart.vue";
121
121
  export { default as TimelineChart } from "./charts/TimelineChart.vue";
122
+ export { default as TimelinePlot } from "./charts/TimelinePlot.vue";
122
123
  export { default as RangeSlider } from "./charts/RangeSlider.vue"
123
124
  export { default as EmptyChart } from "./charts/EmptyChart.vue";
124
125
  export { default as WorkflowChart } from "./charts/WorkflowChart.vue";
@@ -29,7 +29,7 @@
29
29
  </div>
30
30
  <div
31
31
  :class="[
32
- 'col-span-3 md:ml-5 md:overflow-auto',
32
+ 'col-span-3 md:ml-5 md:overflow-auto custom-scrollbar',
33
33
  { 'md:max-h-[calc(100vh-255px)]': searchBar },
34
34
  { 'md:max-h-[calc(100vh-200px)]': !searchBar },
35
35
  ]"
@@ -1,7 +1,7 @@
1
1
  import Prism from "prismjs";
2
2
 
3
3
  const builtinfunction =
4
- /#?(?!\s)\b(?:Add|After|Aggregate|alert|anomaly|append|Append|AWS_AccountRegionLambda|AWS_AccountLambda|AWS_GetCostUsage|AWS_GetMetric|AWS_GetPrice|AWS_LoadAsset|base64Encode|base64Decode|Before|Clone|coalesce|ColumnAggregate|concat|contains|content|Cylance_AddGlobalList|Cylance_GetDevice|Cylance_GetThreatDevices|Cylance_GetThreatDownload|Cylance_GetThreatInfo|Cylance_LoadDevice|Cylance_LoadThreat|decoder_CEF|decoder_CSV|decoder_MixedKeyValue|decoder_QuotedKeyValue|DimensionTable|Each|Emit|endsWith|Error|Find|Filter|Fluency_BehaviorSearch|Fluency_Device_Add|Fluency_Device_Delete|Fluency_DeviceSearch|Fluency_Device_Lookup|Fluency_Device_LookupName|Fluency_Device_Update|Fluency_EntityinfoLookup|Fluency_FusionEvent|fluencyLavadbFpl|Fluency_LavadbQuery|Fluency_LavaLakeFpl|Fluency_ResourceLoad|Fluency_SummarySearch|Fluency_Tenant_Device_Add|Fluency_Tenant_Device_Delete|Fluency_Tenant_Device_Lookup|Fluency_Tenant_Device_LookupName|Fluency_Tenant_Device_Update|Format|geoip|GetColumnValues|GetEnv|GetKeys|GetRow|gzipCompress|gzipDecompress|htmlTemplate|indexOf|isEmpty|IsEmpty|isIPv4|isIPv6|ipNormalize|isNull|isNumber|isString|isUndef|isValidIP|Join|JoinStream|jsonClone|jsonTable|len|Limit|main|Map|match|mergeTable|NewColumns|NewColumnLambda|parseBool|parseFloat|parseInt|parseJson|Platform_Action|Platform_Action_Endpoint|Platform_Asset_Lookup|Platform_Asset_Refresh|Platform_Asset_Register|Platform_Cache_Check|Platform_Cache_Delete|Platform_Cache_DeRegister|Platform_Cache_Get|Platform_Cache_Register|Platform_Cache_Replace|Platform_Cache_Set|Platform_Cache_SetMultiple|Platform_Channel|Platform_DataObject_Delete|Platform_DataObject_Get|Platform_DataObject_Save|Platform_DataObject_Search|Platform_EntityinfoCheck|Platform_EntityinfoLookup|Platform_EntityProvider_Lookup|Platform_EntityProvider_Refresh|Platform_Grok_Add_Pattern|Platform_Grok_Check|Platform_Grok_Parse|Platform_Grok_Register|Platform_LoadComponent|Platform_Metric_Alert_Counter_Stop|Platform_Metric_Counter|Platform_Metric_Query|Platform_Metric_QueryBuild|Platform_Metric_QueryRange|Platform_Metric_Sort|Platform_Metric_Sort_Histogram|Platform_Notification_Email|Platform_Notification_PagerDuty|Platform_Notification_Slack|Platform_Notification_ServiceNow|Platform_PluginLambda|Platform_REST_Call|Platform_Sink|Platform_Site_GetInfo|Platform_Site_GetTenants|Platform_Site_LoadTenantComponent|pluginLambda|Plugin_Bitdefender_LoadEndpoint|Plugin_Falcon_GetHost|Plugin_Falcon_GetIncident|Plugin_Falcon_LoadHost|Plugin_Falcon_LoadIncident|Plugin_InControl_LoadClient|Plugin_InControl_LoadDevice|printf|Prom_Push_Counter|Prom_Push_Gauge|regexp|replace|replaceAll|ReplaceKey|RenameColumn|RemoveColumn|Round|run|RxFPL_GetMetric|setEnv|split|Some|Sort|sprintf|startsWith|SetColumnUnit|SetDimensions|SetTags|SetUnit|sleep|SummaryTable|subString|template|TimeTable|toLower|timezoneOffset|toUpper|transform|trim|trimPrefix|trimSuffix|typeof|Unix|UnixMilli|window)\b(?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/g;
4
+ /#?(?!\s)\b(?:Add|After|Aggregate|alert|anomaly|append|Append|AWS_AccountRegionLambda|AWS_AccountLambda|AWS_GetCostUsage|AWS_GetMetric|AWS_GetPrice|AWS_LoadAsset|base64Encode|base64Decode|Before|Clone|coalesce|ColumnAggregate|concat|contains|content|Cylance_AddGlobalList|Cylance_GetDevice|Cylance_GetThreatDevices|Cylance_GetThreatDownload|Cylance_GetThreatInfo|Cylance_LoadDevice|Cylance_LoadThreat|decoder_CEF|decoder_CSV|decoder_MixedKeyValue|decoder_QuotedKeyValue|DimensionTable|Each|Emit|endsWith|Error|Find|Filter|Fluency_BehaviorSearch|Fluency_Device_Add|Fluency_Device_Delete|Fluency_DeviceSearch|Fluency_Device_Lookup|Fluency_Device_LookupName|Fluency_Device_Update|Fluency_EntityinfoLookup|Fluency_FusionEvent|fluencyLavadbFpl|Fluency_LavadbQuery|fluencyLavaLakeFpl|Fluency_LavaLakeFpl|Fluency_ResourceLoad|Fluency_SummarySearch|Fluency_Tenant_Device_Add|Fluency_Tenant_Device_Delete|Fluency_Tenant_Device_Lookup|Fluency_Tenant_Device_LookupName|Fluency_Tenant_Device_Update|Format|geoip|GetColumnValues|GetEnv|GetKeys|GetRow|gzipCompress|gzipDecompress|htmlTemplate|indexOf|isEmpty|IsEmpty|isIPv4|isIPv6|ipNormalize|isNull|isNumber|isString|isUndef|isValidIP|Join|JoinStream|jsonClone|jsonTable|len|Limit|main|Map|match|mergeTable|NewColumns|NewColumnLambda|parseBool|parseFloat|parseInt|parseJson|Platform_Action|Platform_Action_Endpoint|Platform_Asset_Lookup|Platform_Asset_Refresh|Platform_Asset_Register|Platform_Cache_Check|Platform_Cache_Delete|Platform_Cache_DeRegister|Platform_Cache_Get|Platform_Cache_Register|Platform_Cache_Replace|Platform_Cache_Set|Platform_Cache_SetMultiple|Platform_Channel|Platform_DataObject_Delete|Platform_DataObject_Get|Platform_DataObject_Save|Platform_DataObject_Search|Platform_EntityinfoCheck|Platform_EntityinfoLookup|Platform_EntityProvider_Lookup|Platform_EntityProvider_Refresh|Platform_Grok_Add_Pattern|Platform_Grok_Check|Platform_Grok_Parse|Platform_Grok_Register|Platform_LoadComponent|Platform_Metric_Alert_Counter_Stop|Platform_Metric_Counter|Platform_Metric_Query|Platform_Metric_QueryBuild|Platform_Metric_QueryRange|Platform_Metric_Sort|Platform_Metric_Sort_Histogram|Platform_Notification_Email|Platform_Notification_PagerDuty|Platform_Notification_Slack|Platform_Notification_ServiceNow|Platform_PluginLambda|Platform_REST_Call|Platform_Sink|Platform_Site_GetInfo|Platform_Site_GetTenants|Platform_Site_LoadTenantComponent|pluginLambda|Plugin_Bitdefender_LoadEndpoint|Plugin_Falcon_GetHost|Plugin_Falcon_GetIncident|Plugin_Falcon_LoadHost|Plugin_Falcon_LoadIncident|Plugin_InControl_LoadClient|Plugin_InControl_LoadDevice|printf|Prom_Push_Counter|Prom_Push_Gauge|regexp|replace|replaceAll|ReplaceKey|RenameColumn|RemoveColumn|Round|run|RxFPL_GetMetric|setEnv|split|Some|Sort|sprintf|startsWith|SetColumnUnit|SetDimensions|SetTags|SetUnit|sleep|SummaryTable|subString|template|TimeTable|toLower|timezoneOffset|toUpper|transform|trim|trimPrefix|trimSuffix|typeof|Unix|UnixMilli|window)\b(?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/g;
5
5
 
6
6
  const fpl2 = {
7
7
  regex: Prism.languages.javascript.regex,
package/src/fpl/Panel.vue CHANGED
@@ -72,20 +72,21 @@
72
72
  <ActionButtons download hideTooltip @download="downloadTableCSV" />
73
73
  </div>
74
74
  </div>
75
- <component
76
- v-if="!noData"
77
- :is="getComponent()"
78
- :config="panel.panelConfig"
79
- :obj="obj"
80
- :id="`panel${panel.key}${isMobile ? '-mobile' : ''}`"
81
- :layout="layout"
82
- :class="
83
- panel.panelType === 'image' || panel.panelType === 'text' ? '' : 'p-2'
84
- "
85
- :theme="theme"
86
- :paginationStore="paginationStore"
87
- :apiKey="apiKey"
88
- />
75
+ <div v-if="!noData" class="flex w-full justify-center">
76
+ <component
77
+ :is="getComponent()"
78
+ :config="panel.panelConfig"
79
+ :obj="obj"
80
+ :id="`panel${panel.key}${isMobile ? '-mobile' : ''}`"
81
+ :layout="layout"
82
+ :class="
83
+ panel.panelType === 'image' || panel.panelType === 'text' ? '' : 'p-2'
84
+ "
85
+ :theme="theme"
86
+ :paginationStore="paginationStore"
87
+ :apiKey="apiKey"
88
+ />
89
+ </div>
89
90
  <span v-else class="flex justify-center pt-10 text-lg font-semibold">
90
91
  No data available
91
92
  </span>
@@ -1,8 +1,11 @@
1
1
  <template>
2
- <div class="w-full" style="height: calc(100% - 40px)">
2
+ <div
3
+ class="flex w-full items-center justify-center"
4
+ :style="`height: ${layout.h * 60 - 40}px`"
5
+ >
3
6
  <span
4
7
  :class="[
5
- 'flex items-center justify-center content-center h-full overflow-hidden',
8
+ 'overflow-hidden text-center',
6
9
  layout.w > 4 ? 'text-7xl' : 'text-5xl',
7
10
  ]"
8
11
  >
@@ -1,12 +1,12 @@
1
1
  <template>
2
- <div>
2
+ <div class="w-full overflow-hidden">
3
3
  <PieChart
4
4
  v-if="!redraw"
5
5
  :data="data"
6
6
  :id="id"
7
7
  unit="Value"
8
8
  :height="props.layout.h * 60 - 40"
9
- :width="props.layout.w * 69 - 10"
9
+ :width="props.layout.w * 78"
10
10
  tooltipOffset="offset"
11
11
  :value-name="props.config.categories.focus.name"
12
12
  :theme="theme"
@@ -64,8 +64,8 @@ const panelCSVDownload = (title, columns, rows) => {
64
64
  const exportAsCsv = (table) => {
65
65
  let csv =
66
66
  table.columns
67
- .map((col) => col.replaceAll(", ", " ").replaceAll(",", " "))
68
- .join(",") + "\n";
67
+ // .map((col) => col.replaceAll(", ", " ").replaceAll(",", " "))
68
+ .join("|") + "\n";
69
69
 
70
70
  const order = {};
71
71
  table.columns.forEach((col, index) => {
@@ -79,10 +79,10 @@ const exportAsCsv = (table) => {
79
79
  val = "";
80
80
  }
81
81
  if (typeof val === "string") {
82
- val = val.replaceAll(", ", " ").replaceAll(",", " ");
82
+ // val = val.replaceAll(", ", " ").replaceAll(",", " ");
83
83
  }
84
84
  // add double quotes to csv export
85
- r += '"' + val + '"' + ",";
85
+ r += '"' + val + '"' + "|";
86
86
  });
87
87
  csv += r.slice(0, -1) + "\n";
88
88
  });
@@ -128,20 +128,31 @@ const reportToPdf = async (name, layouts) => {
128
128
  const logoXOffset = 20;
129
129
  const logoYOffset = 10;
130
130
  const logo = await getLogo();
131
+ const clientLogo = await getClientLogo();
132
+ const poweredByText = "Powered by Fluency";
133
+ const poweredByTextWidth = pdf.getTextWidth(poweredByText);
131
134
 
132
135
  let pageIndexOffset = 0;
133
136
  let pageNumber = 1;
134
137
 
138
+ const addNewPageMeta = () => {
139
+ pdf.addImage(logo, "png", logoXOffset, logoYOffset, logoWidth, logoHeight);
140
+ if (clientLogo) {
141
+ pdf.addImage(clientLogo, "png", pageWidth - logoWidth - logoXOffset, logoYOffset, logoWidth, logoHeight);
142
+ }
143
+ pdf.text((pageWidth - poweredByTextWidth) / 2, pageHeight - 10, poweredByText);
144
+ pdf.text(pageWidth - 2 * xMargin, pageHeight - 10, `Page ${pageNumber}`);
145
+ };
146
+
135
147
  const widthUnit = Math.floor((pageWidth - xMargin) / layoutColumns);
136
148
  const heightUnit = widthUnit * 0.9;
137
149
  pdf.setFontSize(fontSize);
138
150
  pdf.setFont(undefined, "normal");
139
- pdf.addImage(logo, "png", logoXOffset, logoYOffset, logoWidth, logoHeight);
140
- pdf.text(pageWidth - 2 * xMargin, pageHeight - 10, `Page ${pageNumber}`);
151
+ addNewPageMeta();
141
152
 
142
153
  for (const layout of layouts.layout) {
143
154
  const widget = document.getElementById(`grid-item-${layout.i}`);
144
- const canvasOptions = {};
155
+ const canvasOptions = { scale: 4 };
145
156
  const canvas = await html2canvas(widget, canvasOptions);
146
157
  const img = canvas.toDataURL("image/png");
147
158
  const x = layout.x * widthUnit;
@@ -151,15 +162,7 @@ const reportToPdf = async (name, layouts) => {
151
162
  if (endOfImg > pageHeight + pageIndexOffset) {
152
163
  pdf.addPage();
153
164
  pageNumber += 1;
154
- pdf.addImage(
155
- logo,
156
- "png",
157
- logoXOffset,
158
- logoYOffset,
159
- logoWidth,
160
- logoHeight
161
- );
162
- pdf.text(pageWidth - 2 * xMargin, pageHeight - 10, `Page ${pageNumber}`);
165
+ addNewPageMeta();
163
166
  pageIndexOffset = y;
164
167
  }
165
168
  // check if panel is oversized for page
@@ -187,10 +190,18 @@ const reportToPdf = async (name, layouts) => {
187
190
  pdf.save(`${name}.pdf`);
188
191
  };
189
192
 
190
- const getLogo = () => {
193
+ const getLogo = async () => {
191
194
  return document.getElementById("logo");
192
195
  };
193
196
 
197
+ const getClientLogo = async () => {
198
+ const img = document.getElementById("client-logo");
199
+ if (img) {
200
+ return img;
201
+ }
202
+ return null;
203
+ };
204
+
194
205
  const base64toImage = async (logo) => {
195
206
  let data = {};
196
207
  const img = new Image();