master
c 1,160 lines 38.6 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "libnetdata/libnetdata.h"
4 #include "../../../server/web_client.h"
5
6 #define BADGE_HORIZONTAL_PADDING 4
7 #define VERDANA_KERNING 0.2
8 #define VERDANA_PADDING 1.0
9
10 /*
11 * verdana11_widths[] has been generated with this method:
12 * https://github.com/badges/shields/blob/master/measure-text.js
13 */
14
15 static double verdana11_widths[128] = {
16 [0] = 0.0,
17 [1] = 0.0,
18 [2] = 0.0,
19 [3] = 0.0,
20 [4] = 0.0,
21 [5] = 0.0,
22 [6] = 0.0,
23 [7] = 0.0,
24 [8] = 0.0,
25 [9] = 0.0,
26 [10] = 0.0,
27 [11] = 0.0,
28 [12] = 0.0,
29 [13] = 0.0,
30 [14] = 0.0,
31 [15] = 0.0,
32 [16] = 0.0,
33 [17] = 0.0,
34 [18] = 0.0,
35 [19] = 0.0,
36 [20] = 0.0,
37 [21] = 0.0,
38 [22] = 0.0,
39 [23] = 0.0,
40 [24] = 0.0,
41 [25] = 0.0,
42 [26] = 0.0,
43 [27] = 0.0,
44 [28] = 0.0,
45 [29] = 0.0,
46 [30] = 0.0,
47 [31] = 0.0,
48 [32] = 3.8671874999999996, //
49 [33] = 4.3291015625, // !
50 [34] = 5.048828125, // "
51 [35] = 9.001953125, // #
52 [36] = 6.9931640625, // $
53 [37] = 11.837890625, // %
54 [38] = 7.992187499999999, // &
55 [39] = 2.9541015625, // '
56 [40] = 4.9951171875, // (
57 [41] = 4.9951171875, // )
58 [42] = 6.9931640625, // *
59 [43] = 9.001953125, // +
60 [44] = 4.00146484375, // ,
61 [45] = 4.9951171875, // -
62 [46] = 4.00146484375, // .
63 [47] = 4.9951171875, // /
64 [48] = 6.9931640625, // 0
65 [49] = 6.9931640625, // 1
66 [50] = 6.9931640625, // 2
67 [51] = 6.9931640625, // 3
68 [52] = 6.9931640625, // 4
69 [53] = 6.9931640625, // 5
70 [54] = 6.9931640625, // 6
71 [55] = 6.9931640625, // 7
72 [56] = 6.9931640625, // 8
73 [57] = 6.9931640625, // 9
74 [58] = 4.9951171875, // :
75 [59] = 4.9951171875, // ;
76 [60] = 9.001953125, // <
77 [61] = 9.001953125, // =
78 [62] = 9.001953125, // >
79 [63] = 5.99951171875, // ?
80 [64] = 11.0, // @
81 [65] = 7.51953125, // A
82 [66] = 7.541015625, // B
83 [67] = 7.680664062499999, // C
84 [68] = 8.4755859375, // D
85 [69] = 6.95556640625, // E
86 [70] = 6.32177734375, // F
87 [71] = 8.529296875, // G
88 [72] = 8.26611328125, // H
89 [73] = 4.6298828125, // I
90 [74] = 5.00048828125, // J
91 [75] = 7.62158203125, // K
92 [76] = 6.123046875, // L
93 [77] = 9.2705078125, // M
94 [78] = 8.228515625, // N
95 [79] = 8.658203125, // O
96 [80] = 6.63330078125, // P
97 [81] = 8.658203125, // Q
98 [82] = 7.6484375, // R
99 [83] = 7.51953125, // S
100 [84] = 6.7783203125, // T
101 [85] = 8.05126953125, // U
102 [86] = 7.51953125, // V
103 [87] = 10.87646484375, // W
104 [88] = 7.53564453125, // X
105 [89] = 6.767578125, // Y
106 [90] = 7.53564453125, // Z
107 [91] = 4.9951171875, // [
108 [92] = 4.9951171875, // backslash
109 [93] = 4.9951171875, // ]
110 [94] = 9.001953125, // ^
111 [95] = 6.9931640625, // _
112 [96] = 6.9931640625, // `
113 [97] = 6.6064453125, // a
114 [98] = 6.853515625, // b
115 [99] = 5.73095703125, // c
116 [100] = 6.853515625, // d
117 [101] = 6.552734375, // e
118 [102] = 3.8671874999999996, // f
119 [103] = 6.853515625, // g
120 [104] = 6.9609375, // h
121 [105] = 3.0185546875, // i
122 [106] = 3.78662109375, // j
123 [107] = 6.509765625, // k
124 [108] = 3.0185546875, // l
125 [109] = 10.69921875, // m
126 [110] = 6.9609375, // n
127 [111] = 6.67626953125, // o
128 [112] = 6.853515625, // p
129 [113] = 6.853515625, // q
130 [114] = 4.6943359375, // r
131 [115] = 5.73095703125, // s
132 [116] = 4.33447265625, // t
133 [117] = 6.9609375, // u
134 [118] = 6.509765625, // v
135 [119] = 9.001953125, // w
136 [120] = 6.509765625, // x
137 [121] = 6.509765625, // y
138 [122] = 5.779296875, // z
139 [123] = 6.982421875, // {
140 [124] = 4.9951171875, // |
141 [125] = 6.982421875, // }
142 [126] = 9.001953125, // ~
143 [127] = 0.0
144 };
145
146 // find the width of the string using the verdana 11points font
147 static inline double verdana11_width(const char *s, float em_size) {
148 double w = 0.0;
149
150 while(*s) {
151 // if UTF8 multibyte char found and guess it's width equal 1em
152 // as label width will be updated with JavaScript this is not so important
153
154 // TODO: maybe move UTF8 functions from url.c to separate util in libnetdata
155 // then use url_utf8_get_byte_length etc.
156 if(IS_UTF8_STARTBYTE(*s)) {
157 s++;
158 while(IS_UTF8_BYTE(*s) && !IS_UTF8_STARTBYTE(*s)){
159 s++;
160 }
161 w += em_size;
162 }
163 else {
164 if(likely(!(*s & 0x80))){ // Byte 1XXX XXXX is not valid in UTF8
165 double t = verdana11_widths[(unsigned char)*s];
166 if(t != 0.0)
167 w += t + VERDANA_KERNING;
168 }
169 s++;
170 }
171 }
172
173 w -= VERDANA_KERNING;
174 w += VERDANA_PADDING;
175 return w;
176 }
177
178 static inline size_t escape_xmlz(char *dst, const char *src, size_t len) {
179 size_t i = len;
180
181 // required escapes from
182 // https://github.com/badges/shields/blob/master/badge.js
183 while(*src && i) {
184 switch(*src) {
185 case '\\':
186 *dst++ = '/';
187 src++;
188 i--;
189 break;
190
191 case '&':
192 if(i > 5) {
193 strcpy(dst, "&amp;");
194 i -= 5;
195 dst += 5;
196 src++;
197 }
198 else goto cleanup;
199 break;
200
201 case '<':
202 if(i > 4) {
203 strcpy(dst, "&lt;");
204 i -= 4;
205 dst += 4;
206 src++;
207 }
208 else goto cleanup;
209 break;
210
211 case '>':
212 if(i > 4) {
213 strcpy(dst, "&gt;");
214 i -= 4;
215 dst += 4;
216 src++;
217 }
218 else goto cleanup;
219 break;
220
221 case '"':
222 if(i > 6) {
223 strcpy(dst, "&quot;");
224 i -= 6;
225 dst += 6;
226 src++;
227 }
228 else goto cleanup;
229 break;
230
231 case '\'':
232 if(i > 6) {
233 strcpy(dst, "&apos;");
234 i -= 6;
235 dst += 6;
236 src++;
237 }
238 else goto cleanup;
239 break;
240
241 default:
242 i--;
243 *dst++ = *src++;
244 break;
245 }
246 }
247
248 cleanup:
249 *dst = '\0';
250 return len - i;
251 }
252
253 static inline char *format_value_with_precision_and_unit(char *value_string, size_t value_string_len,
254 NETDATA_DOUBLE value, const char *units, int precision) {
255 if(unlikely(isnan(value) || isinf(value)))
256 value = 0.0;
257
258 char *separator = "";
259 if(unlikely(isalnum((uint8_t)*units)))
260 separator = " ";
261
262 if(precision < 0) {
263 int len, lstop = 0, trim_zeros = 1;
264
265 NETDATA_DOUBLE abs = value;
266 if(isless(value, 0)) {
267 lstop = 1;
268 abs = fabsndd(value);
269 }
270
271 if(isgreaterequal(abs, 1000)) {
272 len = snprintfz(value_string, value_string_len, "%0.0" NETDATA_DOUBLE_MODIFIER, (NETDATA_DOUBLE) value);
273 trim_zeros = 0;
274 }
275 else if(isgreaterequal(abs, 10)) len = snprintfz(value_string, value_string_len, "%0.1" NETDATA_DOUBLE_MODIFIER, (NETDATA_DOUBLE) value);
276 else if(isgreaterequal(abs, 1)) len = snprintfz(value_string, value_string_len, "%0.2" NETDATA_DOUBLE_MODIFIER, (NETDATA_DOUBLE) value);
277 else if(isgreaterequal(abs, 0.1)) len = snprintfz(value_string, value_string_len, "%0.2" NETDATA_DOUBLE_MODIFIER, (NETDATA_DOUBLE) value);
278 else if(isgreaterequal(abs, 0.01)) len = snprintfz(value_string, value_string_len, "%0.4" NETDATA_DOUBLE_MODIFIER, (NETDATA_DOUBLE) value);
279 else if(isgreaterequal(abs, 0.001)) len = snprintfz(value_string, value_string_len, "%0.5" NETDATA_DOUBLE_MODIFIER, (NETDATA_DOUBLE) value);
280 else if(isgreaterequal(abs, 0.0001)) len = snprintfz(value_string, value_string_len, "%0.6" NETDATA_DOUBLE_MODIFIER, (NETDATA_DOUBLE) value);
281 else len = snprintfz(value_string, value_string_len, "%0.7" NETDATA_DOUBLE_MODIFIER, (NETDATA_DOUBLE) value);
282
283 if(unlikely(trim_zeros)) {
284 int l;
285 // remove trailing zeros from the decimal part
286 for(l = len - 1; l > lstop; l--) {
287 if(likely(value_string[l] == '0')) {
288 value_string[l] = '\0';
289 len--;
290 }
291
292 else if(unlikely(value_string[l] == '.')) {
293 value_string[l] = '\0';
294 len--;
295 break;
296 }
297
298 else
299 break;
300 }
301 }
302
303 if(unlikely(len <= 0)) len = 1;
304 snprintfz(&value_string[len], value_string_len - len, "%s%s", separator, units);
305 }
306 else {
307 if(precision > 50) precision = 50;
308 snprintfz(value_string, value_string_len, "%0.*" NETDATA_DOUBLE_MODIFIER "%s%s", precision, (NETDATA_DOUBLE) value, separator, units);
309 }
310
311 return value_string;
312 }
313
314 typedef enum badge_units_format {
315 UNITS_FORMAT_NONE,
316 UNITS_FORMAT_SECONDS,
317 UNITS_FORMAT_SECONDS_AGO,
318 UNITS_FORMAT_MINUTES,
319 UNITS_FORMAT_MINUTES_AGO,
320 UNITS_FORMAT_HOURS,
321 UNITS_FORMAT_HOURS_AGO,
322 UNITS_FORMAT_ONOFF,
323 UNITS_FORMAT_UPDOWN,
324 UNITS_FORMAT_OKERROR,
325 UNITS_FORMAT_OKFAILED,
326 UNITS_FORMAT_EMPTY,
327 UNITS_FORMAT_PERCENT
328 } UNITS_FORMAT;
329
330
331 static struct units_formatter {
332 const char *units;
333 uint32_t hash;
334 UNITS_FORMAT format;
335 } badge_units_formatters[] = {
336 { "seconds", 0, UNITS_FORMAT_SECONDS },
337 { "seconds ago", 0, UNITS_FORMAT_SECONDS_AGO },
338 { "minutes", 0, UNITS_FORMAT_MINUTES },
339 { "minutes ago", 0, UNITS_FORMAT_MINUTES_AGO },
340 { "hours", 0, UNITS_FORMAT_HOURS },
341 { "hours ago", 0, UNITS_FORMAT_HOURS_AGO },
342 { "on/off", 0, UNITS_FORMAT_ONOFF },
343 { "on-off", 0, UNITS_FORMAT_ONOFF },
344 { "onoff", 0, UNITS_FORMAT_ONOFF },
345 { "up/down", 0, UNITS_FORMAT_UPDOWN },
346 { "up-down", 0, UNITS_FORMAT_UPDOWN },
347 { "updown", 0, UNITS_FORMAT_UPDOWN },
348 { "ok/error", 0, UNITS_FORMAT_OKERROR },
349 { "ok-error", 0, UNITS_FORMAT_OKERROR },
350 { "okerror", 0, UNITS_FORMAT_OKERROR },
351 { "ok/failed", 0, UNITS_FORMAT_OKFAILED },
352 { "ok-failed", 0, UNITS_FORMAT_OKFAILED },
353 { "okfailed", 0, UNITS_FORMAT_OKFAILED },
354 { "empty", 0, UNITS_FORMAT_EMPTY },
355 { "null", 0, UNITS_FORMAT_EMPTY },
356 { "percentage", 0, UNITS_FORMAT_PERCENT },
357 { "percent", 0, UNITS_FORMAT_PERCENT },
358 { "pcent", 0, UNITS_FORMAT_PERCENT },
359
360 // terminator
361 { NULL, 0, UNITS_FORMAT_NONE }
362 };
363
364 char *format_value_and_unit(char *value_string, size_t value_string_len,
365 NETDATA_DOUBLE value, const char *units, int precision) {
366 static int max = -1;
367 int i;
368
369 if(unlikely(max == -1)) {
370 for(i = 0; badge_units_formatters[i].units; i++)
371 badge_units_formatters[i].hash = simple_hash(badge_units_formatters[i].units);
372
373 max = i;
374 }
375
376 if(unlikely(!units)) units = "";
377 uint32_t hash_units = simple_hash(units);
378
379 UNITS_FORMAT format = UNITS_FORMAT_NONE;
380 for(i = 0; i < max; i++) {
381 struct units_formatter *ptr = &badge_units_formatters[i];
382
383 if(hash_units == ptr->hash && !strcmp(units, ptr->units)) {
384 format = ptr->format;
385 break;
386 }
387 }
388
389 if(unlikely(format == UNITS_FORMAT_SECONDS || format == UNITS_FORMAT_SECONDS_AGO)) {
390 if(value == 0.0) {
391 snprintfz(value_string, value_string_len, "%s", "now");
392 return value_string;
393 }
394 else if(isnan(value) || isinf(value)) {
395 snprintfz(value_string, value_string_len, "%s", "undefined");
396 return value_string;
397 }
398
399 const char *suffix = (format == UNITS_FORMAT_SECONDS_AGO)?" ago":"";
400
401 size_t s = (size_t)value;
402 size_t d = s / 86400;
403 s = s % 86400;
404
405 size_t h = s / 3600;
406 s = s % 3600;
407
408 size_t m = s / 60;
409 s = s % 60;
410
411 if(d)
412 snprintfz(value_string, value_string_len, "%zu %s %02zu:%02zu:%02zu%s", d, (d == 1)?"day":"days", h, m, s, suffix);
413 else
414 snprintfz(value_string, value_string_len, "%02zu:%02zu:%02zu%s", h, m, s, suffix);
415
416 return value_string;
417 }
418
419 else if(unlikely(format == UNITS_FORMAT_MINUTES || format == UNITS_FORMAT_MINUTES_AGO)) {
420 if(value == 0.0) {
421 snprintfz(value_string, value_string_len, "%s", "now");
422 return value_string;
423 }
424 else if(isnan(value) || isinf(value)) {
425 snprintfz(value_string, value_string_len, "%s", "undefined");
426 return value_string;
427 }
428
429 const char *suffix = (format == UNITS_FORMAT_MINUTES_AGO)?" ago":"";
430
431 size_t m = (size_t)value;
432 size_t d = m / (60 * 24);
433 m = m % (60 * 24);
434
435 size_t h = m / 60;
436 m = m % 60;
437
438 if(d)
439 snprintfz(value_string, value_string_len, "%zud %02zuh %02zum%s", d, h, m, suffix);
440 else
441 snprintfz(value_string, value_string_len, "%zuh %zum%s", h, m, suffix);
442
443 return value_string;
444 }
445
446 else if(unlikely(format == UNITS_FORMAT_HOURS || format == UNITS_FORMAT_HOURS_AGO)) {
447 if(value == 0.0) {
448 snprintfz(value_string, value_string_len, "%s", "now");
449 return value_string;
450 }
451 else if(isnan(value) || isinf(value)) {
452 snprintfz(value_string, value_string_len, "%s", "undefined");
453 return value_string;
454 }
455
456 const char *suffix = (format == UNITS_FORMAT_HOURS_AGO)?" ago":"";
457
458 size_t h = (size_t)value;
459 size_t d = h / 24;
460 h = h % 24;
461
462 if(d)
463 snprintfz(value_string, value_string_len, "%zud %zuh%s", d, h, suffix);
464 else
465 snprintfz(value_string, value_string_len, "%zuh%s", h, suffix);
466
467 return value_string;
468 }
469
470 else if(unlikely(format == UNITS_FORMAT_ONOFF)) {
471 snprintfz(value_string, value_string_len, "%s", (value != 0.0)?"on":"off");
472 return value_string;
473 }
474
475 else if(unlikely(format == UNITS_FORMAT_UPDOWN)) {
476 snprintfz(value_string, value_string_len, "%s", (value != 0.0)?"up":"down");
477 return value_string;
478 }
479
480 else if(unlikely(format == UNITS_FORMAT_OKERROR)) {
481 snprintfz(value_string, value_string_len, "%s", (value != 0.0)?"ok":"error");
482 return value_string;
483 }
484
485 else if(unlikely(format == UNITS_FORMAT_OKFAILED)) {
486 snprintfz(value_string, value_string_len, "%s", (value != 0.0)?"ok":"failed");
487 return value_string;
488 }
489
490 else if(unlikely(format == UNITS_FORMAT_EMPTY))
491 units = "";
492
493 else if(unlikely(format == UNITS_FORMAT_PERCENT))
494 units = "%";
495
496 if(unlikely(isnan(value) || isinf(value))) {
497 strcpy(value_string, "-");
498 return value_string;
499 }
500
501 return format_value_with_precision_and_unit(value_string, value_string_len, value, units, precision);
502 }
503
504 static struct badge_color {
505 const char *name;
506 uint32_t hash;
507 const char *color;
508 } badge_colors[] = {
509
510 // colors from:
511 // https://github.com/badges/shields/blob/master/colorscheme.json
512
513 { "brightgreen", 0, "4c1" },
514 { "green", 0, "97CA00" },
515 { "yellow", 0, "dfb317" },
516 { "yellowgreen", 0, "a4a61d" },
517 { "orange", 0, "fe7d37" },
518 { "red", 0, "e05d44" },
519 { "blue", 0, "007ec6" },
520 { "grey", 0, "555" },
521 { "gray", 0, "555" },
522 { "lightgrey", 0, "9f9f9f" },
523 { "lightgray", 0, "9f9f9f" },
524
525 // terminator
526 { NULL, 0, NULL }
527 };
528
529 static inline const char *color_map(const char *color, const char *def) {
530 static int max = -1;
531 int i;
532
533 if(unlikely(max == -1)) {
534 for(i = 0; badge_colors[i].name ;i++)
535 badge_colors[i].hash = simple_hash(badge_colors[i].name);
536
537 max = i;
538 }
539
540 uint32_t hash = simple_hash(color);
541
542 for(i = 0; i < max; i++) {
543 struct badge_color *ptr = &badge_colors[i];
544
545 if(hash == ptr->hash && !strcmp(color, ptr->name))
546 return ptr->color;
547 }
548
549 return def;
550 }
551
552 typedef enum color_comparison {
553 COLOR_COMPARE_EQUAL,
554 COLOR_COMPARE_NOTEQUAL,
555 COLOR_COMPARE_LESS,
556 COLOR_COMPARE_LESSEQUAL,
557 COLOR_COMPARE_GREATER,
558 COLOR_COMPARE_GREATEREQUAL,
559 } BADGE_COLOR_COMPARISON;
560
561 static inline void calc_colorz(const char *color, char *final, size_t len, NETDATA_DOUBLE value) {
562 if(isnan(value) || isinf(value))
563 value = NAN;
564
565 char color_buffer[256 + 1] = "";
566 char value_buffer[256 + 1] = "";
567 BADGE_COLOR_COMPARISON comparison = COLOR_COMPARE_GREATER;
568
569 // example input:
570 // color<max|color>min|color:null...
571
572 const char *c = color;
573 while(*c) {
574 char *dc = color_buffer, *dv = NULL;
575 size_t ci = 0, vi = 0;
576
577 const char *t = c;
578
579 while(*t && *t != '|') {
580 switch(*t) {
581 case '!':
582 if(t[1] == '=') t++;
583 comparison = COLOR_COMPARE_NOTEQUAL;
584 dv = value_buffer;
585 break;
586
587 case '=':
588 case ':':
589 comparison = COLOR_COMPARE_EQUAL;
590 dv = value_buffer;
591 break;
592
593 case '}':
594 case ')':
595 case '>':
596 if(t[1] == '=') {
597 comparison = COLOR_COMPARE_GREATEREQUAL;
598 t++;
599 }
600 else
601 comparison = COLOR_COMPARE_GREATER;
602 dv = value_buffer;
603 break;
604
605 case '{':
606 case '(':
607 case '<':
608 if(t[1] == '=') {
609 comparison = COLOR_COMPARE_LESSEQUAL;
610 t++;
611 }
612 else if(t[1] == '>' || t[1] == ')' || t[1] == '}') {
613 comparison = COLOR_COMPARE_NOTEQUAL;
614 t++;
615 }
616 else
617 comparison = COLOR_COMPARE_LESS;
618 dv = value_buffer;
619 break;
620
621 default:
622 if(dv) {
623 if(vi < 256) {
624 vi++;
625 *dv++ = *t;
626 }
627 }
628 else {
629 if(ci < 256) {
630 ci++;
631 *dc++ = *t;
632 }
633 }
634 break;
635 }
636
637 t++;
638 }
639
640 // prepare for next iteration
641 if(*t == '|') t++;
642 c = t;
643
644 // do the math
645 *dc = '\0';
646 if(dv) {
647 *dv = '\0';
648 NETDATA_DOUBLE v;
649
650 if(!*value_buffer || !strcmp(value_buffer, "null")) {
651 v = NAN;
652 }
653 else {
654 v = str2l(value_buffer);
655 if(isnan(v) || isinf(v))
656 v = NAN;
657 }
658
659 if(unlikely(isnan(value) || isnan(v))) {
660 if(isnan(value) && isnan(v))
661 break;
662 }
663 else {
664 if (unlikely(comparison == COLOR_COMPARE_LESS && isless(value, v))) break;
665 else if (unlikely(comparison == COLOR_COMPARE_LESSEQUAL && islessequal(value, v))) break;
666 else if (unlikely(comparison == COLOR_COMPARE_GREATER && isgreater(value, v))) break;
667 else if (unlikely(comparison == COLOR_COMPARE_GREATEREQUAL && isgreaterequal(value, v))) break;
668 else if (unlikely(comparison == COLOR_COMPARE_EQUAL && !islessgreater(value, v))) break;
669 else if (unlikely(comparison == COLOR_COMPARE_NOTEQUAL && islessgreater(value, v))) break;
670 }
671 }
672 else
673 break;
674 }
675
676 const char *b;
677 if(color_buffer[0])
678 b = color_buffer;
679 else
680 b = color;
681
682 strncpyz(final, b, len);
683 }
684
685 // value + units
686 #define VALUE_STRING_SIZE 100
687
688 // label
689 #define LABEL_STRING_SIZE 200
690
691 // colors
692 #define COLOR_STRING_SIZE 100
693
694 static inline int allowed_hexa_char(char x) {
695 return ( (x >= '0' && x <= '9') ||
696 (x >= 'a' && x <= 'f') ||
697 (x >= 'A' && x <= 'F')
698 );
699 }
700
701 static int html_color_check(const char *str) {
702 int i = 0;
703 while(str[i]) {
704 if(!allowed_hexa_char(str[i]))
705 return 0;
706 if(unlikely(i >= 6))
707 return 0;
708 i++;
709 }
710 // want to allow either RGB or RRGGBB
711 return ( i == 6 || i == 3 );
712 }
713
714 // Will parse color arg as #RRGGBB or #RGB or one of the colors
715 // from color_map hash table
716 // if parsing fails (argument error) it will return default color
717 // given as default parameter (def)
718 // in any case it will return either color in "RRGGBB" or "RGB" format as string
719 // or whatever is given as def (without checking - caller responsible to give sensible
720 // safely escaped default) as default if it fails
721 // in any case this function must always return something we can put directly in XML
722 // so no escaping is necessary anymore (with exception of default where caller is responsible)
723 // to give sensible default
724 #define BADGE_SVG_COLOR_ARG_MAXLEN 20
725
726 static const char *parse_color_argument(const char *arg, const char *def)
727 {
728 if( !arg )
729 return def;
730 size_t len = strnlen(arg, BADGE_SVG_COLOR_ARG_MAXLEN);
731 if( len < 2 || len >= BADGE_SVG_COLOR_ARG_MAXLEN )
732 return def;
733 if( html_color_check(arg) )
734 return arg;
735 return color_map(arg, def);
736 }
737
738 static void buffer_svg(BUFFER *wb, const char *label,
739 NETDATA_DOUBLE value, const char *units, const char *label_color, const char *value_color, int precision, int scale, uint32_t options, int fixed_width_lbl, int fixed_width_val, const char* text_color_lbl, const char* text_color_val) {
740 char value_color_buffer[COLOR_STRING_SIZE + 1]
741 , value_string[VALUE_STRING_SIZE + 1]
742 , label_escaped[LABEL_STRING_SIZE + 1]
743 , value_escaped[VALUE_STRING_SIZE + 1];
744
745 const char *label_color_parsed;
746 const char *value_color_parsed;
747
748 double label_width = (double)fixed_width_lbl, value_width = (double)fixed_width_val, total_width;
749 double height = 20.0, font_size = 11.0, text_offset = 5.8, round_corner = 3.0;
750
751 if(scale < 100) scale = 100;
752
753 if(unlikely(!value_color || !*value_color))
754 value_color = (isnan(value) || isinf(value))?"999":"4c1";
755
756 calc_colorz(value_color, value_color_buffer, COLOR_STRING_SIZE, value);
757 format_value_and_unit(value_string, VALUE_STRING_SIZE, (options & RRDR_OPTION_DISPLAY_ABS)? fabsndd(value):value, units, precision);
758
759 if(fixed_width_lbl <= 0 || fixed_width_val <= 0) {
760 label_width = verdana11_width(label, font_size) + (BADGE_HORIZONTAL_PADDING * 2);
761 value_width = verdana11_width(value_string, font_size) + (BADGE_HORIZONTAL_PADDING * 2);
762 }
763 total_width = label_width + value_width;
764
765 escape_xmlz(label_escaped, label, LABEL_STRING_SIZE);
766 escape_xmlz(value_escaped, value_string, VALUE_STRING_SIZE);
767
768 label_color_parsed = parse_color_argument(label_color, "555");
769 value_color_parsed = parse_color_argument(value_color_buffer, "555");
770
771 wb->content_type = CT_IMAGE_SVG_XML;
772
773 total_width = total_width * scale / 100.0;
774 height = height * scale / 100.0;
775 font_size = font_size * scale / 100.0;
776 text_offset = text_offset * scale / 100.0;
777 label_width = label_width * scale / 100.0;
778 value_width = value_width * scale / 100.0;
779 round_corner = round_corner * scale / 100.0;
780
781 // svg template from:
782 // https://raw.githubusercontent.com/badges/shields/master/templates/flat-template.svg
783 buffer_sprintf(wb,
784 "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"%0.2f\" height=\"%0.2f\">"
785 "<linearGradient id=\"smooth\" x2=\"0\" y2=\"100%%\">"
786 "<stop offset=\"0\" stop-color=\"#bbb\" stop-opacity=\".1\"/>"
787 "<stop offset=\"1\" stop-opacity=\".1\"/>"
788 "</linearGradient>"
789 "<mask id=\"round\">"
790 "<rect class=\"bdge-ttl-width\" width=\"%0.2f\" height=\"%0.2f\" rx=\"%0.2f\" fill=\"#fff\"/>"
791 "</mask>"
792 "<g mask=\"url(#round)\">"
793 "<rect class=\"bdge-rect-lbl\" width=\"%0.2f\" height=\"%0.2f\" fill=\"#%s\"/>",
794 total_width, height,
795 total_width, height, round_corner,
796 label_width, height, label_color_parsed); //<rect class="bdge-rect-lbl"
797
798 if(fixed_width_lbl > 0 && fixed_width_val > 0) {
799 buffer_sprintf(wb,
800 "<clipPath id=\"lbl-rect\">"
801 "<rect class=\"bdge-rect-lbl\" width=\"%0.2f\" height=\"%0.2f\"/>"
802 "</clipPath>",
803 label_width, height); //<clipPath id="lbl-rect"> <rect class="bdge-rect-lbl"
804 }
805
806 buffer_sprintf(wb,
807 "<rect class=\"bdge-rect-val\" x=\"%0.2f\" width=\"%0.2f\" height=\"%0.2f\" fill=\"#%s\"/>",
808 label_width, value_width, height, value_color_parsed);
809
810 if(fixed_width_lbl > 0 && fixed_width_val > 0) {
811 buffer_sprintf(wb,
812 "<clipPath id=\"val-rect\">"
813 "<rect class=\"bdge-rect-val\" x=\"%0.2f\" width=\"%0.2f\" height=\"%0.2f\"/>"
814 "</clipPath>",
815 label_width, value_width, height);
816 }
817
818 buffer_sprintf(wb,
819 "<rect class=\"bdge-ttl-width\" width=\"%0.2f\" height=\"%0.2f\" fill=\"url(#smooth)\"/>"
820 "</g>"
821 "<g text-anchor=\"middle\" font-family=\"DejaVu Sans,Verdana,Geneva,sans-serif\" font-size=\"%0.2f\">"
822 "<text class=\"bdge-lbl-lbl\" x=\"%0.2f\" y=\"%0.0f\" fill=\"#010101\" fill-opacity=\".3\" clip-path=\"url(#lbl-rect)\">%s</text>"
823 "<text class=\"bdge-lbl-lbl\" x=\"%0.2f\" y=\"%0.0f\" fill=\"#%s\" clip-path=\"url(#lbl-rect)\">%s</text>"
824 "<text class=\"bdge-lbl-val\" x=\"%0.2f\" y=\"%0.0f\" fill=\"#010101\" fill-opacity=\".3\" clip-path=\"url(#val-rect)\">%s</text>"
825 "<text class=\"bdge-lbl-val\" x=\"%0.2f\" y=\"%0.0f\" fill=\"#%s\" clip-path=\"url(#val-rect)\">%s</text>"
826 "</g>",
827 total_width, height,
828 font_size,
829 label_width / 2, ceil(height - text_offset), label_escaped,
830 label_width / 2, ceil(height - text_offset - 1.0), parse_color_argument(text_color_lbl, "fff"), label_escaped,
831 label_width + value_width / 2 -1, ceil(height - text_offset), value_escaped,
832 label_width + value_width / 2 -1, ceil(height - text_offset - 1.0), parse_color_argument(text_color_val, "fff"), value_escaped);
833
834 if(fixed_width_lbl <= 0 || fixed_width_val <= 0){
835 buffer_sprintf(wb,
836 "<script type=\"text/javascript\">"
837 "var bdg_horiz_padding = %d;"
838 "function netdata_bdge_each(list, attr, value){"
839 "Array.prototype.forEach.call(list, function(el){"
840 "el.setAttribute(attr, value);"
841 "});"
842 "};"
843 "var this_svg = document.currentScript.closest(\"svg\");"
844 "var elem_lbl = this_svg.getElementsByClassName(\"bdge-lbl-lbl\");"
845 "var elem_val = this_svg.getElementsByClassName(\"bdge-lbl-val\");"
846 "var lbl_size = elem_lbl[0].getBBox();"
847 "var val_size = elem_val[0].getBBox();"
848 "var width_total = lbl_size.width + bdg_horiz_padding*2;"
849 "this_svg.getElementsByClassName(\"bdge-rect-lbl\")[0].setAttribute(\"width\", width_total);"
850 "netdata_bdge_each(elem_lbl, \"x\", (lbl_size.width / 2) + bdg_horiz_padding);"
851 "netdata_bdge_each(elem_val, \"x\", width_total + (val_size.width / 2) + bdg_horiz_padding);"
852 "var val_rect = this_svg.getElementsByClassName(\"bdge-rect-val\")[0];"
853 "val_rect.setAttribute(\"width\", val_size.width + bdg_horiz_padding*2);"
854 "val_rect.setAttribute(\"x\", width_total);"
855 "width_total += val_size.width + bdg_horiz_padding*2;"
856 "var width_update_elems = this_svg.getElementsByClassName(\"bdge-ttl-width\");"
857 "netdata_bdge_each(width_update_elems, \"width\", width_total);"
858 "this_svg.setAttribute(\"width\", width_total);"
859 "</script>",
860 BADGE_HORIZONTAL_PADDING);
861 }
862 buffer_sprintf(wb, "</svg>");
863 }
864
865 #define BADGE_URL_ARG_LBL_COLOR "text_color_lbl"
866 #define BADGE_URL_ARG_VAL_COLOR "text_color_val"
867
868 int api_v1_badge(RRDHOST *host, struct web_client *w, char *url) {
869 int ret = HTTP_RESP_BAD_REQUEST;
870 buffer_flush(w->response.data);
871
872 BUFFER *dimensions = NULL;
873
874 const char *chart = NULL
875 , *before_str = NULL
876 , *after_str = NULL
877 , *points_str = NULL
878 , *multiply_str = NULL
879 , *divide_str = NULL
880 , *label = NULL
881 , *units = NULL
882 , *label_color = NULL
883 , *value_color = NULL
884 , *refresh_str = NULL
885 , *precision_str = NULL
886 , *scale_str = NULL
887 , *alarm = NULL
888 , *fixed_width_lbl_str = NULL
889 , *fixed_width_val_str = NULL
890 , *text_color_lbl_str = NULL
891 , *text_color_val_str = NULL
892 , *group_options = NULL;
893
894 int group = RRDR_GROUPING_AVERAGE;
895 uint32_t options = 0x00000000;
896
897 const RRDCALC_ACQUIRED *rca = NULL;
898 RRDCALC *rc = NULL;
899 RRDSET *st = NULL;
900
901 while(url) {
902 char *value = strsep_skip_consecutive_separators(&url, "&");
903 if(!value || !*value) continue;
904
905 char *name = strsep_skip_consecutive_separators(&value, "=");
906 if(!name || !*name) continue;
907 if(!value || !*value) continue;
908
909 netdata_log_debug(D_WEB_CLIENT, "%llu: API v1 badge.svg query param '%s' with value '%s'", w->id, name, value);
910
911 // name and value are now the parameters
912 // they are not null and not empty
913
914 if(!strcmp(name, "chart")) chart = value;
915 else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) {
916 if(!dimensions)
917 dimensions = buffer_create(100, &netdata_buffers_statistics.buffers_api);
918
919 buffer_strcat(dimensions, "|");
920 buffer_strcat(dimensions, value);
921 }
922 else if(!strcmp(name, "after")) after_str = value;
923 else if(!strcmp(name, "before")) before_str = value;
924 else if(!strcmp(name, "points")) points_str = value;
925 else if(!strcmp(name, "group_options")) group_options = value;
926 else if(!strcmp(name, "group")) {
927 group = time_grouping_parse(value, RRDR_GROUPING_AVERAGE);
928 }
929 else if(!strcmp(name, "options")) {
930 options |= rrdr_options_parse(value);
931 }
932 else if(!strcmp(name, "label")) label = value;
933 else if(!strcmp(name, "units")) units = value;
934 else if(!strcmp(name, "label_color")) label_color = value;
935 else if(!strcmp(name, "value_color")) value_color = value;
936 else if(!strcmp(name, "multiply")) multiply_str = value;
937 else if(!strcmp(name, "divide")) divide_str = value;
938 else if(!strcmp(name, "refresh")) refresh_str = value;
939 else if(!strcmp(name, "precision")) precision_str = value;
940 else if(!strcmp(name, "scale")) scale_str = value;
941 else if(!strcmp(name, "fixed_width_lbl")) fixed_width_lbl_str = value;
942 else if(!strcmp(name, "fixed_width_val")) fixed_width_val_str = value;
943 else if(!strcmp(name, "alarm")) alarm = value;
944 else if(!strcmp(name, BADGE_URL_ARG_LBL_COLOR)) text_color_lbl_str = value;
945 else if(!strcmp(name, BADGE_URL_ARG_VAL_COLOR)) text_color_val_str = value;
946 }
947
948 int fixed_width_lbl = -1;
949 int fixed_width_val = -1;
950
951 if(fixed_width_lbl_str && *fixed_width_lbl_str
952 && fixed_width_val_str && *fixed_width_val_str) {
953 fixed_width_lbl = str2i(fixed_width_lbl_str);
954 fixed_width_val = str2i(fixed_width_val_str);
955 }
956
957 if(!chart || !*chart) {
958 buffer_no_cacheable(w->response.data);
959 buffer_sprintf(w->response.data, "No chart id is given at the request.");
960 goto cleanup;
961 }
962
963 int scale = (scale_str && *scale_str)?str2i(scale_str):100;
964
965 st = rrdset_find(host, chart, false);
966 if(!st) st = rrdset_find_byname(host, chart);
967 if(!st) {
968 buffer_no_cacheable(w->response.data);
969 buffer_svg(w->response.data, "chart not found", NAN, "", NULL, NULL, -1, scale, 0, -1, -1, NULL, NULL);
970 ret = HTTP_RESP_OK;
971 goto cleanup;
972 }
973 st->last_accessed_time_s = now_realtime_sec();
974
975 if(alarm) {
976 rca = rrdcalc_from_rrdset_get(st, alarm);
977 rc = rrdcalc_acquired_to_rrdcalc(rca);
978
979 if (!rc) {
980 buffer_no_cacheable(w->response.data);
981 buffer_svg(w->response.data, "alarm not found", NAN, "", NULL, NULL, -1, scale, 0, -1, -1, NULL, NULL);
982 ret = HTTP_RESP_OK;
983 goto cleanup;
984 }
985 }
986
987 long long multiply = (multiply_str && *multiply_str )?str2l(multiply_str):1;
988 long long divide = (divide_str && *divide_str )?str2l(divide_str):1;
989 long long before = (before_str && *before_str )?str2l(before_str):0;
990 long long after = (after_str && *after_str )?str2l(after_str):-st->update_every;
991 int points = (points_str && *points_str )?str2i(points_str):1;
992 int precision = (precision_str && *precision_str)?str2i(precision_str):-1;
993
994 if(!multiply) multiply = 1;
995 if(!divide) divide = 1;
996
997 int refresh = 0;
998 if(refresh_str && *refresh_str) {
999 if(!strcmp(refresh_str, "auto")) {
1000 if(rc) refresh = rc->config.update_every;
1001 else if(options & RRDR_OPTION_NOT_ALIGNED)
1002 refresh = st->update_every;
1003 else {
1004 refresh = (int)(before - after);
1005 if(refresh < 0) refresh = -refresh;
1006 }
1007 }
1008 else {
1009 refresh = str2i(refresh_str);
1010 if(refresh < 0) refresh = -refresh;
1011 }
1012 }
1013
1014 if(!label) {
1015 if(alarm) {
1016 char *s = (char *)alarm;
1017 while(*s) {
1018 if(*s == '_') *s = ' ';
1019 s++;
1020 }
1021 label = alarm;
1022 }
1023 else if(dimensions) {
1024 const char *dim = buffer_tostring(dimensions);
1025 if(*dim == '|') dim++;
1026 label = dim;
1027 }
1028 else
1029 label = rrdset_name(st);
1030 }
1031 if(!units) {
1032 if(alarm) {
1033 if(rc->config.units)
1034 units = rrdcalc_units(rc);
1035 else
1036 units = "";
1037 }
1038 else if(options & RRDR_OPTION_PERCENTAGE)
1039 units = "%";
1040 else
1041 units = rrdset_units(st);
1042 }
1043
1044 netdata_log_debug(D_WEB_CLIENT, "%llu: API command 'badge.svg' for chart '%s', alarm '%s', dimensions '%s', after '%lld', before '%lld', points '%d', group '%d', options '0x%08x'"
1045 , w->id
1046 , chart
1047 , alarm?alarm:""
1048 , (dimensions)?buffer_tostring(dimensions):""
1049 , after
1050 , before
1051 , points
1052 , group
1053 , options
1054 );
1055
1056 if(rc) {
1057 if (refresh > 0) {
1058 buffer_sprintf(w->response.header, "Refresh: %d\r\n", refresh);
1059 w->response.data->date = now_realtime_sec();
1060 w->response.data->expires = w->response.data->date + refresh;
1061 buffer_cacheable(w->response.data);
1062 }
1063 else
1064 buffer_no_cacheable(w->response.data);
1065
1066 if(!value_color) {
1067 switch(rc->status) {
1068 case RRDCALC_STATUS_CRITICAL:
1069 value_color = "red";
1070 break;
1071
1072 case RRDCALC_STATUS_WARNING:
1073 value_color = "orange";
1074 break;
1075
1076 case RRDCALC_STATUS_CLEAR:
1077 value_color = "brightgreen";
1078 break;
1079
1080 case RRDCALC_STATUS_UNDEFINED:
1081 value_color = "lightgrey";
1082 break;
1083
1084 case RRDCALC_STATUS_UNINITIALIZED:
1085 value_color = "#000";
1086 break;
1087
1088 default:
1089 value_color = "grey";
1090 break;
1091 }
1092 }
1093
1094 buffer_svg(w->response.data,
1095 label,
1096 (isnan(rc->value)||isinf(rc->value)) ? rc->value : rc->value * multiply / divide,
1097 units,
1098 label_color,
1099 value_color,
1100 precision,
1101 scale,
1102 options,
1103 fixed_width_lbl,
1104 fixed_width_val,
1105 text_color_lbl_str,
1106 text_color_val_str
1107 );
1108 ret = HTTP_RESP_OK;
1109 }
1110 else {
1111 time_t latest_timestamp = 0;
1112 int value_is_null = 1;
1113 NETDATA_DOUBLE n = NAN;
1114 ret = HTTP_RESP_INTERNAL_SERVER_ERROR;
1115
1116 // if the collected value is too old, don't calculate its value
1117 if (rrdset_last_entry_s(st) >= (now_realtime_sec() - (st->update_every * gap_when_lost_iterations_above)))
1118 ret = rrdset2value_api_v1(st, w->response.data, &n,
1119 (dimensions) ? buffer_tostring(dimensions) : NULL,
1120 points, after, before, group, group_options, 0, options,
1121 NULL, &latest_timestamp,
1122 NULL, NULL, NULL,
1123 &value_is_null, NULL, 0, 0,
1124 QUERY_SOURCE_API_BADGE, STORAGE_PRIORITY_SYNCHRONOUS_FIRST);
1125
1126 // if the value cannot be calculated, show empty badge
1127 if (ret != HTTP_RESP_OK) {
1128 buffer_no_cacheable(w->response.data);
1129 value_is_null = 1;
1130 n = 0;
1131 ret = HTTP_RESP_OK;
1132 }
1133 else if (refresh > 0) {
1134 buffer_sprintf(w->response.header, "Refresh: %d\r\n", refresh);
1135 w->response.data->expires = now_realtime_sec() + refresh;
1136 }
1137 else buffer_no_cacheable(w->response.data);
1138
1139 // render the badge
1140 buffer_svg(w->response.data,
1141 label,
1142 (value_is_null)?NAN:(n * multiply / divide),
1143 units,
1144 label_color,
1145 value_color,
1146 precision,
1147 scale,
1148 options,
1149 fixed_width_lbl,
1150 fixed_width_val,
1151 text_color_lbl_str,
1152 text_color_val_str
1153 );
1154 }
1155
1156 cleanup:
1157 rrdcalc_from_rrdset_release(st, rca);
1158 buffer_free(dimensions);
1159 return ret;
1160 }