summaryrefslogtreecommitdiffstats
path: root/src/web/api
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-11-09 08:36:11 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-11-09 08:37:11 +0000
commit910c794ec6d0a364b4aabccf22b715cb45780e83 (patch)
tree561a9ef6b6a4668102674e1a52b3e7563c57ac61 /src/web/api
parentReleasing debian version 1.47.5-1. (diff)
downloadnetdata-debian.tar.xz
netdata-debian.zip
Merging upstream version 2.0.0 (Closes: #923993, #1042533, #1045145).HEADdebian
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/web/api')
-rw-r--r--src/web/api/badges/web_buffer_svg.h18
-rw-r--r--src/web/api/exporters/README.md9
-rw-r--r--src/web/api/exporters/allmetrics.c132
-rw-r--r--src/web/api/exporters/allmetrics.h12
-rw-r--r--src/web/api/exporters/prometheus/README.md9
-rw-r--r--src/web/api/exporters/shell/README.md15
-rw-r--r--src/web/api/exporters/shell/allmetrics_shell.h21
-rw-r--r--src/web/api/formatters/README.md9
-rw-r--r--src/web/api/formatters/charts2json.c2
-rw-r--r--src/web/api/formatters/csv/README.md9
-rw-r--r--src/web/api/formatters/json/README.md9
-rw-r--r--src/web/api/formatters/rrd2json.c40
-rw-r--r--src/web/api/formatters/rrd2json.h35
-rw-r--r--src/web/api/formatters/ssv/README.md9
-rw-r--r--src/web/api/formatters/value/README.md9
-rw-r--r--src/web/api/functions/function-bearer_get_token.c81
-rw-r--r--src/web/api/functions/function-bearer_get_token.h14
-rw-r--r--src/web/api/functions/function-progress.c8
-rw-r--r--src/web/api/functions/function-progress.h10
-rw-r--r--src/web/api/functions/function-streaming.c627
-rw-r--r--src/web/api/functions/function-streaming.h12
-rw-r--r--src/web/api/functions/functions.c43
-rw-r--r--src/web/api/functions/functions.h14
-rw-r--r--src/web/api/health/README.md12
-rw-r--r--src/web/api/http_auth.c338
-rw-r--r--src/web/api/http_auth.h2
-rw-r--r--src/web/api/ilove/ilove.h13
-rw-r--r--src/web/api/maps/contexts_alert_statuses.c60
-rw-r--r--src/web/api/maps/contexts_alert_statuses.h26
-rw-r--r--src/web/api/maps/contexts_options.c58
-rw-r--r--src/web/api/maps/contexts_options.h22
-rw-r--r--src/web/api/maps/datasource_formats.c89
-rw-r--r--src/web/api/maps/datasource_formats.h32
-rw-r--r--src/web/api/maps/maps.h12
-rw-r--r--src/web/api/maps/rrdr_options.c139
-rw-r--r--src/web/api/maps/rrdr_options.h52
-rw-r--r--src/web/api/queries/average/README.md9
-rw-r--r--src/web/api/queries/countif/README.md9
-rw-r--r--src/web/api/queries/des/README.md9
-rw-r--r--src/web/api/queries/incremental_sum/README.md9
-rw-r--r--src/web/api/queries/max/README.md9
-rw-r--r--src/web/api/queries/median/README.md10
-rw-r--r--src/web/api/queries/min/README.md9
-rw-r--r--src/web/api/queries/percentile/README.md10
-rw-r--r--src/web/api/queries/rrdr.h56
-rw-r--r--src/web/api/queries/ses/README.md9
-rw-r--r--src/web/api/queries/stddev/README.md9
-rw-r--r--src/web/api/queries/sum/README.md9
-rw-r--r--src/web/api/queries/trimmed_mean/README.md10
-rw-r--r--src/web/api/queries/weights.c16
-rw-r--r--src/web/api/queries/weights.h2
-rw-r--r--src/web/api/v1/api_v1_aclk.c20
-rw-r--r--src/web/api/v1/api_v1_alarms.c153
-rw-r--r--src/web/api/v1/api_v1_allmetrics.c (renamed from src/web/api/exporters/shell/allmetrics_shell.c)140
-rw-r--r--src/web/api/v1/api_v1_badge/README.md (renamed from src/web/api/badges/README.md)9
-rw-r--r--src/web/api/v1/api_v1_badge/web_buffer_svg.c (renamed from src/web/api/badges/web_buffer_svg.c)9
-rw-r--r--src/web/api/v1/api_v1_calls.h47
-rw-r--r--src/web/api/v1/api_v1_charts.c64
-rw-r--r--src/web/api/v1/api_v1_config.c92
-rw-r--r--src/web/api/v1/api_v1_context.c68
-rw-r--r--src/web/api/v1/api_v1_contexts.c61
-rw-r--r--src/web/api/v1/api_v1_data.c246
-rw-r--r--src/web/api/v1/api_v1_dbengine.c97
-rw-r--r--src/web/api/v1/api_v1_function.c44
-rw-r--r--src/web/api/v1/api_v1_functions.c19
-rw-r--r--src/web/api/v1/api_v1_info.c207
-rw-r--r--src/web/api/v1/api_v1_manage.c86
-rw-r--r--src/web/api/v1/api_v1_ml_info.c28
-rw-r--r--src/web/api/v1/api_v1_registry.c198
-rw-r--r--src/web/api/v1/api_v1_weights.c11
-rw-r--r--src/web/api/v2/api_v2_alert_config.c32
-rw-r--r--src/web/api/v2/api_v2_alert_transitions.c7
-rw-r--r--src/web/api/v2/api_v2_alerts.c7
-rw-r--r--src/web/api/v2/api_v2_bearer.c139
-rw-r--r--src/web/api/v2/api_v2_calls.h38
-rw-r--r--src/web/api/v2/api_v2_claim.c236
-rw-r--r--src/web/api/v2/api_v2_contexts.c78
-rw-r--r--src/web/api/v2/api_v2_data.c302
-rw-r--r--src/web/api/v2/api_v2_functions.c8
-rw-r--r--src/web/api/v2/api_v2_ilove/README.md (renamed from src/web/api/ilove/README.md)0
-rw-r--r--src/web/api/v2/api_v2_ilove/ilove.c (renamed from src/web/api/ilove/ilove.c)4
-rw-r--r--src/web/api/v2/api_v2_ilove/measure-text.js (renamed from src/web/api/ilove/measure-text.js)0
-rw-r--r--src/web/api/v2/api_v2_info.c7
-rw-r--r--src/web/api/v2/api_v2_node_instances.c10
-rw-r--r--src/web/api/v2/api_v2_nodes.c7
-rw-r--r--src/web/api/v2/api_v2_progress.c28
-rw-r--r--src/web/api/v2/api_v2_q.c9
-rw-r--r--src/web/api/v2/api_v2_versions.c7
-rw-r--r--src/web/api/v2/api_v2_webrtc.c8
-rw-r--r--src/web/api/v2/api_v2_weights.c152
-rw-r--r--src/web/api/v3/api_v3_calls.h11
-rw-r--r--src/web/api/v3/api_v3_me.c37
-rw-r--r--src/web/api/v3/api_v3_settings.c285
-rw-r--r--src/web/api/web_api.c221
-rw-r--r--src/web/api/web_api.h22
-rw-r--r--src/web/api/web_api_v1.c1874
-rw-r--r--src/web/api/web_api_v1.h36
-rw-r--r--src/web/api/web_api_v2.c648
-rw-r--r--src/web/api/web_api_v3.c263
-rw-r--r--src/web/api/web_api_v3.h12
100 files changed, 5083 insertions, 3191 deletions
diff --git a/src/web/api/badges/web_buffer_svg.h b/src/web/api/badges/web_buffer_svg.h
deleted file mode 100644
index 71857811..00000000
--- a/src/web/api/badges/web_buffer_svg.h
+++ /dev/null
@@ -1,18 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#ifndef NETDATA_WEB_BUFFER_SVG_H
-#define NETDATA_WEB_BUFFER_SVG_H 1
-
-#include "libnetdata/libnetdata.h"
-#include "web/server/web_client.h"
-
-void buffer_svg(BUFFER *wb, const char *label,
- 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);
-char *format_value_and_unit(char *value_string, size_t value_string_len,
- NETDATA_DOUBLE value, const char *units, int precision);
-
-int web_client_api_request_v1_badge(struct rrdhost *host, struct web_client *w, char *url);
-
-#include "web/api/web_api_v1.h"
-
-#endif /* NETDATA_WEB_BUFFER_SVG_H */
diff --git a/src/web/api/exporters/README.md b/src/web/api/exporters/README.md
index 20693796..47b44348 100644
--- a/src/web/api/exporters/README.md
+++ b/src/web/api/exporters/README.md
@@ -1,12 +1,3 @@
-<!--
-title: "Exporters"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/src/web/api/exporters/README.md
-sidebar_label: "Exporters"
-learn_status: "Published"
-learn_topic_type: "References"
-learn_rel_path: "Developers/Web/Api"
--->
-
# Exporters
TBD
diff --git a/src/web/api/exporters/allmetrics.c b/src/web/api/exporters/allmetrics.c
deleted file mode 100644
index 55179c0a..00000000
--- a/src/web/api/exporters/allmetrics.c
+++ /dev/null
@@ -1,132 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#include "allmetrics.h"
-
-struct prometheus_output_options {
- char *name;
- PROMETHEUS_OUTPUT_OPTIONS flag;
-} prometheus_output_flags_root[] = {
- { "names", PROMETHEUS_OUTPUT_NAMES },
- { "timestamps", PROMETHEUS_OUTPUT_TIMESTAMPS },
- { "variables", PROMETHEUS_OUTPUT_VARIABLES },
- { "oldunits", PROMETHEUS_OUTPUT_OLDUNITS },
- { "hideunits", PROMETHEUS_OUTPUT_HIDEUNITS },
- // terminator
- { NULL, PROMETHEUS_OUTPUT_NONE },
-};
-
-inline int web_client_api_request_v1_allmetrics(RRDHOST *host, struct web_client *w, char *url) {
- int format = ALLMETRICS_SHELL;
- const char *filter = NULL;
- const char *prometheus_server = w->client_ip;
-
- uint32_t prometheus_exporting_options;
- if (prometheus_exporter_instance)
- prometheus_exporting_options = prometheus_exporter_instance->config.options;
- else
- prometheus_exporting_options = global_exporting_options;
-
- PROMETHEUS_OUTPUT_OPTIONS prometheus_output_options =
- PROMETHEUS_OUTPUT_TIMESTAMPS |
- ((prometheus_exporting_options & EXPORTING_OPTION_SEND_NAMES) ? PROMETHEUS_OUTPUT_NAMES : 0);
-
- const char *prometheus_prefix;
- if (prometheus_exporter_instance)
- prometheus_prefix = prometheus_exporter_instance->config.prefix;
- else
- prometheus_prefix = global_exporting_prefix;
-
- while(url) {
- char *value = strsep_skip_consecutive_separators(&url, "&");
- if (!value || !*value) continue;
-
- char *name = strsep_skip_consecutive_separators(&value, "=");
- if(!name || !*name) continue;
- if(!value || !*value) continue;
-
- if(!strcmp(name, "format")) {
- if(!strcmp(value, ALLMETRICS_FORMAT_SHELL))
- format = ALLMETRICS_SHELL;
- else if(!strcmp(value, ALLMETRICS_FORMAT_PROMETHEUS))
- format = ALLMETRICS_PROMETHEUS;
- else if(!strcmp(value, ALLMETRICS_FORMAT_PROMETHEUS_ALL_HOSTS))
- format = ALLMETRICS_PROMETHEUS_ALL_HOSTS;
- else if(!strcmp(value, ALLMETRICS_FORMAT_JSON))
- format = ALLMETRICS_JSON;
- else
- format = 0;
- }
- else if(!strcmp(name, "filter")) {
- filter = value;
- }
- else if(!strcmp(name, "server")) {
- prometheus_server = value;
- }
- else if(!strcmp(name, "prefix")) {
- prometheus_prefix = value;
- }
- else if(!strcmp(name, "data") || !strcmp(name, "source") || !strcmp(name, "data source") || !strcmp(name, "data-source") || !strcmp(name, "data_source") || !strcmp(name, "datasource")) {
- prometheus_exporting_options = exporting_parse_data_source(value, prometheus_exporting_options);
- }
- else {
- int i;
- for(i = 0; prometheus_output_flags_root[i].name ; i++) {
- if(!strcmp(name, prometheus_output_flags_root[i].name)) {
- if(!strcmp(value, "yes") || !strcmp(value, "1") || !strcmp(value, "true"))
- prometheus_output_options |= prometheus_output_flags_root[i].flag;
- else {
- prometheus_output_options &= ~prometheus_output_flags_root[i].flag;
- }
-
- break;
- }
- }
- }
- }
-
- buffer_flush(w->response.data);
- buffer_no_cacheable(w->response.data);
-
- switch(format) {
- case ALLMETRICS_JSON:
- w->response.data->content_type = CT_APPLICATION_JSON;
- rrd_stats_api_v1_charts_allmetrics_json(host, filter, w->response.data);
- return HTTP_RESP_OK;
-
- case ALLMETRICS_SHELL:
- w->response.data->content_type = CT_TEXT_PLAIN;
- rrd_stats_api_v1_charts_allmetrics_shell(host, filter, w->response.data);
- return HTTP_RESP_OK;
-
- case ALLMETRICS_PROMETHEUS:
- w->response.data->content_type = CT_PROMETHEUS;
- rrd_stats_api_v1_charts_allmetrics_prometheus_single_host(
- host
- , filter
- , w->response.data
- , prometheus_server
- , prometheus_prefix
- , prometheus_exporting_options
- , prometheus_output_options
- );
- return HTTP_RESP_OK;
-
- case ALLMETRICS_PROMETHEUS_ALL_HOSTS:
- w->response.data->content_type = CT_PROMETHEUS;
- rrd_stats_api_v1_charts_allmetrics_prometheus_all_hosts(
- host
- , filter
- , w->response.data
- , prometheus_server
- , prometheus_prefix
- , prometheus_exporting_options
- , prometheus_output_options
- );
- return HTTP_RESP_OK;
-
- default:
- w->response.data->content_type = CT_TEXT_PLAIN;
- buffer_strcat(w->response.data, "Which format? '" ALLMETRICS_FORMAT_SHELL "', '" ALLMETRICS_FORMAT_PROMETHEUS "', '" ALLMETRICS_FORMAT_PROMETHEUS_ALL_HOSTS "' and '" ALLMETRICS_FORMAT_JSON "' are currently supported.");
- return HTTP_RESP_BAD_REQUEST;
- }
-}
diff --git a/src/web/api/exporters/allmetrics.h b/src/web/api/exporters/allmetrics.h
deleted file mode 100644
index 3afc42e2..00000000
--- a/src/web/api/exporters/allmetrics.h
+++ /dev/null
@@ -1,12 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#ifndef NETDATA_API_ALLMETRICS_H
-#define NETDATA_API_ALLMETRICS_H
-
-#include "web/api/formatters/rrd2json.h"
-#include "shell/allmetrics_shell.h"
-#include "web/server/web_client.h"
-
-int web_client_api_request_v1_allmetrics(RRDHOST *host, struct web_client *w, char *url);
-
-#endif //NETDATA_API_ALLMETRICS_H
diff --git a/src/web/api/exporters/prometheus/README.md b/src/web/api/exporters/prometheus/README.md
index 6c6bad3a..f4b1602c 100644
--- a/src/web/api/exporters/prometheus/README.md
+++ b/src/web/api/exporters/prometheus/README.md
@@ -1,12 +1,3 @@
-<!--
-title: "Prometheus exporter"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/src/web/api/exporters/prometheus/README.md
-sidebar_label: "Prometheus exporter"
-learn_status: "Published"
-learn_topic_type: "References"
-learn_rel_path: "Developers/Web/Api/Exporters"
--->
-
# Prometheus exporter
Read the Prometheus exporter documentation: [Using Netdata with Prometheus](/src/exporting/prometheus/README.md).
diff --git a/src/web/api/exporters/shell/README.md b/src/web/api/exporters/shell/README.md
index 86b774f1..14faf1fb 100644
--- a/src/web/api/exporters/shell/README.md
+++ b/src/web/api/exporters/shell/README.md
@@ -1,25 +1,16 @@
-<!--
-title: "Shell exporter"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/src/web/api/exporters/shell/README.md
-sidebar_label: "Shell exporter"
-learn_status: "Published"
-learn_topic_type: "References"
-learn_rel_path: "Developers/Web/Api/Exporters"
--->
-
# Shell exporter
Shell scripts can now query Netdata:
```sh
-eval "$(curl -s 'http://localhost:19999/api/v1/allmetrics')"
+eval "$(curl -s 'http://localhost:19999/api/v3/allmetrics')"
```
after this command, all the Netdata metrics are exposed to shell. Check:
```sh
# source the metrics
-eval "$(curl -s 'http://localhost:19999/api/v1/allmetrics')"
+eval "$(curl -s 'http://localhost:19999/api/v3/allmetrics')"
# let's see if there are variables exposed by Netdata for system.cpu
set | grep "^NETDATA_SYSTEM_CPU"
@@ -50,7 +41,7 @@ echo ${NETDATA_ALARM_SYSTEM_SWAP_USED_SWAP_STATUS}
CLEAR
# is it fast?
-time curl -s 'http://localhost:19999/api/v1/allmetrics' >/dev/null
+time curl -s 'http://localhost:19999/api/v3/allmetrics' >/dev/null
real 0m0,070s
user 0m0,000s
diff --git a/src/web/api/exporters/shell/allmetrics_shell.h b/src/web/api/exporters/shell/allmetrics_shell.h
deleted file mode 100644
index d6598e08..00000000
--- a/src/web/api/exporters/shell/allmetrics_shell.h
+++ /dev/null
@@ -1,21 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#ifndef NETDATA_API_ALLMETRICS_SHELL_H
-#define NETDATA_API_ALLMETRICS_SHELL_H
-
-#include "../allmetrics.h"
-
-#define ALLMETRICS_FORMAT_SHELL "shell"
-#define ALLMETRICS_FORMAT_PROMETHEUS "prometheus"
-#define ALLMETRICS_FORMAT_PROMETHEUS_ALL_HOSTS "prometheus_all_hosts"
-#define ALLMETRICS_FORMAT_JSON "json"
-
-#define ALLMETRICS_SHELL 1
-#define ALLMETRICS_PROMETHEUS 2
-#define ALLMETRICS_JSON 3
-#define ALLMETRICS_PROMETHEUS_ALL_HOSTS 4
-
-void rrd_stats_api_v1_charts_allmetrics_json(RRDHOST *host, const char *filter_string, BUFFER *wb);
-void rrd_stats_api_v1_charts_allmetrics_shell(RRDHOST *host, const char *filter_string, BUFFER *wb);
-
-#endif //NETDATA_API_ALLMETRICS_SHELL_H
diff --git a/src/web/api/formatters/README.md b/src/web/api/formatters/README.md
index 6347f5fb..27d80d40 100644
--- a/src/web/api/formatters/README.md
+++ b/src/web/api/formatters/README.md
@@ -1,12 +1,3 @@
-<!--
-title: "Query formatting"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/src/web/api/formatters/README.md
-sidebar_label: "Query formatting"
-learn_status: "Published"
-learn_topic_type: "References"
-learn_rel_path: "Developers/Web/Api/Formatters"
--->
-
# Query formatting
API data queries need to be formatted before returned to the caller.
diff --git a/src/web/api/formatters/charts2json.c b/src/web/api/formatters/charts2json.c
index 0b45d77c..9407f224 100644
--- a/src/web/api/formatters/charts2json.c
+++ b/src/web/api/formatters/charts2json.c
@@ -37,7 +37,7 @@ const char* get_release_channel() {
}
void charts2json(RRDHOST *host, BUFFER *wb) {
- static char *custom_dashboard_info_js_filename = NULL;
+ static const char *custom_dashboard_info_js_filename = NULL;
size_t c = 0, dimensions = 0, memory = 0, alarms = 0;
RRDSET *st;
diff --git a/src/web/api/formatters/csv/README.md b/src/web/api/formatters/csv/README.md
index e60aab57..435d2380 100644
--- a/src/web/api/formatters/csv/README.md
+++ b/src/web/api/formatters/csv/README.md
@@ -1,12 +1,3 @@
-<!--
-title: "CSV formatter"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/src/web/api/formatters/csv/README.md
-sidebar_label: "CSV formatter"
-learn_status: "Published"
-learn_topic_type: "References"
-learn_rel_path: "Developers/Web/Api/Formatters"
--->
-
# CSV formatter
The CSV formatter presents [results of database queries](/src/web/api/queries/README.md) in the following formats:
diff --git a/src/web/api/formatters/json/README.md b/src/web/api/formatters/json/README.md
index 4137b037..b1d02555 100644
--- a/src/web/api/formatters/json/README.md
+++ b/src/web/api/formatters/json/README.md
@@ -1,12 +1,3 @@
-<!--
-title: "JSON formatter"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/src/web/api/formatters/json/README.md
-sidebar_label: "JSON formatter"
-learn_status: "Published"
-learn_topic_type: "References"
-learn_rel_path: "Developers/Web/Api/Formatters"
--->
-
# JSON formatter
The CSV formatter presents [results of database queries](/src/web/api/queries/README.md) in the following formats:
diff --git a/src/web/api/formatters/rrd2json.c b/src/web/api/formatters/rrd2json.c
index 81c9ad5c..a8027548 100644
--- a/src/web/api/formatters/rrd2json.c
+++ b/src/web/api/formatters/rrd2json.c
@@ -10,46 +10,6 @@ void rrd_stats_api_v1_chart(RRDSET *st, BUFFER *wb)
buffer_json_finalize(wb);
}
-const char *rrdr_format_to_string(DATASOURCE_FORMAT format) {
- switch(format) {
- case DATASOURCE_JSON:
- return DATASOURCE_FORMAT_JSON;
-
- case DATASOURCE_JSON2:
- return DATASOURCE_FORMAT_JSON2;
-
- case DATASOURCE_DATATABLE_JSON:
- return DATASOURCE_FORMAT_DATATABLE_JSON;
-
- case DATASOURCE_DATATABLE_JSONP:
- return DATASOURCE_FORMAT_DATATABLE_JSONP;
-
- case DATASOURCE_JSONP:
- return DATASOURCE_FORMAT_JSONP;
-
- case DATASOURCE_SSV:
- return DATASOURCE_FORMAT_SSV;
-
- case DATASOURCE_CSV:
- return DATASOURCE_FORMAT_CSV;
-
- case DATASOURCE_TSV:
- return DATASOURCE_FORMAT_TSV;
-
- case DATASOURCE_HTML:
- return DATASOURCE_FORMAT_HTML;
-
- case DATASOURCE_JS_ARRAY:
- return DATASOURCE_FORMAT_JS_ARRAY;
-
- case DATASOURCE_SSV_COMMA:
- return DATASOURCE_FORMAT_SSV_COMMA;
-
- default:
- return "unknown";
- }
-}
-
int rrdset2value_api_v1(
RRDSET *st
, BUFFER *wb
diff --git a/src/web/api/formatters/rrd2json.h b/src/web/api/formatters/rrd2json.h
index f0c0c39b..cf3492ff 100644
--- a/src/web/api/formatters/rrd2json.h
+++ b/src/web/api/formatters/rrd2json.h
@@ -3,26 +3,8 @@
#ifndef NETDATA_RRD2JSON_H
#define NETDATA_RRD2JSON_H 1
-// type of JSON generations
-typedef enum {
- DATASOURCE_JSON = 0,
- DATASOURCE_DATATABLE_JSON = 1,
- DATASOURCE_DATATABLE_JSONP = 2,
- DATASOURCE_SSV = 3,
- DATASOURCE_CSV = 4,
- DATASOURCE_JSONP = 5,
- DATASOURCE_TSV = 6,
- DATASOURCE_HTML = 7,
- DATASOURCE_JS_ARRAY = 8,
- DATASOURCE_SSV_COMMA = 9,
- DATASOURCE_CSV_JSON_ARRAY = 10,
- DATASOURCE_CSV_MARKDOWN = 11,
- DATASOURCE_JSON2 = 12,
-} DATASOURCE_FORMAT;
+#include "web/api/web_api.h"
-#include "web/api/web_api_v1.h"
-
-#include "web/api/exporters/allmetrics.h"
#include "web/api/queries/rrdr.h"
#include "web/api/formatters/csv/csv.h"
@@ -38,22 +20,7 @@ typedef enum {
#define HOSTNAME_MAX 1024
-#define DATASOURCE_FORMAT_JSON "json"
-#define DATASOURCE_FORMAT_JSON2 "json2"
-#define DATASOURCE_FORMAT_DATATABLE_JSON "datatable"
-#define DATASOURCE_FORMAT_DATATABLE_JSONP "datasource"
-#define DATASOURCE_FORMAT_JSONP "jsonp"
-#define DATASOURCE_FORMAT_SSV "ssv"
-#define DATASOURCE_FORMAT_CSV "csv"
-#define DATASOURCE_FORMAT_TSV "tsv"
-#define DATASOURCE_FORMAT_HTML "html"
-#define DATASOURCE_FORMAT_JS_ARRAY "array"
-#define DATASOURCE_FORMAT_SSV_COMMA "ssvcomma"
-#define DATASOURCE_FORMAT_CSV_JSON_ARRAY "csvjsonarray"
-#define DATASOURCE_FORMAT_CSV_MARKDOWN "markdown"
-
void rrd_stats_api_v1_chart(RRDSET *st, BUFFER *wb);
-const char *rrdr_format_to_string(DATASOURCE_FORMAT format);
int data_query_execute(ONEWAYALLOC *owa, BUFFER *wb, struct query_target *qt, time_t *latest_timestamp);
diff --git a/src/web/api/formatters/ssv/README.md b/src/web/api/formatters/ssv/README.md
index b3249401..4d07fe5b 100644
--- a/src/web/api/formatters/ssv/README.md
+++ b/src/web/api/formatters/ssv/README.md
@@ -1,12 +1,3 @@
-<!--
-title: "SSV formatter"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/src/web/api/formatters/ssv/README.md
-sidebar_label: "SSV formatter"
-learn_status: "Published"
-learn_topic_type: "References"
-learn_rel_path: "Developers/Web/Api/Formatters"
--->
-
# SSV formatter
The SSV formatter sums all dimensions in [results of database queries](/src/web/api/queries/README.md)
diff --git a/src/web/api/formatters/value/README.md b/src/web/api/formatters/value/README.md
index 8a2df23c..1f0af813 100644
--- a/src/web/api/formatters/value/README.md
+++ b/src/web/api/formatters/value/README.md
@@ -1,12 +1,3 @@
-<!--
-title: "Value formatter"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/src/web/api/formatters/value/README.md
-sidebar_label: "Value formatter"
-learn_status: "Published"
-learn_topic_type: "References"
-learn_rel_path: "Developers/Web/Api/Formatters"
--->
-
# Value formatter
The Value formatter presents [results of database queries](/src/web/api/queries/README.md) as a single value.
diff --git a/src/web/api/functions/function-bearer_get_token.c b/src/web/api/functions/function-bearer_get_token.c
new file mode 100644
index 00000000..8f14e68a
--- /dev/null
+++ b/src/web/api/functions/function-bearer_get_token.c
@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "function-bearer_get_token.h"
+#include "../v2/api_v2_calls.h"
+
+struct bearer_token_request {
+ nd_uuid_t claim_id;
+ nd_uuid_t machine_guid;
+ nd_uuid_t node_id;
+ HTTP_USER_ROLE user_role;
+ HTTP_ACCESS access;
+ nd_uuid_t cloud_account_id;
+ STRING *client_name;
+};
+
+static bool bearer_parse_json_payload(json_object *jobj, const char *path, void *data, BUFFER *error) {
+ struct bearer_token_request *rq = data;
+ JSONC_PARSE_TXT2UUID_OR_ERROR_AND_RETURN(jobj, path, "claim_id", rq->claim_id, error, true);
+ JSONC_PARSE_TXT2UUID_OR_ERROR_AND_RETURN(jobj, path, "machine_guid", rq->machine_guid, error, true);
+ JSONC_PARSE_TXT2UUID_OR_ERROR_AND_RETURN(jobj, path, "node_id", rq->node_id, error, true);
+ JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, path, "user_role", http_user_role2id, rq->user_role, error, true);
+ JSONC_PARSE_ARRAY_OF_TXT2BITMAP_OR_ERROR_AND_RETURN(jobj, path, "access", http_access2id_one, rq->access, error, true);
+ JSONC_PARSE_TXT2UUID_OR_ERROR_AND_RETURN(jobj, path, "cloud_account_id", rq->cloud_account_id, error, true);
+ JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, path, "client_name", rq->client_name, error, true);
+ return true;
+}
+
+int function_bearer_get_token(BUFFER *wb, const char *function __maybe_unused, BUFFER *payload, const char *source) {
+ if(!request_source_is_cloud(source))
+ return rrd_call_function_error(
+ wb, "Bearer tokens can only be provided via NC.", HTTP_RESP_BAD_REQUEST);
+
+ int code;
+ struct bearer_token_request rq = { 0 };
+ CLEAN_JSON_OBJECT *jobj = json_parse_function_payload_or_error(wb, payload, &code, bearer_parse_json_payload, &rq);
+ if(!jobj || code != HTTP_RESP_OK) {
+ string_freez(rq.client_name);
+ return code;
+ }
+
+ char claim_id[UUID_STR_LEN];
+ uuid_unparse_lower(rq.claim_id, claim_id);
+
+ char machine_guid[UUID_STR_LEN];
+ uuid_unparse_lower(rq.machine_guid, machine_guid);
+
+ char node_id[UUID_STR_LEN];
+ uuid_unparse_lower(rq.node_id, node_id);
+
+ int rc = bearer_get_token_json_response(wb, localhost, claim_id, machine_guid, node_id,
+ rq.user_role, rq.access, rq.cloud_account_id,
+ string2str(rq.client_name));
+
+ string_freez(rq.client_name);
+ return rc;
+}
+
+int call_function_bearer_get_token(RRDHOST *host, struct web_client *w, const char *claim_id, const char *machine_guid, const char *node_id) {
+ CLEAN_BUFFER *payload = buffer_create(0, NULL);
+ buffer_json_initialize(payload, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
+ buffer_json_member_add_string(payload, "claim_id", claim_id);
+ buffer_json_member_add_string(payload, "machine_guid", machine_guid);
+ buffer_json_member_add_string(payload, "node_id", node_id);
+ buffer_json_member_add_string(payload, "user_role", http_id2user_role(w->user_role));
+ http_access2buffer_json_array(payload, "access", w->access);
+ buffer_json_member_add_uuid(payload, "cloud_account_id", w->auth.cloud_account_id);
+ buffer_json_member_add_string(payload, "client_name", w->auth.client_name);
+ buffer_json_finalize(payload);
+
+ CLEAN_BUFFER *source = buffer_create(0, NULL);
+ web_client_api_request_vX_source_to_buffer(w, source);
+
+ char transaction_str[UUID_COMPACT_STR_LEN];
+ uuid_unparse_lower_compact(w->transaction, transaction_str);
+ return rrd_function_run(host, w->response.data, 10,
+ w->access, RRDFUNCTIONS_BEARER_GET_TOKEN, true,
+ transaction_str, NULL, NULL,
+ NULL, NULL,
+ NULL, NULL,
+ payload, buffer_tostring(source), true);
+}
diff --git a/src/web/api/functions/function-bearer_get_token.h b/src/web/api/functions/function-bearer_get_token.h
new file mode 100644
index 00000000..03481ebb
--- /dev/null
+++ b/src/web/api/functions/function-bearer_get_token.h
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_FUNCTION_BEARER_GET_TOKEN_H
+#define NETDATA_FUNCTION_BEARER_GET_TOKEN_H
+
+#include "daemon/common.h"
+
+int function_bearer_get_token(BUFFER *wb, const char *function, BUFFER *payload, const char *source);
+int call_function_bearer_get_token(RRDHOST *host, struct web_client *w, const char *claim_id, const char *machine_guid, const char *node_id);
+
+#define RRDFUNCTIONS_BEARER_GET_TOKEN "bearer_get_token"
+#define RRDFUNCTIONS_BEARER_GET_TOKEN_HELP "Get a bearer token for authenticated direct access to the agent"
+
+#endif //NETDATA_FUNCTION_BEARER_GET_TOKEN_H
diff --git a/src/web/api/functions/function-progress.c b/src/web/api/functions/function-progress.c
new file mode 100644
index 00000000..052a9020
--- /dev/null
+++ b/src/web/api/functions/function-progress.c
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "function-progress.h"
+
+int function_progress(BUFFER *wb, const char *function __maybe_unused, BUFFER *payload __maybe_unused, const char *source __maybe_unused) {
+ return progress_function_result(wb, rrdhost_hostname(localhost));
+}
+
diff --git a/src/web/api/functions/function-progress.h b/src/web/api/functions/function-progress.h
new file mode 100644
index 00000000..7d2d10b9
--- /dev/null
+++ b/src/web/api/functions/function-progress.h
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_FUNCTION_PROGRESS_H
+#define NETDATA_FUNCTION_PROGRESS_H
+
+#include "daemon/common.h"
+
+int function_progress(BUFFER *wb, const char *function, BUFFER *payload, const char *source);
+
+#endif //NETDATA_FUNCTION_PROGRESS_H
diff --git a/src/web/api/functions/function-streaming.c b/src/web/api/functions/function-streaming.c
new file mode 100644
index 00000000..11e97044
--- /dev/null
+++ b/src/web/api/functions/function-streaming.c
@@ -0,0 +1,627 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "function-streaming.h"
+
+int function_streaming(BUFFER *wb, const char *function __maybe_unused, BUFFER *payload __maybe_unused, const char *source __maybe_unused) {
+
+ time_t now = now_realtime_sec();
+
+ buffer_flush(wb);
+ wb->content_type = CT_APPLICATION_JSON;
+ buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
+
+ buffer_json_member_add_string(wb, "hostname", rrdhost_hostname(localhost));
+ buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK);
+ buffer_json_member_add_string(wb, "type", "table");
+ buffer_json_member_add_time_t(wb, "update_every", 1);
+ buffer_json_member_add_boolean(wb, "has_history", false);
+ buffer_json_member_add_string(wb, "help", RRDFUNCTIONS_STREAMING_HELP);
+ buffer_json_member_add_array(wb, "data");
+
+ size_t max_sent_bytes_on_this_connection_per_type[STREAM_TRAFFIC_TYPE_MAX] = { 0 };
+ size_t max_db_metrics = 0, max_db_instances = 0, max_db_contexts = 0;
+ size_t max_collection_replication_instances = 0, max_streaming_replication_instances = 0;
+ size_t max_ml_anomalous = 0, max_ml_normal = 0, max_ml_trained = 0, max_ml_pending = 0, max_ml_silenced = 0;
+ {
+ RRDHOST *host;
+ dfe_start_read(rrdhost_root_index, host) {
+ RRDHOST_STATUS s;
+ rrdhost_status(host, now, &s);
+ buffer_json_add_array_item_array(wb);
+
+ if(s.db.metrics > max_db_metrics)
+ max_db_metrics = s.db.metrics;
+
+ if(s.db.instances > max_db_instances)
+ max_db_instances = s.db.instances;
+
+ if(s.db.contexts > max_db_contexts)
+ max_db_contexts = s.db.contexts;
+
+ if(s.ingest.replication.instances > max_collection_replication_instances)
+ max_collection_replication_instances = s.ingest.replication.instances;
+
+ if(s.stream.replication.instances > max_streaming_replication_instances)
+ max_streaming_replication_instances = s.stream.replication.instances;
+
+ for(int i = 0; i < STREAM_TRAFFIC_TYPE_MAX ;i++) {
+ if (s.stream.sent_bytes_on_this_connection_per_type[i] >
+ max_sent_bytes_on_this_connection_per_type[i])
+ max_sent_bytes_on_this_connection_per_type[i] =
+ s.stream.sent_bytes_on_this_connection_per_type[i];
+ }
+
+ // retention
+ buffer_json_add_array_item_string(wb, rrdhost_hostname(s.host)); // Node
+ buffer_json_add_array_item_uint64(wb, s.db.first_time_s * MSEC_PER_SEC); // dbFrom
+ buffer_json_add_array_item_uint64(wb, s.db.last_time_s * MSEC_PER_SEC); // dbTo
+
+ if(s.db.first_time_s && s.db.last_time_s && s.db.last_time_s > s.db.first_time_s)
+ buffer_json_add_array_item_uint64(wb, s.db.last_time_s - s.db.first_time_s); // dbDuration
+ else
+ buffer_json_add_array_item_string(wb, NULL); // dbDuration
+
+ buffer_json_add_array_item_uint64(wb, s.db.metrics); // dbMetrics
+ buffer_json_add_array_item_uint64(wb, s.db.instances); // dbInstances
+ buffer_json_add_array_item_uint64(wb, s.db.contexts); // dbContexts
+
+ // statuses
+ buffer_json_add_array_item_string(wb, rrdhost_ingest_status_to_string(s.ingest.status)); // InStatus
+ buffer_json_add_array_item_string(wb, rrdhost_streaming_status_to_string(s.stream.status)); // OutStatus
+ buffer_json_add_array_item_string(wb, rrdhost_ml_status_to_string(s.ml.status)); // MLStatus
+
+ // collection
+ if(s.ingest.since) {
+ buffer_json_add_array_item_uint64(wb, s.ingest.since * MSEC_PER_SEC); // InSince
+ buffer_json_add_array_item_time_t(wb, s.now - s.ingest.since); // InAge
+ }
+ else {
+ buffer_json_add_array_item_string(wb, NULL); // InSince
+ buffer_json_add_array_item_string(wb, NULL); // InAge
+ }
+ buffer_json_add_array_item_string(wb, stream_handshake_error_to_string(s.ingest.reason)); // InReason
+ buffer_json_add_array_item_uint64(wb, s.ingest.hops); // InHops
+ buffer_json_add_array_item_double(wb, s.ingest.replication.completion); // InReplCompletion
+ buffer_json_add_array_item_uint64(wb, s.ingest.replication.instances); // InReplInstances
+ buffer_json_add_array_item_string(wb, s.ingest.peers.local.ip); // InLocalIP
+ buffer_json_add_array_item_uint64(wb, s.ingest.peers.local.port); // InLocalPort
+ buffer_json_add_array_item_string(wb, s.ingest.peers.peer.ip); // InRemoteIP
+ buffer_json_add_array_item_uint64(wb, s.ingest.peers.peer.port); // InRemotePort
+ buffer_json_add_array_item_string(wb, s.ingest.ssl ? "SSL" : "PLAIN"); // InSSL
+ stream_capabilities_to_json_array(wb, s.ingest.capabilities, NULL); // InCapabilities
+
+ // streaming
+ if(s.stream.since) {
+ buffer_json_add_array_item_uint64(wb, s.stream.since * MSEC_PER_SEC); // OutSince
+ buffer_json_add_array_item_time_t(wb, s.now - s.stream.since); // OutAge
+ }
+ else {
+ buffer_json_add_array_item_string(wb, NULL); // OutSince
+ buffer_json_add_array_item_string(wb, NULL); // OutAge
+ }
+ buffer_json_add_array_item_string(wb, stream_handshake_error_to_string(s.stream.reason)); // OutReason
+ buffer_json_add_array_item_uint64(wb, s.stream.hops); // OutHops
+ buffer_json_add_array_item_double(wb, s.stream.replication.completion); // OutReplCompletion
+ buffer_json_add_array_item_uint64(wb, s.stream.replication.instances); // OutReplInstances
+ buffer_json_add_array_item_string(wb, s.stream.peers.local.ip); // OutLocalIP
+ buffer_json_add_array_item_uint64(wb, s.stream.peers.local.port); // OutLocalPort
+ buffer_json_add_array_item_string(wb, s.stream.peers.peer.ip); // OutRemoteIP
+ buffer_json_add_array_item_uint64(wb, s.stream.peers.peer.port); // OutRemotePort
+ buffer_json_add_array_item_string(wb, s.stream.ssl ? "SSL" : "PLAIN"); // OutSSL
+ buffer_json_add_array_item_string(wb, s.stream.compression ? "COMPRESSED" : "UNCOMPRESSED"); // OutCompression
+ stream_capabilities_to_json_array(wb, s.stream.capabilities, NULL); // OutCapabilities
+ buffer_json_add_array_item_uint64(wb, s.stream.sent_bytes_on_this_connection_per_type[STREAM_TRAFFIC_TYPE_DATA]);
+ buffer_json_add_array_item_uint64(wb, s.stream.sent_bytes_on_this_connection_per_type[STREAM_TRAFFIC_TYPE_METADATA]);
+ buffer_json_add_array_item_uint64(wb, s.stream.sent_bytes_on_this_connection_per_type[STREAM_TRAFFIC_TYPE_REPLICATION]);
+ buffer_json_add_array_item_uint64(wb, s.stream.sent_bytes_on_this_connection_per_type[STREAM_TRAFFIC_TYPE_FUNCTIONS]);
+
+ buffer_json_add_array_item_array(wb); // OutAttemptHandshake
+ time_t last_attempt = 0;
+ for(struct rrdpush_destinations *d = host->destinations; d ; d = d->next) {
+ if(d->since > last_attempt)
+ last_attempt = d->since;
+
+ buffer_json_add_array_item_string(wb, stream_handshake_error_to_string(d->reason));
+ }
+ buffer_json_array_close(wb); // // OutAttemptHandshake
+
+ if(!last_attempt) {
+ buffer_json_add_array_item_string(wb, NULL); // OutAttemptSince
+ buffer_json_add_array_item_string(wb, NULL); // OutAttemptAge
+ }
+ else {
+ buffer_json_add_array_item_uint64(wb, last_attempt * 1000); // OutAttemptSince
+ buffer_json_add_array_item_time_t(wb, s.now - last_attempt); // OutAttemptAge
+ }
+
+ // ML
+ if(s.ml.status == RRDHOST_ML_STATUS_RUNNING) {
+ buffer_json_add_array_item_uint64(wb, s.ml.metrics.anomalous); // MlAnomalous
+ buffer_json_add_array_item_uint64(wb, s.ml.metrics.normal); // MlNormal
+ buffer_json_add_array_item_uint64(wb, s.ml.metrics.trained); // MlTrained
+ buffer_json_add_array_item_uint64(wb, s.ml.metrics.pending); // MlPending
+ buffer_json_add_array_item_uint64(wb, s.ml.metrics.silenced); // MlSilenced
+
+ if(s.ml.metrics.anomalous > max_ml_anomalous)
+ max_ml_anomalous = s.ml.metrics.anomalous;
+
+ if(s.ml.metrics.normal > max_ml_normal)
+ max_ml_normal = s.ml.metrics.normal;
+
+ if(s.ml.metrics.trained > max_ml_trained)
+ max_ml_trained = s.ml.metrics.trained;
+
+ if(s.ml.metrics.pending > max_ml_pending)
+ max_ml_pending = s.ml.metrics.pending;
+
+ if(s.ml.metrics.silenced > max_ml_silenced)
+ max_ml_silenced = s.ml.metrics.silenced;
+
+ }
+ else {
+ buffer_json_add_array_item_string(wb, NULL); // MlAnomalous
+ buffer_json_add_array_item_string(wb, NULL); // MlNormal
+ buffer_json_add_array_item_string(wb, NULL); // MlTrained
+ buffer_json_add_array_item_string(wb, NULL); // MlPending
+ buffer_json_add_array_item_string(wb, NULL); // MlSilenced
+ }
+
+ // close
+ buffer_json_array_close(wb);
+ }
+ dfe_done(host);
+ }
+ buffer_json_array_close(wb); // data
+ buffer_json_member_add_object(wb, "columns");
+ {
+ size_t field_id = 0;
+
+ // Node
+ buffer_rrdf_table_add_field(wb, field_id++, "Node", "Node's Hostname",
+ RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
+ RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
+ RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_UNIQUE_KEY | RRDF_FIELD_OPTS_STICKY,
+ NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "dbFrom", "DB Data Retention From",
+ RRDF_FIELD_TYPE_TIMESTAMP, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_DATETIME_MS,
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
+ RRDF_FIELD_SUMMARY_MIN, RRDF_FIELD_FILTER_RANGE,
+ RRDF_FIELD_OPTS_NONE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "dbTo", "DB Data Retention To",
+ RRDF_FIELD_TYPE_TIMESTAMP, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_DATETIME_MS,
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
+ RRDF_FIELD_SUMMARY_MAX, RRDF_FIELD_FILTER_RANGE,
+ RRDF_FIELD_OPTS_NONE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "dbDuration", "DB Data Retention Duration",
+ RRDF_FIELD_TYPE_DURATION, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_DURATION_S,
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
+ RRDF_FIELD_SUMMARY_MAX, RRDF_FIELD_FILTER_RANGE,
+ RRDF_FIELD_OPTS_VISIBLE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "dbMetrics", "Time-series Metrics in the DB",
+ RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
+ 0, NULL, (double)max_db_metrics, RRDF_FIELD_SORT_DESCENDING, NULL,
+ RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
+ RRDF_FIELD_OPTS_VISIBLE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "dbInstances", "Instances in the DB",
+ RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
+ 0, NULL, (double)max_db_instances, RRDF_FIELD_SORT_DESCENDING, NULL,
+ RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
+ RRDF_FIELD_OPTS_VISIBLE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "dbContexts", "Contexts in the DB",
+ RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
+ 0, NULL, (double)max_db_contexts, RRDF_FIELD_SORT_DESCENDING, NULL,
+ RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
+ RRDF_FIELD_OPTS_VISIBLE, NULL);
+
+ // --- statuses ---
+
+ buffer_rrdf_table_add_field(wb, field_id++, "InStatus", "Data Collection Online Status",
+ RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
+ RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
+ RRDF_FIELD_OPTS_VISIBLE, NULL);
+
+
+ buffer_rrdf_table_add_field(wb, field_id++, "OutStatus", "Streaming Online Status",
+ RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
+ RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
+ RRDF_FIELD_OPTS_VISIBLE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "MlStatus", "ML Status",
+ RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
+ RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
+ RRDF_FIELD_OPTS_VISIBLE, NULL);
+
+ // --- collection ---
+
+ buffer_rrdf_table_add_field(wb, field_id++, "InSince", "Last Data Collection Status Change",
+ RRDF_FIELD_TYPE_TIMESTAMP, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_DATETIME_MS,
+ 0, NULL, NAN, RRDF_FIELD_SORT_DESCENDING, NULL,
+ RRDF_FIELD_SUMMARY_MIN, RRDF_FIELD_FILTER_RANGE,
+ RRDF_FIELD_OPTS_NONE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "InAge", "Last Data Collection Online Status Change Age",
+ RRDF_FIELD_TYPE_DURATION, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_DURATION_S,
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
+ RRDF_FIELD_SUMMARY_MAX, RRDF_FIELD_FILTER_RANGE,
+ RRDF_FIELD_OPTS_VISIBLE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "InReason", "Data Collection Online Status Reason",
+ RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
+ RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
+ RRDF_FIELD_OPTS_VISIBLE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "InHops", "Data Collection Distance Hops from Origin Node",
+ RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
+ RRDF_FIELD_SUMMARY_MIN, RRDF_FIELD_FILTER_RANGE,
+ RRDF_FIELD_OPTS_VISIBLE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "InReplCompletion", "Inbound Replication Completion",
+ RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_BAR, RRDF_FIELD_TRANSFORM_NUMBER,
+ 1, "%", 100.0, RRDF_FIELD_SORT_DESCENDING, NULL,
+ RRDF_FIELD_SUMMARY_MIN, RRDF_FIELD_FILTER_RANGE,
+ RRDF_FIELD_OPTS_VISIBLE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "InReplInstances", "Inbound Replicating Instances",
+ RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
+ 0, "instances", (double)max_collection_replication_instances, RRDF_FIELD_SORT_DESCENDING,
+ NULL,
+ RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
+ RRDF_FIELD_OPTS_NONE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "InLocalIP", "Inbound Local IP",
+ RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
+ RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
+ RRDF_FIELD_OPTS_NONE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "InLocalPort", "Inbound Local Port",
+ RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
+ RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_RANGE,
+ RRDF_FIELD_OPTS_NONE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "InRemoteIP", "Inbound Remote IP",
+ RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
+ RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
+ RRDF_FIELD_OPTS_NONE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "InRemotePort", "Inbound Remote Port",
+ RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
+ RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_RANGE,
+ RRDF_FIELD_OPTS_NONE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "InSSL", "Inbound SSL Connection",
+ RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
+ RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
+ RRDF_FIELD_OPTS_NONE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "InCapabilities", "Inbound Connection Capabilities",
+ RRDF_FIELD_TYPE_ARRAY, RRDF_FIELD_VISUAL_PILL, RRDF_FIELD_TRANSFORM_NONE,
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
+ RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
+ RRDF_FIELD_OPTS_NONE, NULL);
+
+ // --- streaming ---
+
+ buffer_rrdf_table_add_field(wb, field_id++, "OutSince", "Last Streaming Status Change",
+ RRDF_FIELD_TYPE_TIMESTAMP, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_DATETIME_MS,
+ 0, NULL, NAN, RRDF_FIELD_SORT_DESCENDING, NULL,
+ RRDF_FIELD_SUMMARY_MAX, RRDF_FIELD_FILTER_RANGE,
+ RRDF_FIELD_OPTS_NONE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "OutAge", "Last Streaming Status Change Age",
+ RRDF_FIELD_TYPE_DURATION, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_DURATION_S,
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
+ RRDF_FIELD_SUMMARY_MIN, RRDF_FIELD_FILTER_RANGE,
+ RRDF_FIELD_OPTS_VISIBLE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "OutReason", "Streaming Status Reason",
+ RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
+ RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
+ RRDF_FIELD_OPTS_VISIBLE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "OutHops", "Streaming Distance Hops from Origin Node",
+ RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
+ RRDF_FIELD_SUMMARY_MIN, RRDF_FIELD_FILTER_RANGE,
+ RRDF_FIELD_OPTS_VISIBLE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "OutReplCompletion", "Outbound Replication Completion",
+ RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_BAR, RRDF_FIELD_TRANSFORM_NUMBER,
+ 1, "%", 100.0, RRDF_FIELD_SORT_DESCENDING, NULL,
+ RRDF_FIELD_SUMMARY_MIN, RRDF_FIELD_FILTER_RANGE,
+ RRDF_FIELD_OPTS_VISIBLE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "OutReplInstances", "Outbound Replicating Instances",
+ RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
+ 0, "instances", (double)max_streaming_replication_instances, RRDF_FIELD_SORT_DESCENDING,
+ NULL,
+ RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
+ RRDF_FIELD_OPTS_NONE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "OutLocalIP", "Outbound Local IP",
+ RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
+ RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
+ RRDF_FIELD_OPTS_NONE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "OutLocalPort", "Outbound Local Port",
+ RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
+ RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_RANGE,
+ RRDF_FIELD_OPTS_NONE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "OutRemoteIP", "Outbound Remote IP",
+ RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
+ RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
+ RRDF_FIELD_OPTS_NONE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "OutRemotePort", "Outbound Remote Port",
+ RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
+ RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_RANGE,
+ RRDF_FIELD_OPTS_NONE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "OutSSL", "Outbound SSL Connection",
+ RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
+ RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
+ RRDF_FIELD_OPTS_NONE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "OutCompression", "Outbound Compressed Connection",
+ RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
+ RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
+ RRDF_FIELD_OPTS_NONE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "OutCapabilities", "Outbound Connection Capabilities",
+ RRDF_FIELD_TYPE_ARRAY, RRDF_FIELD_VISUAL_PILL, RRDF_FIELD_TRANSFORM_NONE,
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
+ RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
+ RRDF_FIELD_OPTS_NONE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "OutTrafficData", "Outbound Metric Data Traffic",
+ RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
+ 0, "bytes", (double)max_sent_bytes_on_this_connection_per_type[STREAM_TRAFFIC_TYPE_DATA],
+ RRDF_FIELD_SORT_DESCENDING, NULL,
+ RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
+ RRDF_FIELD_OPTS_NONE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "OutTrafficMetadata", "Outbound Metric Metadata Traffic",
+ RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
+ 0, "bytes",
+ (double)max_sent_bytes_on_this_connection_per_type[STREAM_TRAFFIC_TYPE_METADATA],
+ RRDF_FIELD_SORT_DESCENDING, NULL,
+ RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
+ RRDF_FIELD_OPTS_NONE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "OutTrafficReplication", "Outbound Metric Replication Traffic",
+ RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
+ 0, "bytes",
+ (double)max_sent_bytes_on_this_connection_per_type[STREAM_TRAFFIC_TYPE_REPLICATION],
+ RRDF_FIELD_SORT_DESCENDING, NULL,
+ RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
+ RRDF_FIELD_OPTS_NONE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "OutTrafficFunctions", "Outbound Metric Functions Traffic",
+ RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
+ 0, "bytes",
+ (double)max_sent_bytes_on_this_connection_per_type[STREAM_TRAFFIC_TYPE_FUNCTIONS],
+ RRDF_FIELD_SORT_DESCENDING, NULL,
+ RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
+ RRDF_FIELD_OPTS_NONE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "OutAttemptHandshake",
+ "Outbound Connection Attempt Handshake Status",
+ RRDF_FIELD_TYPE_ARRAY, RRDF_FIELD_VISUAL_PILL, RRDF_FIELD_TRANSFORM_NONE,
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
+ RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
+ RRDF_FIELD_OPTS_NONE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "OutAttemptSince",
+ "Last Outbound Connection Attempt Status Change Time",
+ RRDF_FIELD_TYPE_TIMESTAMP, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_DATETIME_MS,
+ 0, NULL, NAN, RRDF_FIELD_SORT_DESCENDING, NULL,
+ RRDF_FIELD_SUMMARY_MAX, RRDF_FIELD_FILTER_RANGE,
+ RRDF_FIELD_OPTS_NONE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "OutAttemptAge",
+ "Last Outbound Connection Attempt Status Change Age",
+ RRDF_FIELD_TYPE_DURATION, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_DURATION_S,
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
+ RRDF_FIELD_SUMMARY_MIN, RRDF_FIELD_FILTER_RANGE,
+ RRDF_FIELD_OPTS_VISIBLE, NULL);
+
+ // --- ML ---
+
+ buffer_rrdf_table_add_field(wb, field_id++, "MlAnomalous", "Number of Anomalous Metrics",
+ RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
+ 0, "metrics",
+ (double)max_ml_anomalous,
+ RRDF_FIELD_SORT_DESCENDING, NULL,
+ RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
+ RRDF_FIELD_OPTS_NONE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "MlNormal", "Number of Not Anomalous Metrics",
+ RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
+ 0, "metrics",
+ (double)max_ml_normal,
+ RRDF_FIELD_SORT_DESCENDING, NULL,
+ RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
+ RRDF_FIELD_OPTS_NONE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "MlTrained", "Number of Trained Metrics",
+ RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
+ 0, "metrics",
+ (double)max_ml_trained,
+ RRDF_FIELD_SORT_DESCENDING, NULL,
+ RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
+ RRDF_FIELD_OPTS_NONE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "MlPending", "Number of Pending Metrics",
+ RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
+ 0, "metrics",
+ (double)max_ml_pending,
+ RRDF_FIELD_SORT_DESCENDING, NULL,
+ RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
+ RRDF_FIELD_OPTS_NONE, NULL);
+
+ buffer_rrdf_table_add_field(wb, field_id++, "MlSilenced", "Number of Silenced Metrics",
+ RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
+ 0, "metrics",
+ (double)max_ml_silenced,
+ RRDF_FIELD_SORT_DESCENDING, NULL,
+ RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
+ RRDF_FIELD_OPTS_NONE, NULL);
+ }
+ buffer_json_object_close(wb); // columns
+ buffer_json_member_add_string(wb, "default_sort_column", "Node");
+ buffer_json_member_add_object(wb, "charts");
+ {
+ // Data Collection Age chart
+ buffer_json_member_add_object(wb, "InAge");
+ {
+ buffer_json_member_add_string(wb, "name", "Data Collection Age");
+ buffer_json_member_add_string(wb, "type", "stacked-bar");
+ buffer_json_member_add_array(wb, "columns");
+ {
+ buffer_json_add_array_item_string(wb, "InAge");
+ }
+ buffer_json_array_close(wb);
+ }
+ buffer_json_object_close(wb);
+
+ // Streaming Age chart
+ buffer_json_member_add_object(wb, "OutAge");
+ {
+ buffer_json_member_add_string(wb, "name", "Streaming Age");
+ buffer_json_member_add_string(wb, "type", "stacked-bar");
+ buffer_json_member_add_array(wb, "columns");
+ {
+ buffer_json_add_array_item_string(wb, "OutAge");
+ }
+ buffer_json_array_close(wb);
+ }
+ buffer_json_object_close(wb);
+
+ // DB Duration
+ buffer_json_member_add_object(wb, "dbDuration");
+ {
+ buffer_json_member_add_string(wb, "name", "Retention Duration");
+ buffer_json_member_add_string(wb, "type", "stacked-bar");
+ buffer_json_member_add_array(wb, "columns");
+ {
+ buffer_json_add_array_item_string(wb, "dbDuration");
+ }
+ buffer_json_array_close(wb);
+ }
+ buffer_json_object_close(wb);
+ }
+ buffer_json_object_close(wb); // charts
+
+ buffer_json_member_add_array(wb, "default_charts");
+ {
+ buffer_json_add_array_item_array(wb);
+ buffer_json_add_array_item_string(wb, "InAge");
+ buffer_json_add_array_item_string(wb, "Node");
+ buffer_json_array_close(wb);
+
+ buffer_json_add_array_item_array(wb);
+ buffer_json_add_array_item_string(wb, "OutAge");
+ buffer_json_add_array_item_string(wb, "Node");
+ buffer_json_array_close(wb);
+ }
+ buffer_json_array_close(wb);
+
+ buffer_json_member_add_object(wb, "group_by");
+ {
+ buffer_json_member_add_object(wb, "Node");
+ {
+ buffer_json_member_add_string(wb, "name", "Node");
+ buffer_json_member_add_array(wb, "columns");
+ {
+ buffer_json_add_array_item_string(wb, "Node");
+ }
+ buffer_json_array_close(wb);
+ }
+ buffer_json_object_close(wb);
+
+ buffer_json_member_add_object(wb, "InStatus");
+ {
+ buffer_json_member_add_string(wb, "name", "Nodes by Collection Status");
+ buffer_json_member_add_array(wb, "columns");
+ {
+ buffer_json_add_array_item_string(wb, "InStatus");
+ }
+ buffer_json_array_close(wb);
+ }
+ buffer_json_object_close(wb);
+
+ buffer_json_member_add_object(wb, "OutStatus");
+ {
+ buffer_json_member_add_string(wb, "name", "Nodes by Streaming Status");
+ buffer_json_member_add_array(wb, "columns");
+ {
+ buffer_json_add_array_item_string(wb, "OutStatus");
+ }
+ buffer_json_array_close(wb);
+ }
+ buffer_json_object_close(wb);
+
+ buffer_json_member_add_object(wb, "MlStatus");
+ {
+ buffer_json_member_add_string(wb, "name", "Nodes by ML Status");
+ buffer_json_member_add_array(wb, "columns");
+ {
+ buffer_json_add_array_item_string(wb, "MlStatus");
+ }
+ buffer_json_array_close(wb);
+ }
+ buffer_json_object_close(wb);
+
+ buffer_json_member_add_object(wb, "InRemoteIP");
+ {
+ buffer_json_member_add_string(wb, "name", "Nodes by Inbound IP");
+ buffer_json_member_add_array(wb, "columns");
+ {
+ buffer_json_add_array_item_string(wb, "InRemoteIP");
+ }
+ buffer_json_array_close(wb);
+ }
+ buffer_json_object_close(wb);
+
+ buffer_json_member_add_object(wb, "OutRemoteIP");
+ {
+ buffer_json_member_add_string(wb, "name", "Nodes by Outbound IP");
+ buffer_json_member_add_array(wb, "columns");
+ {
+ buffer_json_add_array_item_string(wb, "OutRemoteIP");
+ }
+ buffer_json_array_close(wb);
+ }
+ buffer_json_object_close(wb);
+ }
+ buffer_json_object_close(wb); // group_by
+
+ buffer_json_member_add_time_t(wb, "expires", now_realtime_sec() + 1);
+ buffer_json_finalize(wb);
+
+ return HTTP_RESP_OK;
+}
diff --git a/src/web/api/functions/function-streaming.h b/src/web/api/functions/function-streaming.h
new file mode 100644
index 00000000..06da6af9
--- /dev/null
+++ b/src/web/api/functions/function-streaming.h
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_FUNCTION_STREAMING_H
+#define NETDATA_FUNCTION_STREAMING_H
+
+#include "daemon/common.h"
+
+#define RRDFUNCTIONS_STREAMING_HELP "Streaming status for parents and children."
+
+int function_streaming(BUFFER *wb, const char *function, BUFFER *payload, const char *source);
+
+#endif //NETDATA_FUNCTION_STREAMING_H
diff --git a/src/web/api/functions/functions.c b/src/web/api/functions/functions.c
new file mode 100644
index 00000000..c00e04ca
--- /dev/null
+++ b/src/web/api/functions/functions.c
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "functions.h"
+
+void global_functions_add(void) {
+ // we register this only on localhost
+ // for the other nodes, the origin server should register it
+ rrd_function_add_inline(
+ localhost,
+ NULL,
+ "streaming",
+ 10,
+ RRDFUNCTIONS_PRIORITY_DEFAULT + 1,
+ RRDFUNCTIONS_VERSION_DEFAULT,
+ RRDFUNCTIONS_STREAMING_HELP,
+ "top",
+ HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_SENSITIVE_DATA,
+ function_streaming);
+
+ rrd_function_add_inline(
+ localhost,
+ NULL,
+ "netdata-api-calls",
+ 10,
+ RRDFUNCTIONS_PRIORITY_DEFAULT + 2,
+ RRDFUNCTIONS_VERSION_DEFAULT,
+ RRDFUNCTIONS_PROGRESS_HELP,
+ "top",
+ HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_SENSITIVE_DATA,
+ function_progress);
+
+ rrd_function_add_inline(
+ localhost,
+ NULL,
+ RRDFUNCTIONS_BEARER_GET_TOKEN,
+ 10,
+ RRDFUNCTIONS_PRIORITY_DEFAULT + 3,
+ RRDFUNCTIONS_VERSION_DEFAULT,
+ RRDFUNCTIONS_BEARER_GET_TOKEN_HELP,
+ RRDFUNCTIONS_TAG_HIDDEN,
+ HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_SENSITIVE_DATA,
+ function_bearer_get_token);
+}
diff --git a/src/web/api/functions/functions.h b/src/web/api/functions/functions.h
new file mode 100644
index 00000000..28c48354
--- /dev/null
+++ b/src/web/api/functions/functions.h
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_FUNCTIONS_H
+#define NETDATA_FUNCTIONS_H
+
+#include "daemon/common.h"
+
+#include "function-streaming.h"
+#include "function-progress.h"
+#include "function-bearer_get_token.h"
+
+void global_functions_add(void);
+
+#endif //NETDATA_FUNCTIONS_H
diff --git a/src/web/api/health/README.md b/src/web/api/health/README.md
index 725b4a36..a788e9b4 100644
--- a/src/web/api/health/README.md
+++ b/src/web/api/health/README.md
@@ -1,13 +1,3 @@
-<!--
-title: "Health API Calls"
-date: 2020-04-27
-custom_edit_url: https://github.com/netdata/netdata/edit/master/src/web/api/health/README.md
-sidebar_label: "Health API Calls"
-learn_status: "Published"
-learn_topic_type: "References"
-learn_rel_path: "Developers/Web/Api"
--->
-
# Health API Calls
## Health Read API
@@ -33,7 +23,7 @@ The size of the alert log is configured in `netdata.conf`. There are 2 settings:
```
[health]
in memory max health log entries = 1000
- health log history = 432000
+ health log retention = 5d
```
The API call retrieves all entries of the alert log:
diff --git a/src/web/api/http_auth.c b/src/web/api/http_auth.c
index ec052030..5c4fffca 100644
--- a/src/web/api/http_auth.c
+++ b/src/web/api/http_auth.c
@@ -2,83 +2,341 @@
#include "http_auth.h"
-#define BEARER_TOKEN_EXPIRATION 86400
+#define BEARER_TOKEN_EXPIRATION (86400 * 1)
-bool netdata_is_protected_by_bearer = false; // this is controlled by cloud, at the point the agent logs in - this should also be saved to /var/lib/netdata
+bool netdata_is_protected_by_bearer = false;
static DICTIONARY *netdata_authorized_bearers = NULL;
struct bearer_token {
nd_uuid_t cloud_account_id;
- char cloud_user_name[CLOUD_USER_NAME_LENGTH];
+ char client_name[CLOUD_CLIENT_NAME_LENGTH];
HTTP_ACCESS access;
HTTP_USER_ROLE user_role;
time_t created_s;
time_t expires_s;
};
-bool web_client_bearer_token_auth(struct web_client *w, const char *v) {
- if(!uuid_parse_flexi(v, w->auth.bearer_token)) {
- char uuid_str[UUID_COMPACT_STR_LEN];
- uuid_unparse_lower_compact(w->auth.bearer_token, uuid_str);
+static void bearer_tokens_path(char out[FILENAME_MAX]) {
+ filename_from_path_entry(out, netdata_configured_varlib_dir, "bearer_tokens", NULL);
+}
- struct bearer_token *z = dictionary_get(netdata_authorized_bearers, uuid_str);
- if (z && z->expires_s > now_monotonic_sec()) {
- strncpyz(w->auth.client_name, z->cloud_user_name, sizeof(w->auth.client_name) - 1);
- uuid_copy(w->auth.cloud_account_id, z->cloud_account_id);
- web_client_set_permissions(w, z->access, z->user_role, WEB_CLIENT_FLAG_AUTH_BEARER);
- return true;
- }
- }
- else
- nd_log(NDLS_DAEMON, NDLP_NOTICE, "Invalid bearer token '%s' received.", v);
+static void bearer_token_filename(char out[FILENAME_MAX], nd_uuid_t uuid) {
+ char uuid_str[UUID_STR_LEN];
+ uuid_unparse_lower(uuid, uuid_str);
+
+ char path[FILENAME_MAX];
+ bearer_tokens_path(path);
+ filename_from_path_entry(out, path, uuid_str, NULL);
+}
- return false;
+static inline bool bearer_tokens_ensure_path_exists(void) {
+ char path[FILENAME_MAX];
+ bearer_tokens_path(path);
+ return filename_is_dir(path, true);
}
-static void bearer_token_cleanup(void) {
+static void bearer_token_delete_from_disk(nd_uuid_t *token) {
+ char filename[FILENAME_MAX];
+ bearer_token_filename(filename, *token);
+ if(unlink(filename) != 0)
+ nd_log(NDLS_DAEMON, NDLP_ERR, "Failed to unlink() file '%s'", filename);
+}
+
+static void bearer_token_cleanup(bool force) {
static time_t attempts = 0;
- if(++attempts % 1000 != 0)
+ if(++attempts % 1000 != 0 && !force)
return;
- time_t now_s = now_monotonic_sec();
+ time_t now_s = now_realtime_sec();
struct bearer_token *z;
dfe_start_read(netdata_authorized_bearers, z) {
- if(z->expires_s < now_s)
+ if(z->expires_s < now_s) {
+ nd_uuid_t uuid;
+ if(uuid_parse_flexi(z_dfe.name, uuid) == 0)
+ bearer_token_delete_from_disk(&uuid);
+
dictionary_del(netdata_authorized_bearers, z_dfe.name);
+ }
}
dfe_done(z);
dictionary_garbage_collect(netdata_authorized_bearers);
}
-void bearer_tokens_init(void) {
- netdata_authorized_bearers = dictionary_create_advanced(
- DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
- NULL, sizeof(struct bearer_token));
+static uint64_t bearer_token_signature(nd_uuid_t token, struct bearer_token *bt) {
+ // we use a custom structure to make sure that changes in the other code will not affect the signature
+
+ struct {
+ nd_uuid_t host_uuid;
+ nd_uuid_t token;
+ nd_uuid_t cloud_account_id;
+ char client_name[CLOUD_CLIENT_NAME_LENGTH];
+ HTTP_ACCESS access;
+ HTTP_USER_ROLE user_role;
+ time_t created_s;
+ time_t expires_s;
+ } signature_payload = {
+ .access = bt->access,
+ .user_role = bt->user_role,
+ .created_s = bt->created_s,
+ .expires_s = bt->expires_s,
+ };
+ uuid_copy(signature_payload.host_uuid, localhost->host_id.uuid);
+ uuid_copy(signature_payload.token, token);
+ uuid_copy(signature_payload.cloud_account_id, bt->cloud_account_id);
+ memset(signature_payload.client_name, 0, sizeof(signature_payload.client_name));
+ strncpyz(signature_payload.client_name, bt->client_name, sizeof(signature_payload.client_name) - 1);
+
+ return XXH3_64bits(&signature_payload, sizeof(signature_payload));
}
-time_t bearer_create_token(nd_uuid_t *uuid, struct web_client *w) {
+static bool bearer_token_save_to_file(nd_uuid_t token, struct bearer_token *bt) {
+ CLEAN_BUFFER *wb = buffer_create(0, NULL);
+ buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
+ buffer_json_member_add_uint64(wb, "version", 1);
+ buffer_json_member_add_uuid(wb, "host_uuid", localhost->host_id.uuid);
+ buffer_json_member_add_uuid(wb, "token", token);
+ buffer_json_member_add_uuid(wb, "cloud_account_id", bt->cloud_account_id);
+ buffer_json_member_add_string(wb, "client_name", bt->client_name);
+ http_access2buffer_json_array(wb, "access", bt->access);
+ buffer_json_member_add_string(wb, "user_role", http_id2user_role(bt->user_role));
+ buffer_json_member_add_uint64(wb, "created_s", bt->created_s);
+ buffer_json_member_add_uint64(wb, "expires_s", bt->expires_s);
+ buffer_json_member_add_uint64(wb, "signature", bearer_token_signature(token, bt));
+ buffer_json_finalize(wb);
+
+ char filename[FILENAME_MAX];
+ bearer_token_filename(filename, token);
+
+ FILE *fp = fopen(filename, "w");
+ if(!fp) {
+ nd_log(NDLS_DAEMON, NDLP_ERR, "Cannot create file '%s'", filename);
+ return false;
+ }
+
+ if(fwrite(buffer_tostring(wb), 1, buffer_strlen(wb), fp) != buffer_strlen(wb)) {
+ fclose(fp);
+ unlink(filename);
+ nd_log(NDLS_DAEMON, NDLP_ERR, "Cannot save file '%s'", filename);
+ return false;
+ }
+
+ fclose(fp);
+ return true;
+}
+
+static time_t bearer_create_token_internal(nd_uuid_t token, HTTP_USER_ROLE user_role, HTTP_ACCESS access, nd_uuid_t cloud_account_id, const char *client_name, time_t created_s, time_t expires_s, bool save) {
char uuid_str[UUID_COMPACT_STR_LEN];
+ uuid_unparse_lower_compact(token, uuid_str);
+
+ struct bearer_token t = { 0 }, *bt;
+ const DICTIONARY_ITEM *item = dictionary_set_and_acquire_item(netdata_authorized_bearers, uuid_str, &t, sizeof(t));
+ bt = dictionary_acquired_item_value(item);
+
+ if(!bt->created_s) {
+ bt->created_s = created_s;
+ bt->expires_s = expires_s;
+ bt->user_role = user_role;
+ bt->access = access;
+
+ uuid_copy(bt->cloud_account_id, cloud_account_id);
+ strncpyz(bt->client_name, client_name, sizeof(bt->cloud_account_id) - 1);
+
+ if(save)
+ bearer_token_save_to_file(token, bt);
+ }
+
+ time_t expiration = bt->expires_s;
+
+ dictionary_acquired_item_release(netdata_authorized_bearers, item);
+
+ return expiration;
+}
+
+time_t bearer_create_token(nd_uuid_t *uuid, HTTP_USER_ROLE user_role, HTTP_ACCESS access, nd_uuid_t cloud_account_id, const char *client_name) {
+ time_t now_s = now_realtime_sec();
+ time_t expires_s = 0;
+
+ struct bearer_token *bt;
+ dfe_start_read(netdata_authorized_bearers, bt) {
+ if(bt->expires_s > now_s + 3600 * 2 && // expires in more than 2 hours
+ user_role == bt->user_role && // the user_role matches
+ access == bt->access && // the access matches
+ uuid_eq(cloud_account_id, bt->cloud_account_id) && // the cloud_account_id matches
+ strncmp(client_name, bt->client_name, sizeof(bt->client_name) - 1) == 0 && // the client_name matches
+ uuid_parse_flexi(bt_dfe.name, *uuid) == 0) // the token can be parsed
+ return expires_s; /* dfe will cleanup automatically */
+ }
+ dfe_done(bt);
uuid_generate_random(*uuid);
- uuid_unparse_lower_compact(*uuid, uuid_str);
-
- struct bearer_token t = { 0 }, *z;
- z = dictionary_set(netdata_authorized_bearers, uuid_str, &t, sizeof(t));
- if(!z->created_s) {
- z->created_s = now_monotonic_sec();
- z->expires_s = z->created_s + BEARER_TOKEN_EXPIRATION;
- z->user_role = w->user_role;
- z->access = w->access;
- uuid_copy(z->cloud_account_id, w->auth.cloud_account_id);
- strncpyz(z->cloud_user_name, w->auth.client_name, sizeof(z->cloud_account_id) - 1);
+ expires_s = bearer_create_token_internal(
+ *uuid, user_role, access, cloud_account_id, client_name,
+ now_s, now_s + BEARER_TOKEN_EXPIRATION, true);
+
+ bearer_token_cleanup(false);
+
+ return expires_s;
+}
+
+static bool bearer_token_parse_json(nd_uuid_t token, struct json_object *jobj, BUFFER *error) {
+ int64_t version;
+ nd_uuid_t token_in_file, cloud_account_id, host_uuid;
+ CLEAN_STRING *client_name = NULL;
+ HTTP_USER_ROLE user_role = HTTP_USER_ROLE_NONE;
+ HTTP_ACCESS access = HTTP_ACCESS_NONE;
+ time_t created_s = 0, expires_s = 0;
+ uint64_t signature = 0;
+
+ JSONC_PARSE_INT64_OR_ERROR_AND_RETURN(jobj, ".", "version", version, error, true);
+ JSONC_PARSE_TXT2UUID_OR_ERROR_AND_RETURN(jobj, ".", "host_uuid", host_uuid, error, true);
+ JSONC_PARSE_TXT2UUID_OR_ERROR_AND_RETURN(jobj, ".", "token", token_in_file, error, true);
+ JSONC_PARSE_TXT2UUID_OR_ERROR_AND_RETURN(jobj, ".", "cloud_account_id", cloud_account_id, error, true);
+ JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, ".", "client_name", client_name, error, true);
+ JSONC_PARSE_ARRAY_OF_TXT2BITMAP_OR_ERROR_AND_RETURN(jobj, ".", "access", http_access2id_one, access, error, true);
+ JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, ".", "user_role", http_user_role2id, user_role, error, true);
+ JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, ".", "created_s", created_s, error, true);
+ JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, ".", "expires_s", expires_s, error, true);
+ JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, ".", "signature", signature, error, true);
+
+ if(uuid_compare(token, token_in_file) != 0) {
+ buffer_flush(error);
+ buffer_strcat(error, "token in JSON file does not match the filename");
+ return false;
+ }
+
+ if(uuid_compare(host_uuid, localhost->host_id.uuid) != 0) {
+ buffer_flush(error);
+ buffer_strcat(error, "Host UUID in JSON file does not match our host UUID");
+ return false;
+ }
+
+ if(!created_s || !expires_s || created_s >= expires_s) {
+ buffer_flush(error);
+ buffer_strcat(error, "bearer token has invalid dates");
+ return false;
+ }
+
+ struct bearer_token bt = {
+ .access = access,
+ .user_role = user_role,
+ .created_s = created_s,
+ .expires_s = expires_s,
+ };
+ uuid_copy(bt.cloud_account_id, cloud_account_id);
+ strncpyz(bt.client_name, string2str(client_name), sizeof(bt.client_name) - 1);
+
+ if(signature != bearer_token_signature(token_in_file, &bt)) {
+ buffer_flush(error);
+ buffer_strcat(error, "bearer token has invalid signature");
+ return false;
+ }
+
+ bearer_create_token_internal(token, user_role, access,
+ cloud_account_id, string2str(client_name),
+ created_s, expires_s, false);
+
+ return true;
+}
+
+static bool bearer_token_load_token(nd_uuid_t token) {
+ char filename[FILENAME_MAX];
+ bearer_token_filename(filename, token);
+
+ CLEAN_BUFFER *wb = buffer_create(0, NULL);
+ if(!read_txt_file_to_buffer(filename, wb, 1 * 1024 * 1024))
+ return false;
+
+ CLEAN_JSON_OBJECT *jobj = json_tokener_parse(buffer_tostring(wb));
+ if (jobj == NULL) {
+ nd_log(NDLS_DAEMON, NDLP_ERR, "Cannot parse bearer token file '%s'", filename);
+ return false;
+ }
+
+ CLEAN_BUFFER *error = buffer_create(0, NULL);
+ bool rc = bearer_token_parse_json(token, jobj, error);
+ if(!rc) {
+ nd_log(NDLS_DAEMON, NDLP_ERR, "Failed to parse bearer token file '%s': %s", filename, buffer_tostring(error));
+ unlink(filename);
+ return false;
+ }
+
+ bearer_token_cleanup(true);
+
+ return true;
+}
+
+static void bearer_tokens_load_from_disk(void) {
+ bearer_tokens_ensure_path_exists();
+
+ char path[FILENAME_MAX];
+ bearer_tokens_path(path);
+
+ DIR *dir = opendir(path);
+ if(!dir) {
+ nd_log(NDLS_DAEMON, NDLP_ERR, "Cannot open directory '%s' to read saved bearer tokens", path);
+ return;
}
- bearer_token_cleanup();
+ struct dirent *de;
+ while((de = readdir(dir))) {
+ if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
+ continue;
+
+ ND_UUID uuid = UUID_ZERO;
+ if(uuid_parse_flexi(de->d_name, uuid.uuid) != 0 || UUIDiszero(uuid))
+ continue;
+
+ char filename[FILENAME_MAX];
+ filename_from_path_entry(filename, path, de->d_name, NULL);
+
+ if(de->d_type == DT_REG || (de->d_type == DT_LNK && filename_is_file(filename)))
+ bearer_token_load_token(uuid.uuid);
+ }
+
+ closedir(dir);
+}
+
+bool web_client_bearer_token_auth(struct web_client *w, const char *v) {
+ bool rc = false;
+
+ if(!uuid_parse_flexi(v, w->auth.bearer_token)) {
+ char uuid_str[UUID_COMPACT_STR_LEN];
+ uuid_unparse_lower_compact(w->auth.bearer_token, uuid_str);
+
+ const DICTIONARY_ITEM *item = dictionary_get_and_acquire_item(netdata_authorized_bearers, uuid_str);
+ if(!item && bearer_token_load_token(w->auth.bearer_token))
+ item = dictionary_get_and_acquire_item(netdata_authorized_bearers, uuid_str);
+
+ if(item) {
+ struct bearer_token *bt = dictionary_acquired_item_value(item);
+ if (bt->expires_s > now_realtime_sec()) {
+ strncpyz(w->auth.client_name, bt->client_name, sizeof(w->auth.client_name) - 1);
+ uuid_copy(w->auth.cloud_account_id, bt->cloud_account_id);
+ web_client_set_permissions(w, bt->access, bt->user_role, WEB_CLIENT_FLAG_AUTH_BEARER);
+ rc = true;
+ }
+
+ dictionary_acquired_item_release(netdata_authorized_bearers, item);
+ }
+ }
+ else
+ nd_log(NDLS_DAEMON, NDLP_NOTICE, "Invalid bearer token '%s' received.", v);
+
+ return rc;
+}
+
+void bearer_tokens_init(void) {
+ netdata_is_protected_by_bearer =
+ config_get_boolean(CONFIG_SECTION_WEB, "bearer token protection", netdata_is_protected_by_bearer);
+
+ netdata_authorized_bearers = dictionary_create_advanced(
+ DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
+ NULL, sizeof(struct bearer_token));
- return now_realtime_sec() + BEARER_TOKEN_EXPIRATION;
+ bearer_tokens_load_from_disk();
}
bool extract_bearer_token_from_request(struct web_client *w, char *dst, size_t dst_len) {
diff --git a/src/web/api/http_auth.h b/src/web/api/http_auth.h
index f339a44c..0b01fdb1 100644
--- a/src/web/api/http_auth.h
+++ b/src/web/api/http_auth.h
@@ -11,7 +11,7 @@ extern bool netdata_is_protected_by_bearer;
bool extract_bearer_token_from_request(struct web_client *w, char *dst, size_t dst_len);
-time_t bearer_create_token(nd_uuid_t *uuid, struct web_client *w);
+time_t bearer_create_token(nd_uuid_t *uuid, HTTP_USER_ROLE user_role, HTTP_ACCESS access, nd_uuid_t cloud_account_id, const char *client_name);
bool web_client_bearer_token_auth(struct web_client *w, const char *v);
static inline bool http_access_user_has_enough_access_level_for_endpoint(HTTP_ACCESS user, HTTP_ACCESS endpoint) {
diff --git a/src/web/api/ilove/ilove.h b/src/web/api/ilove/ilove.h
deleted file mode 100644
index 010c19c6..00000000
--- a/src/web/api/ilove/ilove.h
+++ /dev/null
@@ -1,13 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#ifndef NETDATA_WEB_API_ILOVE_H
-#define NETDATA_WEB_API_ILOVE_H 1
-
-#include "libnetdata/libnetdata.h"
-#include "web/server/web_client.h"
-
-int web_client_api_request_v2_ilove(RRDHOST *host, struct web_client *w, char *url);
-
-#include "web/api/web_api_v1.h"
-
-#endif /* NETDATA_WEB_API_ILOVE_H */
diff --git a/src/web/api/maps/contexts_alert_statuses.c b/src/web/api/maps/contexts_alert_statuses.c
new file mode 100644
index 00000000..d3565c9e
--- /dev/null
+++ b/src/web/api/maps/contexts_alert_statuses.c
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "contexts_alert_statuses.h"
+
+static struct {
+ const char *name;
+ uint32_t hash;
+ CONTEXTS_ALERT_STATUS value;
+} contexts_alert_status[] = {
+ {"uninitialized" , 0 , CONTEXT_ALERT_UNINITIALIZED}
+ , {"undefined" , 0 , CONTEXT_ALERT_UNDEFINED}
+ , {"clear" , 0 , CONTEXT_ALERT_CLEAR}
+ , {"raised" , 0 , CONTEXT_ALERT_RAISED}
+ , {"active" , 0 , CONTEXT_ALERT_RAISED}
+ , {"warning" , 0 , CONTEXT_ALERT_WARNING}
+ , {"critical" , 0 , CONTEXT_ALERT_CRITICAL}
+ , {NULL , 0 , 0}
+};
+
+CONTEXTS_ALERT_STATUS contexts_alert_status_str_to_id(char *o) {
+ CONTEXTS_ALERT_STATUS ret = 0;
+ char *tok;
+
+ while(o && *o && (tok = strsep_skip_consecutive_separators(&o, ", |"))) {
+ if(!*tok) continue;
+
+ uint32_t hash = simple_hash(tok);
+ int i;
+ for(i = 0; contexts_alert_status[i].name ; i++) {
+ if (unlikely(hash == contexts_alert_status[i].hash && !strcmp(tok, contexts_alert_status[i].name))) {
+ ret |= contexts_alert_status[i].value;
+ break;
+ }
+ }
+ }
+
+ return ret;
+}
+
+void contexts_alerts_status_to_buffer_json_array(BUFFER *wb, const char *key,
+ CONTEXTS_ALERT_STATUS options) {
+ buffer_json_member_add_array(wb, key);
+
+ CONTEXTS_ALERT_STATUS used = 0; // to prevent adding duplicates
+ for(int i = 0; contexts_alert_status[i].name ; i++) {
+ if (unlikely((contexts_alert_status[i].value & options) && !(contexts_alert_status[i].value & used))) {
+ const char *name = contexts_alert_status[i].name;
+ used |= contexts_alert_status[i].value;
+
+ buffer_json_add_array_item_string(wb, name);
+ }
+ }
+
+ buffer_json_array_close(wb);
+}
+
+void contexts_alert_statuses_init(void) {
+ for(size_t i = 0; contexts_alert_status[i].name ; i++)
+ contexts_alert_status[i].hash = simple_hash(contexts_alert_status[i].name);
+}
diff --git a/src/web/api/maps/contexts_alert_statuses.h b/src/web/api/maps/contexts_alert_statuses.h
new file mode 100644
index 00000000..1c38cb97
--- /dev/null
+++ b/src/web/api/maps/contexts_alert_statuses.h
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_CONTEXTS_ALERT_STATUSES_H
+#define NETDATA_CONTEXTS_ALERT_STATUSES_H
+
+#include "libnetdata/libnetdata.h"
+
+typedef enum contexts_alert_status {
+ CONTEXT_ALERT_UNINITIALIZED = (1 << 6), // include UNINITIALIZED alerts
+ CONTEXT_ALERT_UNDEFINED = (1 << 7), // include UNDEFINED alerts
+ CONTEXT_ALERT_CLEAR = (1 << 8), // include CLEAR alerts
+ CONTEXT_ALERT_RAISED = (1 << 9), // include WARNING & CRITICAL alerts
+ CONTEXT_ALERT_WARNING = (1 << 10), // include WARNING alerts
+ CONTEXT_ALERT_CRITICAL = (1 << 11), // include CRITICAL alerts
+} CONTEXTS_ALERT_STATUS;
+
+#define CONTEXTS_ALERT_STATUSES (CONTEXT_ALERT_UNINITIALIZED | CONTEXT_ALERT_UNDEFINED | CONTEXT_ALERT_CLEAR | \
+ CONTEXT_ALERT_RAISED | CONTEXT_ALERT_WARNING | CONTEXT_ALERT_CRITICAL)
+
+CONTEXTS_ALERT_STATUS contexts_alert_status_str_to_id(char *o);
+void contexts_alerts_status_to_buffer_json_array(BUFFER *wb, const char *key,
+ CONTEXTS_ALERT_STATUS options);
+
+void contexts_alert_statuses_init(void);
+
+#endif //NETDATA_CONTEXTS_ALERT_STATUSES_H
diff --git a/src/web/api/maps/contexts_options.c b/src/web/api/maps/contexts_options.c
new file mode 100644
index 00000000..22e50e8d
--- /dev/null
+++ b/src/web/api/maps/contexts_options.c
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "contexts_options.h"
+
+static struct {
+ const char *name;
+ uint32_t hash;
+ CONTEXTS_OPTIONS value;
+} contexts_options[] = {
+ {"minify" , 0 , CONTEXTS_OPTION_MINIFY}
+ , {"debug" , 0 , CONTEXTS_OPTION_DEBUG}
+ , {"config" , 0 , CONTEXTS_OPTION_ALERTS_WITH_CONFIGURATIONS}
+ , {"instances" , 0 , CONTEXTS_OPTION_ALERTS_WITH_INSTANCES}
+ , {"values" , 0 , CONTEXTS_OPTION_ALERTS_WITH_VALUES}
+ , {"summary" , 0 , CONTEXTS_OPTION_ALERTS_WITH_SUMMARY}
+ , {NULL , 0 , 0}
+};
+
+CONTEXTS_OPTIONS contexts_options_str_to_id(char *o) {
+ CONTEXTS_OPTIONS ret = 0;
+ char *tok;
+
+ while(o && *o && (tok = strsep_skip_consecutive_separators(&o, ", |"))) {
+ if(!*tok) continue;
+
+ uint32_t hash = simple_hash(tok);
+ int i;
+ for(i = 0; contexts_options[i].name ; i++) {
+ if (unlikely(hash == contexts_options[i].hash && !strcmp(tok, contexts_options[i].name))) {
+ ret |= contexts_options[i].value;
+ break;
+ }
+ }
+ }
+
+ return ret;
+}
+
+void contexts_options_to_buffer_json_array(BUFFER *wb, const char *key, CONTEXTS_OPTIONS options) {
+ buffer_json_member_add_array(wb, key);
+
+ CONTEXTS_OPTIONS used = 0; // to prevent adding duplicates
+ for(int i = 0; contexts_options[i].name ; i++) {
+ if (unlikely((contexts_options[i].value & options) && !(contexts_options[i].value & used))) {
+ const char *name = contexts_options[i].name;
+ used |= contexts_options[i].value;
+
+ buffer_json_add_array_item_string(wb, name);
+ }
+ }
+
+ buffer_json_array_close(wb);
+}
+
+void contexts_options_init(void) {
+ for(size_t i = 0; contexts_options[i].name ; i++)
+ contexts_options[i].hash = simple_hash(contexts_options[i].name);
+}
diff --git a/src/web/api/maps/contexts_options.h b/src/web/api/maps/contexts_options.h
new file mode 100644
index 00000000..a21bd76c
--- /dev/null
+++ b/src/web/api/maps/contexts_options.h
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_CONTEXTS_OPTIONS_H
+#define NETDATA_CONTEXTS_OPTIONS_H
+
+#include "libnetdata/libnetdata.h"
+
+typedef enum contexts_options {
+ CONTEXTS_OPTION_MINIFY = (1 << 0), // remove JSON spaces and newlines from JSON output
+ CONTEXTS_OPTION_DEBUG = (1 << 1), // show the request
+ CONTEXTS_OPTION_ALERTS_WITH_CONFIGURATIONS = (1 << 2), // include alert configurations (used by /api/v2/alert_transitions)
+ CONTEXTS_OPTION_ALERTS_WITH_INSTANCES = (1 << 3), // include alert instances (used by /api/v2/alerts)
+ CONTEXTS_OPTION_ALERTS_WITH_VALUES = (1 << 4), // include alert latest values (used by /api/v2/alerts)
+ CONTEXTS_OPTION_ALERTS_WITH_SUMMARY = (1 << 5), // include alerts summary counters (used by /api/v2/alerts)
+} CONTEXTS_OPTIONS;
+
+CONTEXTS_OPTIONS contexts_options_str_to_id(char *o);
+void contexts_options_to_buffer_json_array(BUFFER *wb, const char *key, CONTEXTS_OPTIONS options);
+
+void contexts_options_init(void);
+
+#endif //NETDATA_CONTEXTS_OPTIONS_H
diff --git a/src/web/api/maps/datasource_formats.c b/src/web/api/maps/datasource_formats.c
new file mode 100644
index 00000000..33e1e745
--- /dev/null
+++ b/src/web/api/maps/datasource_formats.c
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "datasource_formats.h"
+
+static struct {
+ const char *name;
+ uint32_t hash;
+ DATASOURCE_FORMAT value;
+} google_data_formats[] = {
+ // this is not an error - when Google requests json, it expects javascript
+ // https://developers.google.com/chart/interactive/docs/dev/implementing_data_source#responseformat
+ {"json", 0, DATASOURCE_DATATABLE_JSONP}
+ , {"html", 0, DATASOURCE_HTML}
+ , {"csv", 0, DATASOURCE_CSV}
+ , {"tsv-excel", 0, DATASOURCE_TSV}
+
+ // terminator
+ , {NULL, 0, 0}
+};
+
+inline DATASOURCE_FORMAT google_data_format_str_to_id(char *name) {
+ uint32_t hash = simple_hash(name);
+ int i;
+
+ for(i = 0; google_data_formats[i].name ; i++) {
+ if (unlikely(hash == google_data_formats[i].hash && !strcmp(name, google_data_formats[i].name))) {
+ return google_data_formats[i].value;
+ }
+ }
+
+ return DATASOURCE_JSON;
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+static struct {
+ const char *name;
+ uint32_t hash;
+ DATASOURCE_FORMAT value;
+} datasource_formats[] = {
+ { "datatable" , 0 , DATASOURCE_DATATABLE_JSON}
+ , {"datasource" , 0 , DATASOURCE_DATATABLE_JSONP}
+ , {"json" , 0 , DATASOURCE_JSON}
+ , {"json2" , 0 , DATASOURCE_JSON2}
+ , {"jsonp" , 0 , DATASOURCE_JSONP}
+ , {"ssv" , 0 , DATASOURCE_SSV}
+ , {"csv" , 0 , DATASOURCE_CSV}
+ , {"tsv" , 0 , DATASOURCE_TSV}
+ , {"tsv-excel" , 0 , DATASOURCE_TSV}
+ , {"html" , 0 , DATASOURCE_HTML}
+ , {"array" , 0 , DATASOURCE_JS_ARRAY}
+ , {"ssvcomma" , 0 , DATASOURCE_SSV_COMMA}
+ , {"csvjsonarray" , 0 , DATASOURCE_CSV_JSON_ARRAY}
+ , {"markdown" , 0 , DATASOURCE_CSV_MARKDOWN}
+
+ // terminator
+ , {NULL, 0, 0}
+};
+
+DATASOURCE_FORMAT datasource_format_str_to_id(char *name) {
+ uint32_t hash = simple_hash(name);
+ int i;
+
+ for(i = 0; datasource_formats[i].name ; i++) {
+ if (unlikely(hash == datasource_formats[i].hash && !strcmp(name, datasource_formats[i].name))) {
+ return datasource_formats[i].value;
+ }
+ }
+
+ return DATASOURCE_JSON;
+}
+
+const char *rrdr_format_to_string(DATASOURCE_FORMAT format) {
+ for(size_t i = 0; datasource_formats[i].name ;i++)
+ if(unlikely(datasource_formats[i].value == format))
+ return datasource_formats[i].name;
+
+ return "unknown";
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+void datasource_formats_init(void) {
+ for(size_t i = 0; datasource_formats[i].name ; i++)
+ datasource_formats[i].hash = simple_hash(datasource_formats[i].name);
+
+ for(size_t i = 0; google_data_formats[i].name ; i++)
+ google_data_formats[i].hash = simple_hash(google_data_formats[i].name);
+}
diff --git a/src/web/api/maps/datasource_formats.h b/src/web/api/maps/datasource_formats.h
new file mode 100644
index 00000000..50d8a82b
--- /dev/null
+++ b/src/web/api/maps/datasource_formats.h
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_DATASOURCE_FORMATS_H
+#define NETDATA_DATASOURCE_FORMATS_H
+
+#include "libnetdata/libnetdata.h"
+
+// type of JSON generations
+typedef enum {
+ DATASOURCE_JSON = 0,
+ DATASOURCE_DATATABLE_JSON,
+ DATASOURCE_DATATABLE_JSONP,
+ DATASOURCE_SSV,
+ DATASOURCE_CSV,
+ DATASOURCE_JSONP,
+ DATASOURCE_TSV,
+ DATASOURCE_HTML,
+ DATASOURCE_JS_ARRAY,
+ DATASOURCE_SSV_COMMA,
+ DATASOURCE_CSV_JSON_ARRAY,
+ DATASOURCE_CSV_MARKDOWN,
+ DATASOURCE_JSON2,
+} DATASOURCE_FORMAT;
+
+DATASOURCE_FORMAT datasource_format_str_to_id(char *name);
+const char *rrdr_format_to_string(DATASOURCE_FORMAT format);
+
+DATASOURCE_FORMAT google_data_format_str_to_id(char *name);
+
+void datasource_formats_init(void);
+
+#endif //NETDATA_DATASOURCE_FORMATS_H
diff --git a/src/web/api/maps/maps.h b/src/web/api/maps/maps.h
new file mode 100644
index 00000000..25d21023
--- /dev/null
+++ b/src/web/api/maps/maps.h
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_MAPS_H
+#define NETDATA_MAPS_H
+
+#include "libnetdata/libnetdata.h"
+#include "datasource_formats.h"
+#include "contexts_options.h"
+#include "rrdr_options.h"
+#include "contexts_alert_statuses.h"
+
+#endif //NETDATA_MAPS_H
diff --git a/src/web/api/maps/rrdr_options.c b/src/web/api/maps/rrdr_options.c
new file mode 100644
index 00000000..41161d80
--- /dev/null
+++ b/src/web/api/maps/rrdr_options.c
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rrdr_options.h"
+
+static struct {
+ const char *name;
+ uint32_t hash;
+ RRDR_OPTIONS value;
+} rrdr_options[] = {
+ { "nonzero" , 0 , RRDR_OPTION_NONZERO}
+ , {"flip" , 0 , RRDR_OPTION_REVERSED}
+ , {"reversed" , 0 , RRDR_OPTION_REVERSED}
+ , {"reverse" , 0 , RRDR_OPTION_REVERSED}
+ , {"jsonwrap" , 0 , RRDR_OPTION_JSON_WRAP}
+ , {"min2max" , 0 , RRDR_OPTION_DIMS_MIN2MAX} // rrdr2value() only
+ , {"average" , 0 , RRDR_OPTION_DIMS_AVERAGE} // rrdr2value() only
+ , {"min" , 0 , RRDR_OPTION_DIMS_MIN} // rrdr2value() only
+ , {"max" , 0 , RRDR_OPTION_DIMS_MAX} // rrdr2value() only
+ , {"ms" , 0 , RRDR_OPTION_MILLISECONDS}
+ , {"milliseconds" , 0 , RRDR_OPTION_MILLISECONDS}
+ , {"absolute" , 0 , RRDR_OPTION_ABSOLUTE}
+ , {"abs" , 0 , RRDR_OPTION_ABSOLUTE}
+ , {"absolute_sum" , 0 , RRDR_OPTION_ABSOLUTE}
+ , {"absolute-sum" , 0 , RRDR_OPTION_ABSOLUTE}
+ , {"display_absolute" , 0 , RRDR_OPTION_DISPLAY_ABS}
+ , {"display-absolute" , 0 , RRDR_OPTION_DISPLAY_ABS}
+ , {"seconds" , 0 , RRDR_OPTION_SECONDS}
+ , {"null2zero" , 0 , RRDR_OPTION_NULL2ZERO}
+ , {"objectrows" , 0 , RRDR_OPTION_OBJECTSROWS}
+ , {"google_json" , 0 , RRDR_OPTION_GOOGLE_JSON}
+ , {"google-json" , 0 , RRDR_OPTION_GOOGLE_JSON}
+ , {"percentage" , 0 , RRDR_OPTION_PERCENTAGE}
+ , {"unaligned" , 0 , RRDR_OPTION_NOT_ALIGNED}
+ , {"match_ids" , 0 , RRDR_OPTION_MATCH_IDS}
+ , {"match-ids" , 0 , RRDR_OPTION_MATCH_IDS}
+ , {"match_names" , 0 , RRDR_OPTION_MATCH_NAMES}
+ , {"match-names" , 0 , RRDR_OPTION_MATCH_NAMES}
+ , {"anomaly-bit" , 0 , RRDR_OPTION_ANOMALY_BIT}
+ , {"selected-tier" , 0 , RRDR_OPTION_SELECTED_TIER}
+ , {"raw" , 0 , RRDR_OPTION_RETURN_RAW}
+ , {"jw-anomaly-rates" , 0 , RRDR_OPTION_RETURN_JWAR}
+ , {"natural-points" , 0 , RRDR_OPTION_NATURAL_POINTS}
+ , {"virtual-points" , 0 , RRDR_OPTION_VIRTUAL_POINTS}
+ , {"all-dimensions" , 0 , RRDR_OPTION_ALL_DIMENSIONS}
+ , {"details" , 0 , RRDR_OPTION_SHOW_DETAILS}
+ , {"debug" , 0 , RRDR_OPTION_DEBUG}
+ , {"plan" , 0 , RRDR_OPTION_DEBUG}
+ , {"minify" , 0 , RRDR_OPTION_MINIFY}
+ , {"group-by-labels" , 0 , RRDR_OPTION_GROUP_BY_LABELS}
+ , {"label-quotes" , 0 , RRDR_OPTION_LABEL_QUOTES}
+ , {NULL , 0 , 0}
+};
+
+RRDR_OPTIONS rrdr_options_parse_one(const char *o) {
+ RRDR_OPTIONS ret = 0;
+
+ if(!o || !*o) return ret;
+
+ uint32_t hash = simple_hash(o);
+ int i;
+ for(i = 0; rrdr_options[i].name ; i++) {
+ if (unlikely(hash == rrdr_options[i].hash && !strcmp(o, rrdr_options[i].name))) {
+ ret |= rrdr_options[i].value;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+RRDR_OPTIONS rrdr_options_parse(char *o) {
+ RRDR_OPTIONS ret = 0;
+ char *tok;
+
+ while(o && *o && (tok = strsep_skip_consecutive_separators(&o, ", |"))) {
+ if(!*tok) continue;
+ ret |= rrdr_options_parse_one(tok);
+ }
+
+ return ret;
+}
+
+void rrdr_options_to_buffer_json_array(BUFFER *wb, const char *key, RRDR_OPTIONS options) {
+ buffer_json_member_add_array(wb, key);
+
+ RRDR_OPTIONS used = 0; // to prevent adding duplicates
+ for(int i = 0; rrdr_options[i].name ; i++) {
+ if (unlikely((rrdr_options[i].value & options) && !(rrdr_options[i].value & used))) {
+ const char *name = rrdr_options[i].name;
+ used |= rrdr_options[i].value;
+
+ buffer_json_add_array_item_string(wb, name);
+ }
+ }
+
+ buffer_json_array_close(wb);
+}
+
+void rrdr_options_to_buffer(BUFFER *wb, RRDR_OPTIONS options) {
+ RRDR_OPTIONS used = 0; // to prevent adding duplicates
+ size_t added = 0;
+ for(int i = 0; rrdr_options[i].name ; i++) {
+ if (unlikely((rrdr_options[i].value & options) && !(rrdr_options[i].value & used))) {
+ const char *name = rrdr_options[i].name;
+ used |= rrdr_options[i].value;
+
+ if(added++) buffer_strcat(wb, " ");
+ buffer_strcat(wb, name);
+ }
+ }
+}
+
+void web_client_api_request_data_vX_options_to_string(char *buf, size_t size, RRDR_OPTIONS options) {
+ char *write = buf;
+ char *end = &buf[size - 1];
+
+ RRDR_OPTIONS used = 0; // to prevent adding duplicates
+ int added = 0;
+ for(int i = 0; rrdr_options[i].name ; i++) {
+ if (unlikely((rrdr_options[i].value & options) && !(rrdr_options[i].value & used))) {
+ const char *name = rrdr_options[i].name;
+ used |= rrdr_options[i].value;
+
+ if(added && write < end)
+ *write++ = ',';
+
+ while(*name && write < end)
+ *write++ = *name++;
+
+ added++;
+ }
+ }
+ *write = *end = '\0';
+}
+
+void rrdr_options_init(void) {
+ for(size_t i = 0; rrdr_options[i].name ; i++)
+ rrdr_options[i].hash = simple_hash(rrdr_options[i].name);
+}
diff --git a/src/web/api/maps/rrdr_options.h b/src/web/api/maps/rrdr_options.h
new file mode 100644
index 00000000..4b6697db
--- /dev/null
+++ b/src/web/api/maps/rrdr_options.h
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_RRDR_OPTIONS_H
+#define NETDATA_RRDR_OPTIONS_H
+
+#include "libnetdata/libnetdata.h"
+
+typedef enum rrdr_options {
+ RRDR_OPTION_NONZERO = (1 << 0), // don't output dimensions with just zero values
+ RRDR_OPTION_REVERSED = (1 << 1), // output the rows in reverse order (oldest to newest)
+ RRDR_OPTION_ABSOLUTE = (1 << 2), // values positive, for DATASOURCE_SSV before summing
+ RRDR_OPTION_DIMS_MIN2MAX = (1 << 3), // when adding dimensions, use max - min, instead of sum
+ RRDR_OPTION_DIMS_AVERAGE = (1 << 4), // when adding dimensions, use average, instead of sum
+ RRDR_OPTION_DIMS_MIN = (1 << 5), // when adding dimensions, use minimum, instead of sum
+ RRDR_OPTION_DIMS_MAX = (1 << 6), // when adding dimensions, use maximum, instead of sum
+ RRDR_OPTION_SECONDS = (1 << 7), // output seconds, instead of dates
+ RRDR_OPTION_MILLISECONDS = (1 << 8), // output milliseconds, instead of dates
+ RRDR_OPTION_NULL2ZERO = (1 << 9), // do not show nulls, convert them to zeros
+ RRDR_OPTION_OBJECTSROWS = (1 << 10), // each row of values should be an object, not an array
+ RRDR_OPTION_GOOGLE_JSON = (1 << 11), // comply with google JSON/JSONP specs
+ RRDR_OPTION_JSON_WRAP = (1 << 12), // wrap the response in a JSON header with info about the result
+ RRDR_OPTION_LABEL_QUOTES = (1 << 13), // in CSV output, wrap header labels in double quotes
+ RRDR_OPTION_PERCENTAGE = (1 << 14), // give values as percentage of total
+ RRDR_OPTION_NOT_ALIGNED = (1 << 15), // do not align charts for persistent timeframes
+ RRDR_OPTION_DISPLAY_ABS = (1 << 16), // for badges, display the absolute value, but calculate colors with sign
+ RRDR_OPTION_MATCH_IDS = (1 << 17), // when filtering dimensions, match only IDs
+ RRDR_OPTION_MATCH_NAMES = (1 << 18), // when filtering dimensions, match only names
+ RRDR_OPTION_NATURAL_POINTS = (1 << 19), // return the natural points of the database
+ RRDR_OPTION_VIRTUAL_POINTS = (1 << 20), // return virtual points
+ RRDR_OPTION_ANOMALY_BIT = (1 << 21), // Return the anomaly bit stored in each collected_number
+ RRDR_OPTION_RETURN_RAW = (1 << 22), // Return raw data for aggregating across multiple nodes
+ RRDR_OPTION_RETURN_JWAR = (1 << 23), // Return anomaly rates in jsonwrap
+ RRDR_OPTION_SELECTED_TIER = (1 << 24), // Use the selected tier for the query
+ RRDR_OPTION_ALL_DIMENSIONS = (1 << 25), // Return the full dimensions list
+ RRDR_OPTION_SHOW_DETAILS = (1 << 26), // v2 returns detailed object tree
+ RRDR_OPTION_DEBUG = (1 << 27), // v2 returns request description
+ RRDR_OPTION_MINIFY = (1 << 28), // remove JSON spaces and newlines from JSON output
+ RRDR_OPTION_GROUP_BY_LABELS = (1 << 29), // v2 returns flattened labels per dimension of the chart
+
+ // internal ones - not to be exposed to the API
+ RRDR_OPTION_INTERNAL_AR = (1 << 31), // internal use only, to let the formatters know we want to render the anomaly rate
+} RRDR_OPTIONS;
+
+void rrdr_options_to_buffer(BUFFER *wb, RRDR_OPTIONS options);
+void rrdr_options_to_buffer_json_array(BUFFER *wb, const char *key, RRDR_OPTIONS options);
+void web_client_api_request_data_vX_options_to_string(char *buf, size_t size, RRDR_OPTIONS options);
+void rrdr_options_init(void);
+
+RRDR_OPTIONS rrdr_options_parse(char *o);
+RRDR_OPTIONS rrdr_options_parse_one(const char *o);
+
+#endif //NETDATA_RRDR_OPTIONS_H
diff --git a/src/web/api/queries/average/README.md b/src/web/api/queries/average/README.md
index 1ad78bee..97fb8beb 100644
--- a/src/web/api/queries/average/README.md
+++ b/src/web/api/queries/average/README.md
@@ -1,12 +1,3 @@
-<!--
-title: "Average or Mean"
-sidebar_label: "Average or Mean"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/src/web/api/queries/average/README.md
-learn_status: "Published"
-learn_topic_type: "References"
-learn_rel_path: "Developers/Web/Api/Queries"
--->
-
# Average or Mean
> This query is available as `average` and `mean`.
diff --git a/src/web/api/queries/countif/README.md b/src/web/api/queries/countif/README.md
index a4053539..1b7b682c 100644
--- a/src/web/api/queries/countif/README.md
+++ b/src/web/api/queries/countif/README.md
@@ -1,12 +1,3 @@
-<!--
-title: "CountIf"
-sidebar_label: "CountIf"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/src/web/api/queries/countif/README.md
-learn_status: "Published"
-learn_topic_type: "References"
-learn_rel_path: "Developers/Web/Api/Queries"
--->
-
# CountIf
> This query is available as `countif`.
diff --git a/src/web/api/queries/des/README.md b/src/web/api/queries/des/README.md
index 6dc19e73..fb053de5 100644
--- a/src/web/api/queries/des/README.md
+++ b/src/web/api/queries/des/README.md
@@ -1,12 +1,3 @@
-<!--
-title: "double exponential smoothing"
-sidebar_label: "double exponential smoothing"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/src/web/api/queries/des/README.md
-learn_status: "Published"
-learn_topic_type: "References"
-learn_rel_path: "Developers/Web/Api/Queries"
--->
-
# double exponential smoothing
Exponential smoothing is one of many window functions commonly applied to smooth data in signal
diff --git a/src/web/api/queries/incremental_sum/README.md b/src/web/api/queries/incremental_sum/README.md
index 6f02abe7..5e246289 100644
--- a/src/web/api/queries/incremental_sum/README.md
+++ b/src/web/api/queries/incremental_sum/README.md
@@ -1,12 +1,3 @@
-<!--
-title: "Incremental Sum (`incremental_sum`)"
-sidebar_label: "Incremental Sum (`incremental_sum`)"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/src/web/api/queries/incremental_sum/README.md
-learn_status: "Published"
-learn_topic_type: "References"
-learn_rel_path: "Developers/Web/Api/Queries"
--->
-
# Incremental Sum (`incremental_sum`)
This modules finds the incremental sum of a period, which `last value - first value`.
diff --git a/src/web/api/queries/max/README.md b/src/web/api/queries/max/README.md
index ae634e05..6a24a0a5 100644
--- a/src/web/api/queries/max/README.md
+++ b/src/web/api/queries/max/README.md
@@ -1,12 +1,3 @@
-<!--
-title: "Max"
-sidebar_label: "Max"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/src/web/api/queries/max/README.md
-learn_status: "Published"
-learn_topic_type: "References"
-learn_rel_path: "Developers/Web/Api/Queries"
--->
-
# Max
This module finds the max value in the time-frame given.
diff --git a/src/web/api/queries/median/README.md b/src/web/api/queries/median/README.md
index e6f6c04e..42a9afb1 100644
--- a/src/web/api/queries/median/README.md
+++ b/src/web/api/queries/median/README.md
@@ -1,13 +1,3 @@
-<!--
-title: "Median"
-sidebar_label: "Median"
-description: "Use median in API queries and health entities to find the 'middle' value from a sample, eliminating any unwanted spikes in the returned metrics."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/src/web/api/queries/median/README.md
-learn_status: "Published"
-learn_topic_type: "References"
-learn_rel_path: "Developers/Web/Api/Queries"
--->
-
# Median
The median is the value separating the higher half from the lower half of a data sample
diff --git a/src/web/api/queries/min/README.md b/src/web/api/queries/min/README.md
index 35acb8c9..f2a35d62 100644
--- a/src/web/api/queries/min/README.md
+++ b/src/web/api/queries/min/README.md
@@ -1,12 +1,3 @@
-<!--
-title: "Min"
-sidebar_label: "Min"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/src/web/api/queries/min/README.md
-learn_status: "Published"
-learn_topic_type: "References"
-learn_rel_path: "Developers/Web/Api/Queries"
--->
-
# Min
This module finds the min value in the time-frame given.
diff --git a/src/web/api/queries/percentile/README.md b/src/web/api/queries/percentile/README.md
index 88abf8d5..0f9a2f39 100644
--- a/src/web/api/queries/percentile/README.md
+++ b/src/web/api/queries/percentile/README.md
@@ -1,13 +1,3 @@
-<!--
-title: "Percentile"
-sidebar_label: "Percentile"
-description: "Use percentile in API queries and health entities to find the 'percentile' value from a sample, eliminating any unwanted spikes in the returned metrics."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/src/web/api/queries/percentile/README.md
-learn_status: "Published"
-learn_topic_type: "References"
-learn_rel_path: "Developers/Web/Api/Queries"
--->
-
# Percentile
The percentile is the average value of a series using only the smaller N percentile of the values.
diff --git a/src/web/api/queries/rrdr.h b/src/web/api/queries/rrdr.h
index d36d3f5b..860a375c 100644
--- a/src/web/api/queries/rrdr.h
+++ b/src/web/api/queries/rrdr.h
@@ -17,62 +17,6 @@ typedef enum tier_query_fetch {
TIER_QUERY_FETCH_AVERAGE
} TIER_QUERY_FETCH;
-typedef enum rrdr_options {
- RRDR_OPTION_NONZERO = (1 << 0), // don't output dimensions with just zero values
- RRDR_OPTION_REVERSED = (1 << 1), // output the rows in reverse order (oldest to newest)
- RRDR_OPTION_ABSOLUTE = (1 << 2), // values positive, for DATASOURCE_SSV before summing
- RRDR_OPTION_DIMS_MIN2MAX = (1 << 3), // when adding dimensions, use max - min, instead of sum
- RRDR_OPTION_DIMS_AVERAGE = (1 << 4), // when adding dimensions, use average, instead of sum
- RRDR_OPTION_DIMS_MIN = (1 << 5), // when adding dimensions, use minimum, instead of sum
- RRDR_OPTION_DIMS_MAX = (1 << 6), // when adding dimensions, use maximum, instead of sum
- RRDR_OPTION_SECONDS = (1 << 7), // output seconds, instead of dates
- RRDR_OPTION_MILLISECONDS = (1 << 8), // output milliseconds, instead of dates
- RRDR_OPTION_NULL2ZERO = (1 << 9), // do not show nulls, convert them to zeros
- RRDR_OPTION_OBJECTSROWS = (1 << 10), // each row of values should be an object, not an array
- RRDR_OPTION_GOOGLE_JSON = (1 << 11), // comply with google JSON/JSONP specs
- RRDR_OPTION_JSON_WRAP = (1 << 12), // wrap the response in a JSON header with info about the result
- RRDR_OPTION_LABEL_QUOTES = (1 << 13), // in CSV output, wrap header labels in double quotes
- RRDR_OPTION_PERCENTAGE = (1 << 14), // give values as percentage of total
- RRDR_OPTION_NOT_ALIGNED = (1 << 15), // do not align charts for persistent timeframes
- RRDR_OPTION_DISPLAY_ABS = (1 << 16), // for badges, display the absolute value, but calculate colors with sign
- RRDR_OPTION_MATCH_IDS = (1 << 17), // when filtering dimensions, match only IDs
- RRDR_OPTION_MATCH_NAMES = (1 << 18), // when filtering dimensions, match only names
- RRDR_OPTION_NATURAL_POINTS = (1 << 19), // return the natural points of the database
- RRDR_OPTION_VIRTUAL_POINTS = (1 << 20), // return virtual points
- RRDR_OPTION_ANOMALY_BIT = (1 << 21), // Return the anomaly bit stored in each collected_number
- RRDR_OPTION_RETURN_RAW = (1 << 22), // Return raw data for aggregating across multiple nodes
- RRDR_OPTION_RETURN_JWAR = (1 << 23), // Return anomaly rates in jsonwrap
- RRDR_OPTION_SELECTED_TIER = (1 << 24), // Use the selected tier for the query
- RRDR_OPTION_ALL_DIMENSIONS = (1 << 25), // Return the full dimensions list
- RRDR_OPTION_SHOW_DETAILS = (1 << 26), // v2 returns detailed object tree
- RRDR_OPTION_DEBUG = (1 << 27), // v2 returns request description
- RRDR_OPTION_MINIFY = (1 << 28), // remove JSON spaces and newlines from JSON output
- RRDR_OPTION_GROUP_BY_LABELS = (1 << 29), // v2 returns flattened labels per dimension of the chart
-
- // internal ones - not to be exposed to the API
- RRDR_OPTION_INTERNAL_AR = (1 << 31), // internal use only, to let the formatters know we want to render the anomaly rate
-} RRDR_OPTIONS;
-
-typedef enum context_v2_options {
- CONTEXT_V2_OPTION_MINIFY = (1 << 0), // remove JSON spaces and newlines from JSON output
- CONTEXT_V2_OPTION_DEBUG = (1 << 1), // show the request
- CONTEXT_V2_OPTION_ALERTS_WITH_CONFIGURATIONS = (1 << 2), // include alert configurations (used by /api/v2/alert_transitions)
- CONTEXT_V2_OPTION_ALERTS_WITH_INSTANCES = (1 << 3), // include alert instances (used by /api/v2/alerts)
- CONTEXT_V2_OPTION_ALERTS_WITH_VALUES = (1 << 4), // include alert latest values (used by /api/v2/alerts)
- CONTEXT_V2_OPTION_ALERTS_WITH_SUMMARY = (1 << 5), // include alerts summary counters (used by /api/v2/alerts)
-} CONTEXTS_V2_OPTIONS;
-
-typedef enum context_v2_alert_status {
- CONTEXT_V2_ALERT_UNINITIALIZED = (1 << 5), // include UNINITIALIZED alerts
- CONTEXT_V2_ALERT_UNDEFINED = (1 << 6), // include UNDEFINED alerts
- CONTEXT_V2_ALERT_CLEAR = (1 << 7), // include CLEAR alerts
- CONTEXT_V2_ALERT_RAISED = (1 << 8), // include WARNING & CRITICAL alerts
- CONTEXT_V2_ALERT_WARNING = (1 << 9), // include WARNING alerts
- CONTEXT_V2_ALERT_CRITICAL = (1 << 10), // include CRITICAL alerts
-} CONTEXTS_V2_ALERT_STATUS;
-
-#define CONTEXTS_V2_ALERT_STATUSES (CONTEXT_V2_ALERT_UNINITIALIZED|CONTEXT_V2_ALERT_UNDEFINED|CONTEXT_V2_ALERT_CLEAR|CONTEXT_V2_ALERT_RAISED|CONTEXT_V2_ALERT_WARNING|CONTEXT_V2_ALERT_CRITICAL)
-
typedef enum __attribute__ ((__packed__)) rrdr_value_flag {
// IMPORTANT:
diff --git a/src/web/api/queries/ses/README.md b/src/web/api/queries/ses/README.md
index e2fd65d7..58afdbe0 100644
--- a/src/web/api/queries/ses/README.md
+++ b/src/web/api/queries/ses/README.md
@@ -1,12 +1,3 @@
-<!--
-title: "Single (or Simple) Exponential Smoothing (`ses`)"
-sidebar_label: "Single (or Simple) Exponential Smoothing (`ses`)"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/src/web/api/queries/ses/README.md
-learn_status: "Published"
-learn_topic_type: "References"
-learn_rel_path: "Developers/Web/Api/Queries"
--->
-
# Single (or Simple) Exponential Smoothing (`ses`)
> This query is also available as `ema` and `ewma`.
diff --git a/src/web/api/queries/stddev/README.md b/src/web/api/queries/stddev/README.md
index 76cfee1f..62cda84f 100644
--- a/src/web/api/queries/stddev/README.md
+++ b/src/web/api/queries/stddev/README.md
@@ -1,12 +1,3 @@
-<!--
-title: "standard deviation (`stddev`)"
-sidebar_label: "standard deviation (`stddev`)"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/src/web/api/queries/stddev/README.md
-learn_status: "Published"
-learn_topic_type: "References"
-learn_rel_path: "Developers/Web/Api/Queries"
--->
-
# standard deviation (`stddev`)
The standard deviation is a measure that is used to quantify the amount of variation or dispersion
diff --git a/src/web/api/queries/sum/README.md b/src/web/api/queries/sum/README.md
index dd29b9c5..1bb71a62 100644
--- a/src/web/api/queries/sum/README.md
+++ b/src/web/api/queries/sum/README.md
@@ -1,12 +1,3 @@
-<!--
-title: "Sum"
-sidebar_label: "Sum"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/src/web/api/queries/sum/README.md
-learn_status: "Published"
-learn_topic_type: "References"
-learn_rel_path: "Developers/Web/Api/Queries"
--->
-
# Sum
This module sums all the values in the time-frame requested.
diff --git a/src/web/api/queries/trimmed_mean/README.md b/src/web/api/queries/trimmed_mean/README.md
index 96902329..66d86a53 100644
--- a/src/web/api/queries/trimmed_mean/README.md
+++ b/src/web/api/queries/trimmed_mean/README.md
@@ -1,13 +1,3 @@
-<!--
-title: "Trimmed Mean"
-sidebar_label: "Trimmed Mean"
-description: "Use trimmed-mean in API queries and health entities to find the average value from a sample, eliminating any unwanted spikes in the returned metrics."
-custom_edit_url: https://github.com/netdata/netdata/edit/master/src/web/api/queries/trimmed_mean/README.md
-learn_status: "Published"
-learn_topic_type: "References"
-learn_rel_path: "Developers/Web/Api/Queries"
--->
-
# Trimmed Mean
The trimmed mean is the average value of a series excluding the smallest and biggest points.
diff --git a/src/web/api/queries/weights.c b/src/web/api/queries/weights.c
index 44928fea..e34774f3 100644
--- a/src/web/api/queries/weights.c
+++ b/src/web/api/queries/weights.c
@@ -4,9 +4,7 @@
#include "database/KolmogorovSmirnovDist.h"
#define MAX_POINTS 10000
-int enable_metric_correlations = CONFIG_BOOLEAN_YES;
int metric_correlations_version = 1;
-WEIGHTS_METHOD default_metric_correlations_method = WEIGHTS_METHOD_MC_KS2;
typedef struct weights_stats {
NETDATA_DOUBLE max_base_high_ratio;
@@ -36,7 +34,7 @@ WEIGHTS_METHOD weights_string_to_method(const char *method) {
if(strcmp(method, weights_methods[i].name) == 0)
return weights_methods[i].value;
- return default_metric_correlations_method;
+ return WEIGHTS_METHOD_MC_KS2;
}
const char *weights_method_to_string(WEIGHTS_METHOD method) {
@@ -44,7 +42,7 @@ const char *weights_method_to_string(WEIGHTS_METHOD method) {
if(weights_methods[i].value == method)
return weights_methods[i].name;
- return "unknown";
+ return "ks2";
}
// ----------------------------------------------------------------------------
@@ -978,6 +976,12 @@ static size_t registered_results_to_json_multinode_group_by(
BUFFER *key = buffer_create(0, NULL);
BUFFER *name = buffer_create(0, NULL);
dfe_start_read(results, t) {
+ char node_uuid[UUID_STR_LEN];
+
+ if(UUIDiszero(t->host->node_id))
+ uuid_unparse_lower(t->host->host_id.uuid, node_uuid);
+ else
+ uuid_unparse_lower(t->host->node_id.uuid, node_uuid);
buffer_flush(key);
buffer_flush(name);
@@ -998,7 +1002,7 @@ static size_t registered_results_to_json_multinode_group_by(
if(!(qwd->qwr->group_by.group_by & RRDR_GROUP_BY_NODE)) {
buffer_fast_strcat(key, "@", 1);
buffer_fast_strcat(name, "@", 1);
- buffer_strcat(key, t->host->machine_guid);
+ buffer_strcat(key, node_uuid);
buffer_strcat(name, rrdhost_hostname(t->host));
}
}
@@ -1008,7 +1012,7 @@ static size_t registered_results_to_json_multinode_group_by(
buffer_fast_strcat(name, ",", 1);
}
- buffer_strcat(key, t->host->machine_guid);
+ buffer_strcat(key, node_uuid);
buffer_strcat(name, rrdhost_hostname(t->host));
}
if(qwd->qwr->group_by.group_by & RRDR_GROUP_BY_CONTEXT) {
diff --git a/src/web/api/queries/weights.h b/src/web/api/queries/weights.h
index be7e5a8b..6d2bf8e0 100644
--- a/src/web/api/queries/weights.h
+++ b/src/web/api/queries/weights.h
@@ -18,9 +18,7 @@ typedef enum {
WEIGHTS_FORMAT_MULTINODE = 3,
} WEIGHTS_FORMAT;
-extern int enable_metric_correlations;
extern int metric_correlations_version;
-extern WEIGHTS_METHOD default_metric_correlations_method;
typedef bool (*weights_interrupt_callback_t)(void *data);
diff --git a/src/web/api/v1/api_v1_aclk.c b/src/web/api/v1/api_v1_aclk.c
new file mode 100644
index 00000000..b9878db2
--- /dev/null
+++ b/src/web/api/v1/api_v1_aclk.c
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "api_v1_calls.h"
+
+int api_v1_aclk(RRDHOST *host, struct web_client *w, char *url) {
+ UNUSED(url);
+ UNUSED(host);
+ if (!netdata_ready) return HTTP_RESP_SERVICE_UNAVAILABLE;
+
+ BUFFER *wb = w->response.data;
+ buffer_flush(wb);
+ char *str = aclk_state_json();
+ buffer_strcat(wb, str);
+ freez(str);
+
+ wb->content_type = CT_APPLICATION_JSON;
+ buffer_no_cacheable(wb);
+ return HTTP_RESP_OK;
+}
+
diff --git a/src/web/api/v1/api_v1_alarms.c b/src/web/api/v1/api_v1_alarms.c
new file mode 100644
index 00000000..4f3af74b
--- /dev/null
+++ b/src/web/api/v1/api_v1_alarms.c
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "api_v1_calls.h"
+
+static int web_client_api_request_v1_alarms_select(char *url) {
+ int all = 0;
+ while(url) {
+ char *value = strsep_skip_consecutive_separators(&url, "&");
+ if (!value || !*value) continue;
+
+ if(!strcmp(value, "all") || !strcmp(value, "all=true")) all = 1;
+ else if(!strcmp(value, "active") || !strcmp(value, "active=true")) all = 0;
+ }
+
+ return all;
+}
+
+int api_v1_alarms(RRDHOST *host, struct web_client *w, char *url) {
+ int all = web_client_api_request_v1_alarms_select(url);
+
+ buffer_flush(w->response.data);
+ w->response.data->content_type = CT_APPLICATION_JSON;
+ health_alarms2json(host, w->response.data, all);
+ buffer_no_cacheable(w->response.data);
+ return HTTP_RESP_OK;
+}
+
+int api_v1_alarms_values(RRDHOST *host, struct web_client *w, char *url) {
+ int all = web_client_api_request_v1_alarms_select(url);
+
+ buffer_flush(w->response.data);
+ w->response.data->content_type = CT_APPLICATION_JSON;
+ health_alarms_values2json(host, w->response.data, all);
+ buffer_no_cacheable(w->response.data);
+ return HTTP_RESP_OK;
+}
+
+int api_v1_alarm_count(RRDHOST *host, struct web_client *w, char *url) {
+ RRDCALC_STATUS status = RRDCALC_STATUS_RAISED;
+ BUFFER *contexts = NULL;
+
+ buffer_flush(w->response.data);
+ buffer_sprintf(w->response.data, "[");
+
+ while(url) {
+ char *value = strsep_skip_consecutive_separators(&url, "&");
+ if(!value || !*value) continue;
+
+ char *name = strsep_skip_consecutive_separators(&value, "=");
+ if(!name || !*name) continue;
+ if(!value || !*value) continue;
+
+ netdata_log_debug(D_WEB_CLIENT, "%llu: API v1 alarm_count query param '%s' with value '%s'", w->id, name, value);
+
+ char* p = value;
+ if(!strcmp(name, "status")) {
+ while ((*p = toupper(*p))) p++;
+ if (!strcmp("CRITICAL", value)) status = RRDCALC_STATUS_CRITICAL;
+ else if (!strcmp("WARNING", value)) status = RRDCALC_STATUS_WARNING;
+ else if (!strcmp("UNINITIALIZED", value)) status = RRDCALC_STATUS_UNINITIALIZED;
+ else if (!strcmp("UNDEFINED", value)) status = RRDCALC_STATUS_UNDEFINED;
+ else if (!strcmp("REMOVED", value)) status = RRDCALC_STATUS_REMOVED;
+ else if (!strcmp("CLEAR", value)) status = RRDCALC_STATUS_CLEAR;
+ }
+ else if(!strcmp(name, "context") || !strcmp(name, "ctx")) {
+ if(!contexts) contexts = buffer_create(255, &netdata_buffers_statistics.buffers_api);
+ buffer_strcat(contexts, "|");
+ buffer_strcat(contexts, value);
+ }
+ }
+
+ health_aggregate_alarms(host, w->response.data, contexts, status);
+
+ buffer_sprintf(w->response.data, "]\n");
+ w->response.data->content_type = CT_APPLICATION_JSON;
+ buffer_no_cacheable(w->response.data);
+
+ buffer_free(contexts);
+ return 200;
+}
+
+int api_v1_alarm_log(RRDHOST *host, struct web_client *w, char *url) {
+ time_t after = 0;
+ char *chart = NULL;
+
+ while(url) {
+ char *value = strsep_skip_consecutive_separators(&url, "&");
+ if (!value || !*value) continue;
+
+ char *name = strsep_skip_consecutive_separators(&value, "=");
+ if(!name || !*name) continue;
+ if(!value || !*value) continue;
+
+ if (!strcmp(name, "after")) after = (time_t) strtoul(value, NULL, 0);
+ else if (!strcmp(name, "chart")) chart = value;
+ }
+
+ buffer_flush(w->response.data);
+ w->response.data->content_type = CT_APPLICATION_JSON;
+ sql_health_alarm_log2json(host, w->response.data, after, chart);
+ return HTTP_RESP_OK;
+}
+
+int api_v1_variable(RRDHOST *host, struct web_client *w, char *url) {
+ int ret = HTTP_RESP_BAD_REQUEST;
+ char *chart = NULL;
+ char *variable = NULL;
+
+ buffer_flush(w->response.data);
+
+ while(url) {
+ char *value = strsep_skip_consecutive_separators(&url, "&");
+ if(!value || !*value) continue;
+
+ char *name = strsep_skip_consecutive_separators(&value, "=");
+ if(!name || !*name) continue;
+ if(!value || !*value) continue;
+
+ // name and value are now the parameters
+ // they are not null and not empty
+
+ if(!strcmp(name, "chart")) chart = value;
+ else if(!strcmp(name, "variable")) variable = value;
+ }
+
+ if(!chart || !*chart || !variable || !*variable) {
+ buffer_sprintf(w->response.data, "A chart= and a variable= are required.");
+ goto cleanup;
+ }
+
+ RRDSET *st = rrdset_find(host, chart);
+ if(!st) st = rrdset_find_byname(host, chart);
+ if(!st) {
+ buffer_strcat(w->response.data, "Chart is not found: ");
+ buffer_strcat_htmlescape(w->response.data, chart);
+ ret = HTTP_RESP_NOT_FOUND;
+ goto cleanup;
+ }
+
+ w->response.data->content_type = CT_APPLICATION_JSON;
+ st->last_accessed_time_s = now_realtime_sec();
+ alert_variable_lookup_trace(host, st, variable, w->response.data);
+
+ return HTTP_RESP_OK;
+
+cleanup:
+ return ret;
+}
+
+int api_v1_alarm_variables(RRDHOST *host, struct web_client *w, char *url) {
+ return api_v1_single_chart_helper(host, w, url, health_api_v1_chart_variables2json);
+}
+
diff --git a/src/web/api/exporters/shell/allmetrics_shell.c b/src/web/api/v1/api_v1_allmetrics.c
index c8248c14..593475ef 100644
--- a/src/web/api/exporters/shell/allmetrics_shell.c
+++ b/src/web/api/v1/api_v1_allmetrics.c
@@ -1,6 +1,29 @@
// SPDX-License-Identifier: GPL-3.0-or-later
-#include "allmetrics_shell.h"
+#include "api_v1_calls.h"
+
+#define ALLMETRICS_FORMAT_SHELL "shell"
+#define ALLMETRICS_FORMAT_PROMETHEUS "prometheus"
+#define ALLMETRICS_FORMAT_PROMETHEUS_ALL_HOSTS "prometheus_all_hosts"
+#define ALLMETRICS_FORMAT_JSON "json"
+
+#define ALLMETRICS_SHELL 1
+#define ALLMETRICS_PROMETHEUS 2
+#define ALLMETRICS_JSON 3
+#define ALLMETRICS_PROMETHEUS_ALL_HOSTS 4
+
+struct prometheus_output_options {
+ char *name;
+ PROMETHEUS_OUTPUT_OPTIONS flag;
+} prometheus_output_flags_root[] = {
+ { "names", PROMETHEUS_OUTPUT_NAMES },
+ { "timestamps", PROMETHEUS_OUTPUT_TIMESTAMPS },
+ { "variables", PROMETHEUS_OUTPUT_VARIABLES },
+ { "oldunits", PROMETHEUS_OUTPUT_OLDUNITS },
+ { "hideunits", PROMETHEUS_OUTPUT_HIDEUNITS },
+ // terminator
+ { NULL, PROMETHEUS_OUTPUT_NONE },
+};
// ----------------------------------------------------------------------------
// BASH
@@ -168,3 +191,118 @@ void rrd_stats_api_v1_charts_allmetrics_json(RRDHOST *host, const char *filter_s
simple_pattern_free(filter);
}
+int api_v1_allmetrics(RRDHOST *host, struct web_client *w, char *url) {
+ int format = ALLMETRICS_SHELL;
+ const char *filter = NULL;
+ const char *prometheus_server = w->client_ip;
+
+ uint32_t prometheus_exporting_options;
+ if (prometheus_exporter_instance)
+ prometheus_exporting_options = prometheus_exporter_instance->config.options;
+ else
+ prometheus_exporting_options = global_exporting_options;
+
+ PROMETHEUS_OUTPUT_OPTIONS prometheus_output_options =
+ PROMETHEUS_OUTPUT_TIMESTAMPS |
+ ((prometheus_exporting_options & EXPORTING_OPTION_SEND_NAMES) ? PROMETHEUS_OUTPUT_NAMES : 0);
+
+ const char *prometheus_prefix;
+ if (prometheus_exporter_instance)
+ prometheus_prefix = prometheus_exporter_instance->config.prefix;
+ else
+ prometheus_prefix = global_exporting_prefix;
+
+ while(url) {
+ char *value = strsep_skip_consecutive_separators(&url, "&");
+ if (!value || !*value) continue;
+
+ char *name = strsep_skip_consecutive_separators(&value, "=");
+ if(!name || !*name) continue;
+ if(!value || !*value) continue;
+
+ if(!strcmp(name, "format")) {
+ if(!strcmp(value, ALLMETRICS_FORMAT_SHELL))
+ format = ALLMETRICS_SHELL;
+ else if(!strcmp(value, ALLMETRICS_FORMAT_PROMETHEUS))
+ format = ALLMETRICS_PROMETHEUS;
+ else if(!strcmp(value, ALLMETRICS_FORMAT_PROMETHEUS_ALL_HOSTS))
+ format = ALLMETRICS_PROMETHEUS_ALL_HOSTS;
+ else if(!strcmp(value, ALLMETRICS_FORMAT_JSON))
+ format = ALLMETRICS_JSON;
+ else
+ format = 0;
+ }
+ else if(!strcmp(name, "filter")) {
+ filter = value;
+ }
+ else if(!strcmp(name, "server")) {
+ prometheus_server = value;
+ }
+ else if(!strcmp(name, "prefix")) {
+ prometheus_prefix = value;
+ }
+ else if(!strcmp(name, "data") || !strcmp(name, "source") || !strcmp(name, "data source") || !strcmp(name, "data-source") || !strcmp(name, "data_source") || !strcmp(name, "datasource")) {
+ prometheus_exporting_options = exporting_parse_data_source(value, prometheus_exporting_options);
+ }
+ else {
+ int i;
+ for(i = 0; prometheus_output_flags_root[i].name ; i++) {
+ if(!strcmp(name, prometheus_output_flags_root[i].name)) {
+ if(!strcmp(value, "yes") || !strcmp(value, "1") || !strcmp(value, "true"))
+ prometheus_output_options |= prometheus_output_flags_root[i].flag;
+ else {
+ prometheus_output_options &= ~prometheus_output_flags_root[i].flag;
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ buffer_flush(w->response.data);
+ buffer_no_cacheable(w->response.data);
+
+ switch(format) {
+ case ALLMETRICS_JSON:
+ w->response.data->content_type = CT_APPLICATION_JSON;
+ rrd_stats_api_v1_charts_allmetrics_json(host, filter, w->response.data);
+ return HTTP_RESP_OK;
+
+ case ALLMETRICS_SHELL:
+ w->response.data->content_type = CT_TEXT_PLAIN;
+ rrd_stats_api_v1_charts_allmetrics_shell(host, filter, w->response.data);
+ return HTTP_RESP_OK;
+
+ case ALLMETRICS_PROMETHEUS:
+ w->response.data->content_type = CT_PROMETHEUS;
+ rrd_stats_api_v1_charts_allmetrics_prometheus_single_host(
+ host
+ , filter
+ , w->response.data
+ , prometheus_server
+ , prometheus_prefix
+ , prometheus_exporting_options
+ , prometheus_output_options
+ );
+ return HTTP_RESP_OK;
+
+ case ALLMETRICS_PROMETHEUS_ALL_HOSTS:
+ w->response.data->content_type = CT_PROMETHEUS;
+ rrd_stats_api_v1_charts_allmetrics_prometheus_all_hosts(
+ host
+ , filter
+ , w->response.data
+ , prometheus_server
+ , prometheus_prefix
+ , prometheus_exporting_options
+ , prometheus_output_options
+ );
+ return HTTP_RESP_OK;
+
+ default:
+ w->response.data->content_type = CT_TEXT_PLAIN;
+ buffer_strcat(w->response.data, "Which format? '" ALLMETRICS_FORMAT_SHELL "', '" ALLMETRICS_FORMAT_PROMETHEUS "', '" ALLMETRICS_FORMAT_PROMETHEUS_ALL_HOSTS "' and '" ALLMETRICS_FORMAT_JSON "' are currently supported.");
+ return HTTP_RESP_BAD_REQUEST;
+ }
+}
diff --git a/src/web/api/badges/README.md b/src/web/api/v1/api_v1_badge/README.md
index 1388fe0c..d6deb799 100644
--- a/src/web/api/badges/README.md
+++ b/src/web/api/v1/api_v1_badge/README.md
@@ -1,12 +1,3 @@
-<!--
-title: "Netdata badges"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/src/web/api/badges/README.md
-sidebar_label: "Netdata badges"
-learn_status: "Published"
-learn_topic_type: "References"
-learn_rel_path: "Developers/Web/Api"
--->
-
# Netdata badges
**Badges are cool!**
diff --git a/src/web/api/badges/web_buffer_svg.c b/src/web/api/v1/api_v1_badge/web_buffer_svg.c
index 747c46d5..642261fd 100644
--- a/src/web/api/badges/web_buffer_svg.c
+++ b/src/web/api/v1/api_v1_badge/web_buffer_svg.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
-#include "web_buffer_svg.h"
+#include "libnetdata/libnetdata.h"
+#include "../../../server/web_client.h"
#define BADGE_HORIZONTAL_PADDING 4
#define VERDANA_KERNING 0.2
@@ -360,7 +361,7 @@ static struct units_formatter {
{ NULL, 0, UNITS_FORMAT_NONE }
};
-inline char *format_value_and_unit(char *value_string, size_t value_string_len,
+char *format_value_and_unit(char *value_string, size_t value_string_len,
NETDATA_DOUBLE value, const char *units, int precision) {
static int max = -1;
int i;
@@ -734,7 +735,7 @@ static const char *parse_color_argument(const char *arg, const char *def)
return color_map(arg, def);
}
-void buffer_svg(BUFFER *wb, const char *label,
+static void buffer_svg(BUFFER *wb, const char *label,
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) {
char value_color_buffer[COLOR_STRING_SIZE + 1]
, value_string[VALUE_STRING_SIZE + 1]
@@ -864,7 +865,7 @@ void buffer_svg(BUFFER *wb, const char *label,
#define BADGE_URL_ARG_LBL_COLOR "text_color_lbl"
#define BADGE_URL_ARG_VAL_COLOR "text_color_val"
-int web_client_api_request_v1_badge(RRDHOST *host, struct web_client *w, char *url) {
+int api_v1_badge(RRDHOST *host, struct web_client *w, char *url) {
int ret = HTTP_RESP_BAD_REQUEST;
buffer_flush(w->response.data);
diff --git a/src/web/api/v1/api_v1_calls.h b/src/web/api/v1/api_v1_calls.h
new file mode 100644
index 00000000..36a0605c
--- /dev/null
+++ b/src/web/api/v1/api_v1_calls.h
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_API_V1_CALLS_H
+#define NETDATA_API_V1_CALLS_H
+
+#include "../web_api_v1.h"
+
+int api_v1_info(RRDHOST *host, struct web_client *w, char *url);
+
+int api_v1_config(RRDHOST *host, struct web_client *w, char *url);
+
+int api_v1_registry(RRDHOST *host, struct web_client *w, char *url);
+
+int api_v1_manage(RRDHOST *host, struct web_client *w, char *url);
+
+int api_v1_data(RRDHOST *host, struct web_client *w, char *url);
+int api_v1_chart(RRDHOST *host, struct web_client *w, char *url);
+int api_v1_charts(RRDHOST *host, struct web_client *w, char *url);
+
+int api_v1_context(RRDHOST *host, struct web_client *w, char *url);
+int api_v1_contexts(RRDHOST *host, struct web_client *w, char *url);
+
+int api_v1_alarms(RRDHOST *host, struct web_client *w, char *url);
+int api_v1_alarms_values(RRDHOST *host, struct web_client *w, char *url);
+int api_v1_alarm_count(RRDHOST *host, struct web_client *w, char *url);
+int api_v1_alarm_log(RRDHOST *host, struct web_client *w, char *url);
+int api_v1_variable(RRDHOST *host, struct web_client *w, char *url);
+int api_v1_alarm_variables(RRDHOST *host, struct web_client *w, char *url);
+
+int api_v1_dbengine_stats(RRDHOST *host, struct web_client *w, char *url);
+int api_v1_ml_info(RRDHOST *host, struct web_client *w, char *url);
+int api_v1_aclk(RRDHOST *host, struct web_client *w, char *url);
+
+int api_v1_functions(RRDHOST *host, struct web_client *w, char *url);
+int api_v1_function(RRDHOST *host, struct web_client *w, char *url);
+
+int api_v1_metric_correlations(RRDHOST *host, struct web_client *w, char *url);
+int api_v1_weights(RRDHOST *host, struct web_client *w, char *url);
+
+int api_v1_badge(RRDHOST *host, struct web_client *w, char *url);
+int api_v1_allmetrics(RRDHOST *host, struct web_client *w, char *url);
+
+// common library calls
+int api_v1_single_chart_helper(RRDHOST *host, struct web_client *w, char *url, void callback(RRDSET *st, BUFFER *buf));
+void api_v1_management_init(void);
+
+#endif //NETDATA_API_V1_CALLS_H
diff --git a/src/web/api/v1/api_v1_charts.c b/src/web/api/v1/api_v1_charts.c
new file mode 100644
index 00000000..afc67af6
--- /dev/null
+++ b/src/web/api/v1/api_v1_charts.c
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "api_v1_calls.h"
+
+int api_v1_single_chart_helper(RRDHOST *host, struct web_client *w, char *url, void callback(RRDSET *st, BUFFER *buf)) {
+ int ret = HTTP_RESP_BAD_REQUEST;
+ char *chart = NULL;
+
+ buffer_flush(w->response.data);
+
+ while(url) {
+ char *value = strsep_skip_consecutive_separators(&url, "&");
+ if(!value || !*value) continue;
+
+ char *name = strsep_skip_consecutive_separators(&value, "=");
+ if(!name || !*name) continue;
+ if(!value || !*value) continue;
+
+ // name and value are now the parameters
+ // they are not null and not empty
+
+ if(!strcmp(name, "chart")) chart = value;
+ //else {
+ /// buffer_sprintf(w->response.data, "Unknown parameter '%s' in request.", name);
+ // goto cleanup;
+ //}
+ }
+
+ if(!chart || !*chart) {
+ buffer_sprintf(w->response.data, "No chart id is given at the request.");
+ goto cleanup;
+ }
+
+ RRDSET *st = rrdset_find(host, chart);
+ if(!st) st = rrdset_find_byname(host, chart);
+ if(!st) {
+ buffer_strcat(w->response.data, "Chart is not found: ");
+ buffer_strcat_htmlescape(w->response.data, chart);
+ ret = HTTP_RESP_NOT_FOUND;
+ goto cleanup;
+ }
+
+ w->response.data->content_type = CT_APPLICATION_JSON;
+ st->last_accessed_time_s = now_realtime_sec();
+ callback(st, w->response.data);
+ return HTTP_RESP_OK;
+
+cleanup:
+ return ret;
+}
+
+int api_v1_charts(RRDHOST *host, struct web_client *w, char *url) {
+ (void)url;
+
+ buffer_flush(w->response.data);
+ w->response.data->content_type = CT_APPLICATION_JSON;
+ charts2json(host, w->response.data);
+ return HTTP_RESP_OK;
+}
+
+int api_v1_chart(RRDHOST *host, struct web_client *w, char *url) {
+ return api_v1_single_chart_helper(host, w, url, rrd_stats_api_v1_chart);
+}
+
diff --git a/src/web/api/v1/api_v1_config.c b/src/web/api/v1/api_v1_config.c
new file mode 100644
index 00000000..69bcde76
--- /dev/null
+++ b/src/web/api/v1/api_v1_config.c
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "web/api/v2/api_v2_calls.h"
+
+int api_v1_config(RRDHOST *host, struct web_client *w, char *url __maybe_unused) {
+ char *action = "tree";
+ char *path = "/";
+ char *id = NULL;
+ char *add_name = NULL;
+ int timeout = 120;
+
+ while(url) {
+ char *value = strsep_skip_consecutive_separators(&url, "&");
+ if(!value || !*value) continue;
+
+ char *name = strsep_skip_consecutive_separators(&value, "=");
+ if(!name || !*name) continue;
+ if(!value || !*value) continue;
+
+ // name and value are now the parameters
+ // they are not null and not empty
+
+ if(!strcmp(name, "action"))
+ action = value;
+ else if(!strcmp(name, "path"))
+ path = value;
+ else if(!strcmp(name, "id"))
+ id = value;
+ else if(!strcmp(name, "name"))
+ add_name = value;
+ else if(!strcmp(name, "timeout")) {
+ timeout = (int)strtol(value, NULL, 10);
+ if(timeout < 10)
+ timeout = 10;
+ }
+ }
+
+ char transaction[UUID_COMPACT_STR_LEN];
+ uuid_unparse_lower_compact(w->transaction, transaction);
+
+ size_t len = strlen(action) + (id ? strlen(id) : 0) + strlen(path) + (add_name ? strlen(add_name) : 0) + 100;
+
+ char cmd[len];
+ if(strcmp(action, "tree") == 0)
+ snprintfz(cmd, sizeof(cmd), PLUGINSD_FUNCTION_CONFIG " tree '%s' '%s'", path, id?id:"");
+ else {
+ DYNCFG_CMDS c = dyncfg_cmds2id(action);
+ if(!id || !*id || !dyncfg_is_valid_id(id)) {
+ rrd_call_function_error(w->response.data, "Invalid id", HTTP_RESP_BAD_REQUEST);
+ return HTTP_RESP_BAD_REQUEST;
+ }
+
+ if(c == DYNCFG_CMD_NONE) {
+ rrd_call_function_error(w->response.data, "Invalid action", HTTP_RESP_BAD_REQUEST);
+ return HTTP_RESP_BAD_REQUEST;
+ }
+
+ if(c == DYNCFG_CMD_ADD || c == DYNCFG_CMD_USERCONFIG || c == DYNCFG_CMD_TEST) {
+ if(c == DYNCFG_CMD_TEST && (!add_name || !*add_name)) {
+ // backwards compatibility for TEST without a name
+ char *colon = strrchr(id, ':');
+ if(colon) {
+ *colon = '\0';
+ add_name = ++colon;
+ }
+ else
+ add_name = "test";
+ }
+
+ if(!add_name || !*add_name || !dyncfg_is_valid_id(add_name)) {
+ rrd_call_function_error(w->response.data, "Invalid name", HTTP_RESP_BAD_REQUEST);
+ return HTTP_RESP_BAD_REQUEST;
+ }
+ snprintfz(cmd, sizeof(cmd), PLUGINSD_FUNCTION_CONFIG " %s %s %s", id, dyncfg_id2cmd_one(c), add_name);
+ }
+ else
+ snprintfz(cmd, sizeof(cmd), PLUGINSD_FUNCTION_CONFIG " %s %s", id, dyncfg_id2cmd_one(c));
+ }
+
+ CLEAN_BUFFER *source = buffer_create(100, NULL);
+ web_client_api_request_vX_source_to_buffer(w, source);
+
+ buffer_flush(w->response.data);
+ int code = rrd_function_run(host, w->response.data, timeout, w->access, cmd,
+ true, transaction,
+ NULL, NULL,
+ web_client_progress_functions_update, w,
+ web_client_interrupt_callback, w,
+ w->payload, buffer_tostring(source), false);
+
+ return code;
+}
diff --git a/src/web/api/v1/api_v1_context.c b/src/web/api/v1/api_v1_context.c
new file mode 100644
index 00000000..5b7baf80
--- /dev/null
+++ b/src/web/api/v1/api_v1_context.c
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "api_v1_calls.h"
+
+int api_v1_context(RRDHOST *host, struct web_client *w, char *url) {
+ char *context = NULL;
+ RRDCONTEXT_TO_JSON_OPTIONS options = RRDCONTEXT_OPTION_NONE;
+ time_t after = 0, before = 0;
+ const char *chart_label_key = NULL, *chart_labels_filter = NULL;
+ BUFFER *dimensions = NULL;
+
+ buffer_flush(w->response.data);
+
+ while(url) {
+ char *value = strsep_skip_consecutive_separators(&url, "&");
+ if(!value || !*value) continue;
+
+ char *name = strsep_skip_consecutive_separators(&value, "=");
+ if(!name || !*name) continue;
+ if(!value || !*value) continue;
+
+ // name and value are now the parameters
+ // they are not null and not empty
+
+ if(!strcmp(name, "context") || !strcmp(name, "ctx")) context = value;
+ else if(!strcmp(name, "after")) after = str2l(value);
+ else if(!strcmp(name, "before")) before = str2l(value);
+ else if(!strcmp(name, "options")) options = rrdcontext_to_json_parse_options(value);
+ else if(!strcmp(name, "chart_label_key")) chart_label_key = value;
+ else if(!strcmp(name, "chart_labels_filter")) chart_labels_filter = value;
+ else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) {
+ if(!dimensions) dimensions = buffer_create(100, &netdata_buffers_statistics.buffers_api);
+ buffer_strcat(dimensions, "|");
+ buffer_strcat(dimensions, value);
+ }
+ }
+
+ if(!context || !*context) {
+ buffer_sprintf(w->response.data, "No context is given at the request.");
+ return HTTP_RESP_BAD_REQUEST;
+ }
+
+ SIMPLE_PATTERN *chart_label_key_pattern = NULL;
+ SIMPLE_PATTERN *chart_labels_filter_pattern = NULL;
+ SIMPLE_PATTERN *chart_dimensions_pattern = NULL;
+
+ if(chart_label_key)
+ chart_label_key_pattern = simple_pattern_create(chart_label_key, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT, true);
+
+ if(chart_labels_filter)
+ chart_labels_filter_pattern = simple_pattern_create(chart_labels_filter, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT,
+ true);
+
+ if(dimensions) {
+ chart_dimensions_pattern = simple_pattern_create(buffer_tostring(dimensions), ",|\t\r\n\f\v",
+ SIMPLE_PATTERN_EXACT, true);
+ buffer_free(dimensions);
+ }
+
+ w->response.data->content_type = CT_APPLICATION_JSON;
+ int ret = rrdcontext_to_json(host, w->response.data, after, before, options, context, chart_label_key_pattern, chart_labels_filter_pattern, chart_dimensions_pattern);
+
+ simple_pattern_free(chart_label_key_pattern);
+ simple_pattern_free(chart_labels_filter_pattern);
+ simple_pattern_free(chart_dimensions_pattern);
+
+ return ret;
+}
diff --git a/src/web/api/v1/api_v1_contexts.c b/src/web/api/v1/api_v1_contexts.c
new file mode 100644
index 00000000..90d376d4
--- /dev/null
+++ b/src/web/api/v1/api_v1_contexts.c
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "api_v1_calls.h"
+
+int api_v1_contexts(RRDHOST *host, struct web_client *w, char *url) {
+ RRDCONTEXT_TO_JSON_OPTIONS options = RRDCONTEXT_OPTION_NONE;
+ time_t after = 0, before = 0;
+ const char *chart_label_key = NULL, *chart_labels_filter = NULL;
+ BUFFER *dimensions = NULL;
+
+ buffer_flush(w->response.data);
+
+ while(url) {
+ char *value = strsep_skip_consecutive_separators(&url, "&");
+ if(!value || !*value) continue;
+
+ char *name = strsep_skip_consecutive_separators(&value, "=");
+ if(!name || !*name) continue;
+ if(!value || !*value) continue;
+
+ // name and value are now the parameters
+ // they are not null and not empty
+
+ if(!strcmp(name, "after")) after = str2l(value);
+ else if(!strcmp(name, "before")) before = str2l(value);
+ else if(!strcmp(name, "options")) options = rrdcontext_to_json_parse_options(value);
+ else if(!strcmp(name, "chart_label_key")) chart_label_key = value;
+ else if(!strcmp(name, "chart_labels_filter")) chart_labels_filter = value;
+ else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) {
+ if(!dimensions) dimensions = buffer_create(100, &netdata_buffers_statistics.buffers_api);
+ buffer_strcat(dimensions, "|");
+ buffer_strcat(dimensions, value);
+ }
+ }
+
+ SIMPLE_PATTERN *chart_label_key_pattern = NULL;
+ SIMPLE_PATTERN *chart_labels_filter_pattern = NULL;
+ SIMPLE_PATTERN *chart_dimensions_pattern = NULL;
+
+ if(chart_label_key)
+ chart_label_key_pattern = simple_pattern_create(chart_label_key, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT, true);
+
+ if(chart_labels_filter)
+ chart_labels_filter_pattern = simple_pattern_create(chart_labels_filter, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT,
+ true);
+
+ if(dimensions) {
+ chart_dimensions_pattern = simple_pattern_create(buffer_tostring(dimensions), ",|\t\r\n\f\v",
+ SIMPLE_PATTERN_EXACT, true);
+ buffer_free(dimensions);
+ }
+
+ w->response.data->content_type = CT_APPLICATION_JSON;
+ int ret = rrdcontexts_to_json(host, w->response.data, after, before, options, chart_label_key_pattern, chart_labels_filter_pattern, chart_dimensions_pattern);
+
+ simple_pattern_free(chart_label_key_pattern);
+ simple_pattern_free(chart_labels_filter_pattern);
+ simple_pattern_free(chart_dimensions_pattern);
+
+ return ret;
+}
diff --git a/src/web/api/v1/api_v1_data.c b/src/web/api/v1/api_v1_data.c
new file mode 100644
index 00000000..30328ed3
--- /dev/null
+++ b/src/web/api/v1/api_v1_data.c
@@ -0,0 +1,246 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "api_v1_calls.h"
+
+int api_v1_data(RRDHOST *host, struct web_client *w, char *url) {
+ netdata_log_debug(D_WEB_CLIENT, "%llu: API v1 data with URL '%s'", w->id, url);
+
+ int ret = HTTP_RESP_BAD_REQUEST;
+ BUFFER *dimensions = NULL;
+
+ buffer_flush(w->response.data);
+
+ char *google_version = "0.6",
+ *google_reqId = "0",
+ *google_sig = "0",
+ *google_out = "json",
+ *responseHandler = NULL,
+ *outFileName = NULL;
+
+ time_t last_timestamp_in_data = 0, google_timestamp = 0;
+
+ char *chart = NULL;
+ char *before_str = NULL;
+ char *after_str = NULL;
+ char *group_time_str = NULL;
+ char *points_str = NULL;
+ char *timeout_str = NULL;
+ char *context = NULL;
+ char *chart_label_key = NULL;
+ char *chart_labels_filter = NULL;
+ char *group_options = NULL;
+ size_t tier = 0;
+ RRDR_TIME_GROUPING group = RRDR_GROUPING_AVERAGE;
+ DATASOURCE_FORMAT format = DATASOURCE_JSON;
+ RRDR_OPTIONS options = 0;
+
+ while(url) {
+ char *value = strsep_skip_consecutive_separators(&url, "&");
+ if(!value || !*value) continue;
+
+ char *name = strsep_skip_consecutive_separators(&value, "=");
+ if(!name || !*name) continue;
+ if(!value || !*value) continue;
+
+ netdata_log_debug(D_WEB_CLIENT, "%llu: API v1 data query param '%s' with value '%s'", w->id, name, value);
+
+ // name and value are now the parameters
+ // they are not null and not empty
+
+ if(!strcmp(name, "context")) context = value;
+ else if(!strcmp(name, "chart_label_key")) chart_label_key = value;
+ else if(!strcmp(name, "chart_labels_filter")) chart_labels_filter = value;
+ else if(!strcmp(name, "chart")) chart = value;
+ else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) {
+ if(!dimensions) dimensions = buffer_create(100, &netdata_buffers_statistics.buffers_api);
+ buffer_strcat(dimensions, "|");
+ buffer_strcat(dimensions, value);
+ }
+ else if(!strcmp(name, "show_dimensions")) options |= RRDR_OPTION_ALL_DIMENSIONS;
+ else if(!strcmp(name, "after")) after_str = value;
+ else if(!strcmp(name, "before")) before_str = value;
+ else if(!strcmp(name, "points")) points_str = value;
+ else if(!strcmp(name, "timeout")) timeout_str = value;
+ else if(!strcmp(name, "gtime")) group_time_str = value;
+ else if(!strcmp(name, "group_options")) group_options = value;
+ else if(!strcmp(name, "group")) {
+ group = time_grouping_parse(value, RRDR_GROUPING_AVERAGE);
+ }
+ else if(!strcmp(name, "format")) {
+ format = datasource_format_str_to_id(value);
+ }
+ else if(!strcmp(name, "options")) {
+ options |= rrdr_options_parse(value);
+ }
+ else if(!strcmp(name, "callback")) {
+ responseHandler = value;
+ }
+ else if(!strcmp(name, "filename")) {
+ outFileName = value;
+ }
+ else if(!strcmp(name, "tqx")) {
+ // parse Google Visualization API options
+ // https://developers.google.com/chart/interactive/docs/dev/implementing_data_source
+ char *tqx_name, *tqx_value;
+
+ while(value) {
+ tqx_value = strsep_skip_consecutive_separators(&value, ";");
+ if(!tqx_value || !*tqx_value) continue;
+
+ tqx_name = strsep_skip_consecutive_separators(&tqx_value, ":");
+ if(!tqx_name || !*tqx_name) continue;
+ if(!tqx_value || !*tqx_value) continue;
+
+ if(!strcmp(tqx_name, "version"))
+ google_version = tqx_value;
+ else if(!strcmp(tqx_name, "reqId"))
+ google_reqId = tqx_value;
+ else if(!strcmp(tqx_name, "sig")) {
+ google_sig = tqx_value;
+ google_timestamp = strtoul(google_sig, NULL, 0);
+ }
+ else if(!strcmp(tqx_name, "out")) {
+ google_out = tqx_value;
+ format = google_data_format_str_to_id(google_out);
+ }
+ else if(!strcmp(tqx_name, "responseHandler"))
+ responseHandler = tqx_value;
+ else if(!strcmp(tqx_name, "outFileName"))
+ outFileName = tqx_value;
+ }
+ }
+ else if(!strcmp(name, "tier")) {
+ tier = str2ul(value);
+ if(tier < storage_tiers)
+ options |= RRDR_OPTION_SELECTED_TIER;
+ else
+ tier = 0;
+ }
+ }
+
+ // validate the google parameters given
+ fix_google_param(google_out);
+ fix_google_param(google_sig);
+ fix_google_param(google_reqId);
+ fix_google_param(google_version);
+ fix_google_param(responseHandler);
+ fix_google_param(outFileName);
+
+ RRDSET *st = NULL;
+ ONEWAYALLOC *owa = onewayalloc_create(0);
+ QUERY_TARGET *qt = NULL;
+
+ if(!is_valid_sp(chart) && !is_valid_sp(context)) {
+ buffer_sprintf(w->response.data, "No chart or context is given.");
+ goto cleanup;
+ }
+
+ if(chart && !context) {
+ // check if this is a specific chart
+ st = rrdset_find(host, chart);
+ if (!st) st = rrdset_find_byname(host, chart);
+ }
+
+ long long before = (before_str && *before_str)?str2l(before_str):0;
+ long long after = (after_str && *after_str) ?str2l(after_str):-600;
+ int points = (points_str && *points_str)?str2i(points_str):0;
+ int timeout = (timeout_str && *timeout_str)?str2i(timeout_str): 0;
+ long group_time = (group_time_str && *group_time_str)?str2l(group_time_str):0;
+
+ QUERY_TARGET_REQUEST qtr = {
+ .version = 1,
+ .after = after,
+ .before = before,
+ .host = host,
+ .st = st,
+ .nodes = NULL,
+ .contexts = context,
+ .instances = chart,
+ .dimensions = (dimensions)?buffer_tostring(dimensions):NULL,
+ .timeout_ms = timeout,
+ .points = points,
+ .format = format,
+ .options = options,
+ .time_group_method = group,
+ .time_group_options = group_options,
+ .resampling_time = group_time,
+ .tier = tier,
+ .chart_label_key = chart_label_key,
+ .labels = chart_labels_filter,
+ .query_source = QUERY_SOURCE_API_DATA,
+ .priority = STORAGE_PRIORITY_NORMAL,
+ .interrupt_callback = web_client_interrupt_callback,
+ .interrupt_callback_data = w,
+ .transaction = &w->transaction,
+ };
+ qt = query_target_create(&qtr);
+
+ if(!qt || !qt->query.used) {
+ buffer_sprintf(w->response.data, "No metrics where matched to query.");
+ ret = HTTP_RESP_NOT_FOUND;
+ goto cleanup;
+ }
+
+ web_client_timeout_checkpoint_set(w, timeout);
+ if(web_client_timeout_checkpoint_and_check(w, NULL)) {
+ ret = w->response.code;
+ goto cleanup;
+ }
+
+ if(outFileName && *outFileName) {
+ buffer_sprintf(w->response.header, "Content-Disposition: attachment; filename=\"%s\"\r\n", outFileName);
+ netdata_log_debug(D_WEB_CLIENT, "%llu: generating outfilename header: '%s'", w->id, outFileName);
+ }
+
+ if(format == DATASOURCE_DATATABLE_JSONP) {
+ if(responseHandler == NULL)
+ responseHandler = "google.visualization.Query.setResponse";
+
+ netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: GOOGLE JSON/JSONP: version = '%s', reqId = '%s', sig = '%s', out = '%s', responseHandler = '%s', outFileName = '%s'",
+ w->id, google_version, google_reqId, google_sig, google_out, responseHandler, outFileName
+ );
+
+ buffer_sprintf(
+ w->response.data,
+ "%s({version:'%s',reqId:'%s',status:'ok',sig:'%"PRId64"',table:",
+ responseHandler,
+ google_version,
+ google_reqId,
+ (int64_t)(st ? st->last_updated.tv_sec : 0));
+ }
+ else if(format == DATASOURCE_JSONP) {
+ if(responseHandler == NULL)
+ responseHandler = "callback";
+
+ buffer_strcat(w->response.data, responseHandler);
+ buffer_strcat(w->response.data, "(");
+ }
+
+ ret = data_query_execute(owa, w->response.data, qt, &last_timestamp_in_data);
+
+ if(format == DATASOURCE_DATATABLE_JSONP) {
+ if(google_timestamp < last_timestamp_in_data)
+ buffer_strcat(w->response.data, "});");
+
+ else {
+ // the client already has the latest data
+ buffer_flush(w->response.data);
+ buffer_sprintf(w->response.data,
+ "%s({version:'%s',reqId:'%s',status:'error',errors:[{reason:'not_modified',message:'Data not modified'}]});",
+ responseHandler, google_version, google_reqId);
+ }
+ }
+ else if(format == DATASOURCE_JSONP)
+ buffer_strcat(w->response.data, ");");
+
+ if(qt->internal.relative)
+ buffer_no_cacheable(w->response.data);
+ else
+ buffer_cacheable(w->response.data);
+
+cleanup:
+ query_target_release(qt);
+ onewayalloc_destroy(owa);
+ buffer_free(dimensions);
+ return ret;
+}
diff --git a/src/web/api/v1/api_v1_dbengine.c b/src/web/api/v1/api_v1_dbengine.c
new file mode 100644
index 00000000..89855f88
--- /dev/null
+++ b/src/web/api/v1/api_v1_dbengine.c
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "api_v1_calls.h"
+
+#ifndef ENABLE_DBENGINE
+int web_client_api_request_v1_dbengine_stats(RRDHOST *host __maybe_unused, struct web_client *w __maybe_unused, char *url __maybe_unused) {
+ return HTTP_RESP_NOT_FOUND;
+}
+#else
+static void web_client_api_v1_dbengine_stats_for_tier(BUFFER *wb, size_t tier) {
+ RRDENG_SIZE_STATS stats = rrdeng_size_statistics(multidb_ctx[tier]);
+
+ buffer_sprintf(wb,
+ "\n\t\t\"default_granularity_secs\":%zu"
+ ",\n\t\t\"sizeof_datafile\":%zu"
+ ",\n\t\t\"sizeof_page_in_cache\":%zu"
+ ",\n\t\t\"sizeof_point_data\":%zu"
+ ",\n\t\t\"sizeof_page_data\":%zu"
+ ",\n\t\t\"pages_per_extent\":%zu"
+ ",\n\t\t\"datafiles\":%zu"
+ ",\n\t\t\"extents\":%zu"
+ ",\n\t\t\"extents_pages\":%zu"
+ ",\n\t\t\"points\":%zu"
+ ",\n\t\t\"metrics\":%zu"
+ ",\n\t\t\"metrics_pages\":%zu"
+ ",\n\t\t\"extents_compressed_bytes\":%zu"
+ ",\n\t\t\"pages_uncompressed_bytes\":%zu"
+ ",\n\t\t\"pages_duration_secs\":%lld"
+ ",\n\t\t\"single_point_pages\":%zu"
+ ",\n\t\t\"first_t\":%ld"
+ ",\n\t\t\"last_t\":%ld"
+ ",\n\t\t\"database_retention_secs\":%lld"
+ ",\n\t\t\"average_compression_savings\":%0.2f"
+ ",\n\t\t\"average_point_duration_secs\":%0.2f"
+ ",\n\t\t\"average_metric_retention_secs\":%0.2f"
+ ",\n\t\t\"ephemeral_metrics_per_day_percent\":%0.2f"
+ ",\n\t\t\"average_page_size_bytes\":%0.2f"
+ ",\n\t\t\"estimated_concurrently_collected_metrics\":%zu"
+ ",\n\t\t\"currently_collected_metrics\":%zu"
+ ",\n\t\t\"disk_space\":%zu"
+ ",\n\t\t\"max_disk_space\":%zu"
+ , stats.default_granularity_secs
+ , stats.sizeof_datafile
+ , stats.sizeof_page_in_cache
+ , stats.sizeof_point_data
+ , stats.sizeof_page_data
+ , stats.pages_per_extent
+ , stats.datafiles
+ , stats.extents
+ , stats.extents_pages
+ , stats.points
+ , stats.metrics
+ , stats.metrics_pages
+ , stats.extents_compressed_bytes
+ , stats.pages_uncompressed_bytes
+ , (long long)stats.pages_duration_secs
+ , stats.single_point_pages
+ , stats.first_time_s
+ , stats.last_time_s
+ , (long long)stats.database_retention_secs
+ , stats.average_compression_savings
+ , stats.average_point_duration_secs
+ , stats.average_metric_retention_secs
+ , stats.ephemeral_metrics_per_day_percent
+ , stats.average_page_size_bytes
+ , stats.estimated_concurrently_collected_metrics
+ , stats.currently_collected_metrics
+ , stats.disk_space
+ , stats.max_disk_space
+ );
+}
+
+int api_v1_dbengine_stats(RRDHOST *host __maybe_unused, struct web_client *w, char *url __maybe_unused) {
+ if (!netdata_ready)
+ return HTTP_RESP_SERVICE_UNAVAILABLE;
+
+ BUFFER *wb = w->response.data;
+ buffer_flush(wb);
+
+ if(!dbengine_enabled) {
+ buffer_strcat(wb, "dbengine is not enabled");
+ return HTTP_RESP_NOT_FOUND;
+ }
+
+ wb->content_type = CT_APPLICATION_JSON;
+ buffer_no_cacheable(wb);
+ buffer_strcat(wb, "{");
+ for(size_t tier = 0; tier < storage_tiers ;tier++) {
+ buffer_sprintf(wb, "%s\n\t\"tier%zu\": {", tier?",":"", tier);
+ web_client_api_v1_dbengine_stats_for_tier(wb, tier);
+ buffer_strcat(wb, "\n\t}");
+ }
+ buffer_strcat(wb, "\n}");
+
+ return HTTP_RESP_OK;
+}
+#endif
diff --git a/src/web/api/v1/api_v1_function.c b/src/web/api/v1/api_v1_function.c
new file mode 100644
index 00000000..761164fd
--- /dev/null
+++ b/src/web/api/v1/api_v1_function.c
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "api_v1_calls.h"
+
+int api_v1_function(RRDHOST *host, struct web_client *w, char *url) {
+ if (!netdata_ready)
+ return HTTP_RESP_SERVICE_UNAVAILABLE;
+
+ int timeout = 0;
+ const char *function = NULL;
+
+ while (url) {
+ char *value = strsep_skip_consecutive_separators(&url, "&");
+ if (!value || !*value)
+ continue;
+
+ char *name = strsep_skip_consecutive_separators(&value, "=");
+ if (!name || !*name)
+ continue;
+
+ if (!strcmp(name, "function"))
+ function = value;
+
+ else if (!strcmp(name, "timeout"))
+ timeout = (int) strtoul(value, NULL, 0);
+ }
+
+ BUFFER *wb = w->response.data;
+ buffer_flush(wb);
+ wb->content_type = CT_APPLICATION_JSON;
+ buffer_no_cacheable(wb);
+
+ char transaction[UUID_COMPACT_STR_LEN];
+ uuid_unparse_lower_compact(w->transaction, transaction);
+
+ CLEAN_BUFFER *source = buffer_create(100, NULL);
+ web_client_api_request_vX_source_to_buffer(w, source);
+
+ return rrd_function_run(host, wb, timeout, w->access, function, true, transaction,
+ NULL, NULL,
+ web_client_progress_functions_update, w,
+ web_client_interrupt_callback, w, w->payload,
+ buffer_tostring(source), false);
+}
diff --git a/src/web/api/v1/api_v1_functions.c b/src/web/api/v1/api_v1_functions.c
new file mode 100644
index 00000000..bc1c7df8
--- /dev/null
+++ b/src/web/api/v1/api_v1_functions.c
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "api_v1_calls.h"
+
+int api_v1_functions(RRDHOST *host, struct web_client *w, char *url __maybe_unused) {
+ if (!netdata_ready)
+ return HTTP_RESP_SERVICE_UNAVAILABLE;
+
+ BUFFER *wb = w->response.data;
+ buffer_flush(wb);
+ wb->content_type = CT_APPLICATION_JSON;
+ buffer_no_cacheable(wb);
+
+ buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
+ host_functions2json(host, wb);
+ buffer_json_finalize(wb);
+
+ return HTTP_RESP_OK;
+}
diff --git a/src/web/api/v1/api_v1_info.c b/src/web/api/v1/api_v1_info.c
new file mode 100644
index 00000000..2395cea5
--- /dev/null
+++ b/src/web/api/v1/api_v1_info.c
@@ -0,0 +1,207 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "api_v1_calls.h"
+
+static void host_collectors(RRDHOST *host, BUFFER *wb) {
+ buffer_json_member_add_array(wb, "collectors");
+
+ DICTIONARY *dict = dictionary_create(DICT_OPTION_SINGLE_THREADED|DICT_OPTION_DONT_OVERWRITE_VALUE);
+ RRDSET *st;
+ char name[500];
+
+ time_t now = now_realtime_sec();
+
+ rrdset_foreach_read(st, host) {
+ if (!rrdset_is_available_for_viewers(st))
+ continue;
+
+ sprintf(name, "%s:%s", rrdset_plugin_name(st), rrdset_module_name(st));
+
+ bool old = 0;
+ bool *set = dictionary_set(dict, name, &old, sizeof(bool));
+ if(!*set) {
+ *set = true;
+ st->last_accessed_time_s = now;
+ buffer_json_add_array_item_object(wb);
+ buffer_json_member_add_string(wb, "plugin", rrdset_plugin_name(st));
+ buffer_json_member_add_string(wb, "module", rrdset_module_name(st));
+ buffer_json_object_close(wb);
+ }
+ }
+ rrdset_foreach_done(st);
+ dictionary_destroy(dict);
+
+ buffer_json_array_close(wb);
+}
+
+static inline void web_client_api_request_v1_info_mirrored_hosts_status(BUFFER *wb, RRDHOST *host) {
+ buffer_json_add_array_item_object(wb);
+
+ buffer_json_member_add_string(wb, "hostname", rrdhost_hostname(host));
+ buffer_json_member_add_uint64(wb, "hops", host->system_info ? host->system_info->hops : (host == localhost) ? 0 : 1);
+ buffer_json_member_add_boolean(wb, "reachable", (host == localhost || !rrdhost_flag_check(host, RRDHOST_FLAG_ORPHAN)));
+
+ buffer_json_member_add_string(wb, "guid", host->machine_guid);
+ buffer_json_member_add_uuid(wb, "node_id", host->node_id.uuid);
+ CLAIM_ID claim_id = rrdhost_claim_id_get(host);
+ buffer_json_member_add_string(wb, "claim_id", claim_id_is_set(claim_id) ? claim_id.str : NULL);
+
+ buffer_json_object_close(wb);
+}
+
+static inline void web_client_api_request_v1_info_mirrored_hosts(BUFFER *wb) {
+ RRDHOST *host;
+
+ rrd_rdlock();
+
+ buffer_json_member_add_array(wb, "mirrored_hosts");
+ rrdhost_foreach_read(host)
+ buffer_json_add_array_item_string(wb, rrdhost_hostname(host));
+ buffer_json_array_close(wb);
+
+ buffer_json_member_add_array(wb, "mirrored_hosts_status");
+ rrdhost_foreach_read(host) {
+ if ((host == localhost || !rrdhost_flag_check(host, RRDHOST_FLAG_ORPHAN))) {
+ web_client_api_request_v1_info_mirrored_hosts_status(wb, host);
+ }
+ }
+ rrdhost_foreach_read(host) {
+ if ((host != localhost && rrdhost_flag_check(host, RRDHOST_FLAG_ORPHAN))) {
+ web_client_api_request_v1_info_mirrored_hosts_status(wb, host);
+ }
+ }
+ buffer_json_array_close(wb);
+
+ rrd_rdunlock();
+}
+
+static void web_client_api_request_v1_info_summary_alarm_statuses(RRDHOST *host, BUFFER *wb, const char *key) {
+ buffer_json_member_add_object(wb, key);
+
+ size_t normal = 0, warning = 0, critical = 0;
+ RRDCALC *rc;
+ foreach_rrdcalc_in_rrdhost_read(host, rc) {
+ if(unlikely(!rc->rrdset || !rc->rrdset->last_collected_time.tv_sec))
+ continue;
+
+ switch(rc->status) {
+ case RRDCALC_STATUS_WARNING:
+ warning++;
+ break;
+ case RRDCALC_STATUS_CRITICAL:
+ critical++;
+ break;
+ default:
+ normal++;
+ }
+ }
+ foreach_rrdcalc_in_rrdhost_done(rc);
+
+ buffer_json_member_add_uint64(wb, "normal", normal);
+ buffer_json_member_add_uint64(wb, "warning", warning);
+ buffer_json_member_add_uint64(wb, "critical", critical);
+
+ buffer_json_object_close(wb);
+}
+
+static int web_client_api_request_v1_info_fill_buffer(RRDHOST *host, BUFFER *wb) {
+ buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
+
+ buffer_json_member_add_string(wb, "version", rrdhost_program_version(host));
+ buffer_json_member_add_string(wb, "uid", host->machine_guid);
+
+ buffer_json_member_add_uint64(wb, "hosts-available", rrdhost_hosts_available());
+ web_client_api_request_v1_info_mirrored_hosts(wb);
+
+ web_client_api_request_v1_info_summary_alarm_statuses(host, wb, "alarms");
+
+ buffer_json_member_add_string_or_empty(wb, "os_name", host->system_info->host_os_name);
+ buffer_json_member_add_string_or_empty(wb, "os_id", host->system_info->host_os_id);
+ buffer_json_member_add_string_or_empty(wb, "os_id_like", host->system_info->host_os_id_like);
+ buffer_json_member_add_string_or_empty(wb, "os_version", host->system_info->host_os_version);
+ buffer_json_member_add_string_or_empty(wb, "os_version_id", host->system_info->host_os_version_id);
+ buffer_json_member_add_string_or_empty(wb, "os_detection", host->system_info->host_os_detection);
+ buffer_json_member_add_string_or_empty(wb, "cores_total", host->system_info->host_cores);
+ buffer_json_member_add_string_or_empty(wb, "total_disk_space", host->system_info->host_disk_space);
+ buffer_json_member_add_string_or_empty(wb, "cpu_freq", host->system_info->host_cpu_freq);
+ buffer_json_member_add_string_or_empty(wb, "ram_total", host->system_info->host_ram_total);
+
+ buffer_json_member_add_string_or_omit(wb, "container_os_name", host->system_info->container_os_name);
+ buffer_json_member_add_string_or_omit(wb, "container_os_id", host->system_info->container_os_id);
+ buffer_json_member_add_string_or_omit(wb, "container_os_id_like", host->system_info->container_os_id_like);
+ buffer_json_member_add_string_or_omit(wb, "container_os_version", host->system_info->container_os_version);
+ buffer_json_member_add_string_or_omit(wb, "container_os_version_id", host->system_info->container_os_version_id);
+ buffer_json_member_add_string_or_omit(wb, "container_os_detection", host->system_info->container_os_detection);
+ buffer_json_member_add_string_or_omit(wb, "is_k8s_node", host->system_info->is_k8s_node);
+
+ buffer_json_member_add_string_or_empty(wb, "kernel_name", host->system_info->kernel_name);
+ buffer_json_member_add_string_or_empty(wb, "kernel_version", host->system_info->kernel_version);
+ buffer_json_member_add_string_or_empty(wb, "architecture", host->system_info->architecture);
+ buffer_json_member_add_string_or_empty(wb, "virtualization", host->system_info->virtualization);
+ buffer_json_member_add_string_or_empty(wb, "virt_detection", host->system_info->virt_detection);
+ buffer_json_member_add_string_or_empty(wb, "container", host->system_info->container);
+ buffer_json_member_add_string_or_empty(wb, "container_detection", host->system_info->container_detection);
+
+ buffer_json_member_add_string_or_omit(wb, "cloud_provider_type", host->system_info->cloud_provider_type);
+ buffer_json_member_add_string_or_omit(wb, "cloud_instance_type", host->system_info->cloud_instance_type);
+ buffer_json_member_add_string_or_omit(wb, "cloud_instance_region", host->system_info->cloud_instance_region);
+
+ host_labels2json(host, wb, "host_labels");
+ host_functions2json(host, wb);
+ host_collectors(host, wb);
+
+ buffer_json_member_add_boolean(wb, "cloud-enabled", true);
+ buffer_json_member_add_boolean(wb, "cloud-available", true);
+ buffer_json_member_add_boolean(wb, "agent-claimed", is_agent_claimed());
+ buffer_json_member_add_boolean(wb, "aclk-available", aclk_online());
+
+ buffer_json_member_add_string(wb, "memory-mode", rrd_memory_mode_name(host->rrd_memory_mode));
+#ifdef ENABLE_DBENGINE
+ buffer_json_member_add_uint64(wb, "multidb-disk-quota", default_multidb_disk_quota_mb);
+ buffer_json_member_add_uint64(wb, "page-cache-size", default_rrdeng_page_cache_mb);
+#endif // ENABLE_DBENGINE
+ buffer_json_member_add_boolean(wb, "web-enabled", web_server_mode != WEB_SERVER_MODE_NONE);
+ buffer_json_member_add_boolean(wb, "stream-enabled", stream_conf_send_enabled);
+
+ buffer_json_member_add_boolean(wb, "stream-compression",
+ host->sender && host->sender->compressor.initialized);
+
+ buffer_json_member_add_boolean(wb, "https-enabled", true);
+
+ buffer_json_member_add_quoted_string(wb, "buildinfo", analytics_data.netdata_buildinfo);
+ buffer_json_member_add_quoted_string(wb, "release-channel", analytics_data.netdata_config_release_channel);
+ buffer_json_member_add_quoted_string(wb, "notification-methods", analytics_data.netdata_notification_methods);
+
+ buffer_json_member_add_boolean(wb, "exporting-enabled", analytics_data.exporting_enabled);
+ buffer_json_member_add_quoted_string(wb, "exporting-connectors", analytics_data.netdata_exporting_connectors);
+
+ buffer_json_member_add_uint64(wb, "allmetrics-prometheus-used", analytics_data.prometheus_hits);
+ buffer_json_member_add_uint64(wb, "allmetrics-shell-used", analytics_data.shell_hits);
+ buffer_json_member_add_uint64(wb, "allmetrics-json-used", analytics_data.json_hits);
+ buffer_json_member_add_uint64(wb, "dashboard-used", analytics_data.dashboard_hits);
+
+ buffer_json_member_add_uint64(wb, "charts-count", analytics_data.charts_count);
+ buffer_json_member_add_uint64(wb, "metrics-count", analytics_data.metrics_count);
+
+#if defined(ENABLE_ML)
+ buffer_json_member_add_object(wb, "ml-info");
+ ml_host_get_info(host, wb);
+ buffer_json_object_close(wb);
+#endif
+
+ buffer_json_finalize(wb);
+ return 0;
+}
+
+int api_v1_info(RRDHOST *host, struct web_client *w, char *url) {
+ (void)url;
+ if (!netdata_ready) return HTTP_RESP_SERVICE_UNAVAILABLE;
+ BUFFER *wb = w->response.data;
+ buffer_flush(wb);
+ wb->content_type = CT_APPLICATION_JSON;
+
+ web_client_api_request_v1_info_fill_buffer(host, wb);
+
+ buffer_no_cacheable(wb);
+ return HTTP_RESP_OK;
+}
diff --git a/src/web/api/v1/api_v1_manage.c b/src/web/api/v1/api_v1_manage.c
new file mode 100644
index 00000000..46611fbf
--- /dev/null
+++ b/src/web/api/v1/api_v1_manage.c
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "api_v1_calls.h"
+
+char *api_secret;
+
+static char *get_mgmt_api_key(void) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s/netdata.api.key", netdata_configured_varlib_dir);
+ const char *api_key_filename = config_get(CONFIG_SECTION_REGISTRY, "netdata management api key file", filename);
+ static char guid[GUID_LEN + 1] = "";
+
+ if(likely(guid[0]))
+ return guid;
+
+ // read it from disk
+ int fd = open(api_key_filename, O_RDONLY | O_CLOEXEC);
+ if(fd != -1) {
+ char buf[GUID_LEN + 1];
+ if(read(fd, buf, GUID_LEN) != GUID_LEN)
+ netdata_log_error("Failed to read management API key from '%s'", api_key_filename);
+ else {
+ buf[GUID_LEN] = '\0';
+ if(regenerate_guid(buf, guid) == -1) {
+ netdata_log_error("Failed to validate management API key '%s' from '%s'.",
+ buf, api_key_filename);
+
+ guid[0] = '\0';
+ }
+ }
+ close(fd);
+ }
+
+ // generate a new one?
+ if(!guid[0]) {
+ nd_uuid_t uuid;
+
+ uuid_generate_time(uuid);
+ uuid_unparse_lower(uuid, guid);
+ guid[GUID_LEN] = '\0';
+
+ // save it
+ fd = open(api_key_filename, O_WRONLY|O_CREAT|O_TRUNC | O_CLOEXEC, 444);
+ if(fd == -1) {
+ netdata_log_error("Cannot create unique management API key file '%s'. Please adjust config parameter 'netdata management api key file' to a proper path and file.", api_key_filename);
+ goto temp_key;
+ }
+
+ if(write(fd, guid, GUID_LEN) != GUID_LEN) {
+ netdata_log_error("Cannot write the unique management API key file '%s'. Please adjust config parameter 'netdata management api key file' to a proper path and file with enough space left.", api_key_filename);
+ close(fd);
+ goto temp_key;
+ }
+
+ close(fd);
+ }
+
+ return guid;
+
+temp_key:
+ netdata_log_info("You can still continue to use the alarm management API using the authorization token %s during this Netdata session only.", guid);
+ return guid;
+}
+
+void api_v1_management_init(void) {
+ api_secret = get_mgmt_api_key();
+}
+
+#define HLT_MGM "manage/health"
+int api_v1_manage(RRDHOST *host, struct web_client *w, char *url) {
+ const char *haystack = buffer_tostring(w->url_path_decoded);
+ char *needle;
+
+ buffer_flush(w->response.data);
+
+ if ((needle = strstr(haystack, HLT_MGM)) == NULL) {
+ buffer_strcat(w->response.data, "Invalid management request. Curently only 'health' is supported.");
+ return HTTP_RESP_NOT_FOUND;
+ }
+ needle += strlen(HLT_MGM);
+ if (*needle != '\0') {
+ buffer_strcat(w->response.data, "Invalid management request. Currently only 'health' is supported.");
+ return HTTP_RESP_NOT_FOUND;
+ }
+ return web_client_api_request_v1_mgmt_health(host, w, url);
+}
diff --git a/src/web/api/v1/api_v1_ml_info.c b/src/web/api/v1/api_v1_ml_info.c
new file mode 100644
index 00000000..121f9bf9
--- /dev/null
+++ b/src/web/api/v1/api_v1_ml_info.c
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "api_v1_calls.h"
+
+int api_v1_ml_info(RRDHOST *host, struct web_client *w, char *url) {
+ (void) url;
+#if defined(ENABLE_ML)
+
+ if (!netdata_ready)
+ return HTTP_RESP_SERVICE_UNAVAILABLE;
+
+ BUFFER *wb = w->response.data;
+ buffer_flush(wb);
+ wb->content_type = CT_APPLICATION_JSON;
+
+ buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
+ ml_host_get_detection_info(host, wb);
+ buffer_json_finalize(wb);
+
+ buffer_no_cacheable(wb);
+
+ return HTTP_RESP_OK;
+#else
+ UNUSED(host);
+ UNUSED(w);
+ return HTTP_RESP_SERVICE_UNAVAILABLE;
+#endif // ENABLE_ML
+}
diff --git a/src/web/api/v1/api_v1_registry.c b/src/web/api/v1/api_v1_registry.c
new file mode 100644
index 00000000..fa4ce4ca
--- /dev/null
+++ b/src/web/api/v1/api_v1_registry.c
@@ -0,0 +1,198 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "api_v1_calls.h"
+
+// Pings a netdata server:
+// /api/v1/registry?action=hello
+//
+// Access to a netdata registry:
+// /api/v1/registry?action=access&machine=${machine_guid}&name=${hostname}&url=${url}
+//
+// Delete from a netdata registry:
+// /api/v1/registry?action=delete&machine=${machine_guid}&name=${hostname}&url=${url}&delete_url=${delete_url}
+//
+// Search for the URLs of a machine:
+// /api/v1/registry?action=search&for=${machine_guid}
+//
+// Impersonate:
+// /api/v1/registry?action=switch&machine=${machine_guid}&name=${hostname}&url=${url}&to=${new_person_guid}
+int api_v1_registry(RRDHOST *host, struct web_client *w, char *url) {
+ static uint32_t hash_action = 0, hash_access = 0, hash_hello = 0, hash_delete = 0, hash_search = 0,
+ hash_switch = 0, hash_machine = 0, hash_url = 0, hash_name = 0, hash_delete_url = 0, hash_for = 0,
+ hash_to = 0 /*, hash_redirects = 0 */;
+
+ if(unlikely(!hash_action)) {
+ hash_action = simple_hash("action");
+ hash_access = simple_hash("access");
+ hash_hello = simple_hash("hello");
+ hash_delete = simple_hash("delete");
+ hash_search = simple_hash("search");
+ hash_switch = simple_hash("switch");
+ hash_machine = simple_hash("machine");
+ hash_url = simple_hash("url");
+ hash_name = simple_hash("name");
+ hash_delete_url = simple_hash("delete_url");
+ hash_for = simple_hash("for");
+ hash_to = simple_hash("to");
+ /*
+ hash_redirects = simple_hash("redirects");
+*/
+ }
+
+ netdata_log_debug(D_WEB_CLIENT, "%llu: API v1 registry with URL '%s'", w->id, url);
+
+ // TODO
+ // The browser may send multiple cookies with our id
+
+ char person_guid[UUID_STR_LEN] = "";
+ char *cookie = strstr(w->response.data->buffer, NETDATA_REGISTRY_COOKIE_NAME "=");
+ if(cookie)
+ strncpyz(person_guid, &cookie[sizeof(NETDATA_REGISTRY_COOKIE_NAME)], UUID_STR_LEN - 1);
+ else if(!extract_bearer_token_from_request(w, person_guid, sizeof(person_guid)))
+ person_guid[0] = '\0';
+
+ char action = '\0';
+ char *machine_guid = NULL,
+ *machine_url = NULL,
+ *url_name = NULL,
+ *search_machine_guid = NULL,
+ *delete_url = NULL,
+ *to_person_guid = NULL;
+ /*
+ int redirects = 0;
+*/
+
+ // Don't cache registry responses
+ buffer_no_cacheable(w->response.data);
+
+ while(url) {
+ char *value = strsep_skip_consecutive_separators(&url, "&");
+ if (!value || !*value) continue;
+
+ char *name = strsep_skip_consecutive_separators(&value, "=");
+ if (!name || !*name) continue;
+ if (!value || !*value) continue;
+
+ netdata_log_debug(D_WEB_CLIENT, "%llu: API v1 registry query param '%s' with value '%s'", w->id, name, value);
+
+ uint32_t hash = simple_hash(name);
+
+ if(hash == hash_action && !strcmp(name, "action")) {
+ uint32_t vhash = simple_hash(value);
+
+ if(vhash == hash_access && !strcmp(value, "access")) action = 'A';
+ else if(vhash == hash_hello && !strcmp(value, "hello")) action = 'H';
+ else if(vhash == hash_delete && !strcmp(value, "delete")) action = 'D';
+ else if(vhash == hash_search && !strcmp(value, "search")) action = 'S';
+ else if(vhash == hash_switch && !strcmp(value, "switch")) action = 'W';
+#ifdef NETDATA_INTERNAL_CHECKS
+ else netdata_log_error("unknown registry action '%s'", value);
+#endif /* NETDATA_INTERNAL_CHECKS */
+ }
+ /*
+ else if(hash == hash_redirects && !strcmp(name, "redirects"))
+ redirects = atoi(value);
+*/
+ else if(hash == hash_machine && !strcmp(name, "machine"))
+ machine_guid = value;
+
+ else if(hash == hash_url && !strcmp(name, "url"))
+ machine_url = value;
+
+ else if(action == 'A') {
+ if(hash == hash_name && !strcmp(name, "name"))
+ url_name = value;
+ }
+ else if(action == 'D') {
+ if(hash == hash_delete_url && !strcmp(name, "delete_url"))
+ delete_url = value;
+ }
+ else if(action == 'S') {
+ if(hash == hash_for && !strcmp(name, "for"))
+ search_machine_guid = value;
+ }
+ else if(action == 'W') {
+ if(hash == hash_to && !strcmp(name, "to"))
+ to_person_guid = value;
+ }
+#ifdef NETDATA_INTERNAL_CHECKS
+ else netdata_log_error("unused registry URL parameter '%s' with value '%s'", name, value);
+#endif /* NETDATA_INTERNAL_CHECKS */
+ }
+
+ bool do_not_track = respect_web_browser_do_not_track_policy && web_client_has_donottrack(w);
+
+ if(unlikely(action == 'H')) {
+ // HELLO request, dashboard ACL
+ analytics_log_dashboard();
+ if(unlikely(!http_can_access_dashboard(w)))
+ return web_client_permission_denied_acl(w);
+ }
+ else {
+ // everything else, registry ACL
+ if(unlikely(!http_can_access_registry(w)))
+ return web_client_permission_denied_acl(w);
+
+ if(unlikely(do_not_track)) {
+ buffer_flush(w->response.data);
+ buffer_sprintf(w->response.data, "Your web browser is sending 'DNT: 1' (Do Not Track). The registry requires persistent cookies on your browser to work.");
+ return HTTP_RESP_BAD_REQUEST;
+ }
+ }
+
+ buffer_no_cacheable(w->response.data);
+
+ switch(action) {
+ case 'A':
+ if(unlikely(!machine_guid || !machine_url || !url_name)) {
+ netdata_log_error("Invalid registry request - access requires these parameters: machine ('%s'), url ('%s'), name ('%s')", machine_guid ? machine_guid : "UNSET", machine_url ? machine_url : "UNSET", url_name ? url_name : "UNSET");
+ buffer_flush(w->response.data);
+ buffer_strcat(w->response.data, "Invalid registry Access request.");
+ return HTTP_RESP_BAD_REQUEST;
+ }
+
+ web_client_enable_tracking_required(w);
+ return registry_request_access_json(host, w, person_guid, machine_guid, machine_url, url_name, now_realtime_sec());
+
+ case 'D':
+ if(unlikely(!machine_guid || !machine_url || !delete_url)) {
+ netdata_log_error("Invalid registry request - delete requires these parameters: machine ('%s'), url ('%s'), delete_url ('%s')", machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", delete_url?delete_url:"UNSET");
+ buffer_flush(w->response.data);
+ buffer_strcat(w->response.data, "Invalid registry Delete request.");
+ return HTTP_RESP_BAD_REQUEST;
+ }
+
+ web_client_enable_tracking_required(w);
+ return registry_request_delete_json(host, w, person_guid, machine_guid, machine_url, delete_url, now_realtime_sec());
+
+ case 'S':
+ if(unlikely(!search_machine_guid)) {
+ netdata_log_error("Invalid registry request - search requires these parameters: for ('%s')", search_machine_guid?search_machine_guid:"UNSET");
+ buffer_flush(w->response.data);
+ buffer_strcat(w->response.data, "Invalid registry Search request.");
+ return HTTP_RESP_BAD_REQUEST;
+ }
+
+ web_client_enable_tracking_required(w);
+ return registry_request_search_json(host, w, person_guid, search_machine_guid);
+
+ case 'W':
+ if(unlikely(!machine_guid || !machine_url || !to_person_guid)) {
+ netdata_log_error("Invalid registry request - switching identity requires these parameters: machine ('%s'), url ('%s'), to ('%s')", machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", to_person_guid?to_person_guid:"UNSET");
+ buffer_flush(w->response.data);
+ buffer_strcat(w->response.data, "Invalid registry Switch request.");
+ return HTTP_RESP_BAD_REQUEST;
+ }
+
+ web_client_enable_tracking_required(w);
+ return registry_request_switch_json(host, w, person_guid, machine_guid, machine_url, to_person_guid, now_realtime_sec());
+
+ case 'H':
+ return registry_request_hello_json(host, w, do_not_track);
+
+ default:
+ buffer_flush(w->response.data);
+ buffer_strcat(w->response.data, "Invalid registry request - you need to set an action: hello, access, delete, search");
+ return HTTP_RESP_BAD_REQUEST;
+ }
+}
diff --git a/src/web/api/v1/api_v1_weights.c b/src/web/api/v1/api_v1_weights.c
new file mode 100644
index 00000000..e39fceae
--- /dev/null
+++ b/src/web/api/v1/api_v1_weights.c
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "api_v1_calls.h"
+
+int api_v1_metric_correlations(RRDHOST *host, struct web_client *w, char *url) {
+ return web_client_api_request_weights(host, w, url, WEIGHTS_METHOD_MC_KS2, WEIGHTS_FORMAT_CHARTS, 1);
+}
+
+int api_v1_weights(RRDHOST *host, struct web_client *w, char *url) {
+ return web_client_api_request_weights(host, w, url, WEIGHTS_METHOD_ANOMALY_RATE, WEIGHTS_FORMAT_CONTEXTS, 1);
+}
diff --git a/src/web/api/v2/api_v2_alert_config.c b/src/web/api/v2/api_v2_alert_config.c
new file mode 100644
index 00000000..b4f5344e
--- /dev/null
+++ b/src/web/api/v2/api_v2_alert_config.c
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "api_v2_calls.h"
+
+int api_v2_alert_config(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
+ const char *config = NULL;
+
+ while(url) {
+ char *value = strsep_skip_consecutive_separators(&url, "&");
+ if(!value || !*value) continue;
+
+ char *name = strsep_skip_consecutive_separators(&value, "=");
+ if(!name || !*name) continue;
+ if(!value || !*value) continue;
+
+ // name and value are now the parameters
+ // they are not null and not empty
+
+ if(!strcmp(name, "config"))
+ config = value;
+ }
+
+ buffer_flush(w->response.data);
+
+ if(!config) {
+ w->response.data->content_type = CT_TEXT_PLAIN;
+ buffer_strcat(w->response.data, "A config hash ID is required. Add ?config=UUID query param");
+ return HTTP_RESP_BAD_REQUEST;
+ }
+
+ return contexts_v2_alert_config_to_json(w, config);
+}
diff --git a/src/web/api/v2/api_v2_alert_transitions.c b/src/web/api/v2/api_v2_alert_transitions.c
new file mode 100644
index 00000000..e84b8018
--- /dev/null
+++ b/src/web/api/v2/api_v2_alert_transitions.c
@@ -0,0 +1,7 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "api_v2_calls.h"
+
+int api_v2_alert_transitions(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
+ return api_v2_contexts_internal(host, w, url, CONTEXTS_V2_ALERT_TRANSITIONS | CONTEXTS_V2_NODES);
+}
diff --git a/src/web/api/v2/api_v2_alerts.c b/src/web/api/v2/api_v2_alerts.c
new file mode 100644
index 00000000..c5d1922e
--- /dev/null
+++ b/src/web/api/v2/api_v2_alerts.c
@@ -0,0 +1,7 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "api_v2_calls.h"
+
+int api_v2_alerts(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
+ return api_v2_contexts_internal(host, w, url, CONTEXTS_V2_ALERTS | CONTEXTS_V2_NODES);
+}
diff --git a/src/web/api/v2/api_v2_bearer.c b/src/web/api/v2/api_v2_bearer.c
new file mode 100644
index 00000000..312ca4e4
--- /dev/null
+++ b/src/web/api/v2/api_v2_bearer.c
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "api_v2_calls.h"
+
+static bool verify_host_uuids(RRDHOST *host, const char *machine_guid, const char *node_id) {
+ if(!machine_guid || !node_id)
+ return false;
+
+ if(strcmp(machine_guid, host->machine_guid) != 0)
+ return false;
+
+ if(UUIDiszero(host->node_id))
+ return false;
+
+ char buf[UUID_STR_LEN];
+ uuid_unparse_lower(host->node_id.uuid, buf);
+
+ return strcmp(node_id, buf) == 0;
+}
+
+int api_v2_bearer_protection(RRDHOST *host __maybe_unused, struct web_client *w __maybe_unused, char *url) {
+ char *machine_guid = NULL;
+ char *claim_id = NULL;
+ char *node_id = NULL;
+ bool protection = netdata_is_protected_by_bearer;
+
+ while (url) {
+ char *value = strsep_skip_consecutive_separators(&url, "&");
+ if (!value || !*value) continue;
+
+ char *name = strsep_skip_consecutive_separators(&value, "=");
+ if (!name || !*name) continue;
+ if (!value || !*value) continue;
+
+ if(!strcmp(name, "bearer_protection")) {
+ if(!strcmp(value, "on") || !strcmp(value, "true") || !strcmp(value, "yes"))
+ protection = true;
+ else
+ protection = false;
+ }
+ else if(!strcmp(name, "machine_guid"))
+ machine_guid = value;
+ else if(!strcmp(name, "claim_id"))
+ claim_id = value;
+ else if(!strcmp(name, "node_id"))
+ node_id = value;
+ }
+
+ if(!claim_id_matches(claim_id)) {
+ buffer_reset(w->response.data);
+ buffer_strcat(w->response.data, "The request is for a different claimed agent");
+ return HTTP_RESP_BAD_REQUEST;
+ }
+
+ if(!verify_host_uuids(localhost, machine_guid, node_id)) {
+ buffer_reset(w->response.data);
+ buffer_strcat(w->response.data, "The request is missing or not matching local UUIDs");
+ return HTTP_RESP_BAD_REQUEST;
+ }
+
+ netdata_is_protected_by_bearer = protection;
+
+ BUFFER *wb = w->response.data;
+ buffer_reset(wb);
+ buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
+ buffer_json_member_add_boolean(wb, "bearer_protection", netdata_is_protected_by_bearer);
+ buffer_json_finalize(wb);
+
+ return HTTP_RESP_OK;
+}
+
+int bearer_get_token_json_response(BUFFER *wb, RRDHOST *host, const char *claim_id, const char *machine_guid, const char *node_id, HTTP_USER_ROLE user_role, HTTP_ACCESS access, nd_uuid_t cloud_account_id, const char *client_name) {
+ if(!claim_id_matches_any(claim_id))
+ return rrd_call_function_error(wb, "The request is for a different agent", HTTP_RESP_BAD_REQUEST);
+
+ if(!verify_host_uuids(host, machine_guid, node_id))
+ return rrd_call_function_error(wb, "The request is missing or not matching local node UUIDs", HTTP_RESP_BAD_REQUEST);
+
+ nd_uuid_t uuid;
+ time_t expires_s = bearer_create_token(&uuid, user_role, access, cloud_account_id, client_name);
+
+ buffer_reset(wb);
+ buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
+ buffer_json_member_add_int64(wb, "status", HTTP_RESP_OK);
+ buffer_json_member_add_string(wb, "mg", host->machine_guid);
+ buffer_json_member_add_boolean(wb, "bearer_protection", netdata_is_protected_by_bearer);
+ buffer_json_member_add_uuid(wb, "token", uuid);
+ buffer_json_member_add_time_t(wb, "expiration", expires_s);
+ buffer_json_finalize(wb);
+ return HTTP_RESP_OK;
+}
+
+int api_v2_bearer_get_token(RRDHOST *host, struct web_client *w, char *url) {
+ char *machine_guid = NULL;
+ char *claim_id = NULL;
+ char *node_id = NULL;
+
+ while(url) {
+ char *value = strsep_skip_consecutive_separators(&url, "&");
+ if (!value || !*value) continue;
+
+ char *name = strsep_skip_consecutive_separators(&value, "=");
+ if (!name || !*name) continue;
+ if (!value || !*value) continue;
+
+ if(!strcmp(name, "machine_guid"))
+ machine_guid = value;
+ else if(!strcmp(name, "claim_id"))
+ claim_id = value;
+ else if(!strcmp(name, "node_id"))
+ node_id = value;
+ }
+
+ if(!claim_id_matches(claim_id)) {
+ buffer_reset(w->response.data);
+ buffer_strcat(w->response.data, "The request is for a different claimed agent");
+ return HTTP_RESP_BAD_REQUEST;
+ }
+
+ if(!verify_host_uuids(host, machine_guid, node_id)) {
+ buffer_reset(w->response.data);
+ buffer_strcat(w->response.data, "The request is missing or not matching local UUIDs");
+ return HTTP_RESP_BAD_REQUEST;
+ }
+
+ if(host != localhost)
+ return call_function_bearer_get_token(host, w, claim_id, machine_guid, node_id);
+
+ return bearer_get_token_json_response(
+ w->response.data,
+ host,
+ claim_id,
+ machine_guid,
+ node_id,
+ w->user_role,
+ w->access,
+ w->auth.cloud_account_id,
+ w->auth.client_name);
+}
diff --git a/src/web/api/v2/api_v2_calls.h b/src/web/api/v2/api_v2_calls.h
new file mode 100644
index 00000000..809af966
--- /dev/null
+++ b/src/web/api/v2/api_v2_calls.h
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_API_V2_CALLS_H
+#define NETDATA_API_V2_CALLS_H
+
+#include "../web_api_v2.h"
+
+int api_v2_info(RRDHOST *host, struct web_client *w, char *url);
+
+int api_v2_data(RRDHOST *host, struct web_client *w, char *url);
+int api_v2_weights(RRDHOST *host, struct web_client *w, char *url);
+
+int api_v2_alert_config(RRDHOST *host, struct web_client *w, char *url);
+
+int api_v2_contexts_internal(RRDHOST *host, struct web_client *w, char *url, CONTEXTS_V2_MODE mode);
+int api_v2_contexts(RRDHOST *host, struct web_client *w, char *url);
+int api_v2_alert_transitions(RRDHOST *host, struct web_client *w, char *url);
+int api_v2_alerts(RRDHOST *host, struct web_client *w, char *url);
+int api_v2_functions(RRDHOST *host, struct web_client *w, char *url);
+int api_v2_versions(RRDHOST *host, struct web_client *w, char *url);
+int api_v2_q(RRDHOST *host, struct web_client *w, char *url);
+int api_v2_nodes(RRDHOST *host, struct web_client *w, char *url);
+int api_v2_node_instances(RRDHOST *host, struct web_client *w, char *url);
+
+int api_v2_ilove(RRDHOST *host, struct web_client *w, char *url);
+
+int api_v2_claim(RRDHOST *host, struct web_client *w, char *url);
+int api_v3_claim(RRDHOST *host, struct web_client *w, char *url);
+
+int api_v2_webrtc(RRDHOST *host, struct web_client *w, char *url);
+
+int api_v2_progress(RRDHOST *host, struct web_client *w, char *url);
+
+int api_v2_bearer_get_token(RRDHOST *host, struct web_client *w, char *url);
+int bearer_get_token_json_response(BUFFER *wb, RRDHOST *host, const char *claim_id, const char *machine_guid, const char *node_id, HTTP_USER_ROLE user_role, HTTP_ACCESS access, nd_uuid_t cloud_account_id, const char *client_name);
+int api_v2_bearer_protection(RRDHOST *host, struct web_client *w, char *url);
+
+#endif //NETDATA_API_V2_CALLS_H
diff --git a/src/web/api/v2/api_v2_claim.c b/src/web/api/v2/api_v2_claim.c
new file mode 100644
index 00000000..90698610
--- /dev/null
+++ b/src/web/api/v2/api_v2_claim.c
@@ -0,0 +1,236 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "api_v2_calls.h"
+#include "claim/claim.h"
+
+static char *netdata_random_session_id_filename = NULL;
+static nd_uuid_t netdata_random_session_id = { 0 };
+
+bool netdata_random_session_id_generate(void) {
+ static char guid[UUID_STR_LEN] = "";
+
+ uuid_generate_random(netdata_random_session_id);
+ uuid_unparse_lower(netdata_random_session_id, guid);
+
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s/netdata_random_session_id", netdata_configured_varlib_dir);
+
+ bool ret = true;
+
+ (void)unlink(filename);
+
+ // save it
+ int fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 640);
+ if(fd == -1) {
+ netdata_log_error("Cannot create random session id file '%s'.", filename);
+ ret = false;
+ }
+ else {
+ if (write(fd, guid, UUID_STR_LEN - 1) != UUID_STR_LEN - 1) {
+ netdata_log_error("Cannot write the random session id file '%s'.", filename);
+ ret = false;
+ } else {
+ ssize_t bytes = write(fd, "\n", 1);
+ UNUSED(bytes);
+ }
+ close(fd);
+ }
+
+ if(ret && (!netdata_random_session_id_filename || strcmp(netdata_random_session_id_filename, filename) != 0)) {
+ freez(netdata_random_session_id_filename);
+ netdata_random_session_id_filename = strdupz(filename);
+ }
+
+ return ret;
+}
+
+static const char *netdata_random_session_id_get_filename(void) {
+ if(!netdata_random_session_id_filename)
+ netdata_random_session_id_generate();
+
+ return netdata_random_session_id_filename;
+}
+
+static bool netdata_random_session_id_matches(const char *guid) {
+ if(uuid_is_null(netdata_random_session_id))
+ return false;
+
+ nd_uuid_t uuid;
+
+ if(uuid_parse(guid, uuid))
+ return false;
+
+ if(uuid_compare(netdata_random_session_id, uuid) == 0)
+ return true;
+
+ return false;
+}
+
+static bool check_claim_param(const char *s) {
+ if(!s || !*s) return true;
+
+ do {
+ if(isalnum((uint8_t)*s) || *s == '.' || *s == ',' || *s == '-' || *s == ':' || *s == '/' || *s == '_')
+ ;
+ else
+ return false;
+
+ } while(*++s);
+
+ return true;
+}
+
+static bool agent_can_be_claimed(void) {
+ CLOUD_STATUS status = cloud_status();
+ switch(status) {
+ case CLOUD_STATUS_AVAILABLE:
+ case CLOUD_STATUS_OFFLINE:
+ case CLOUD_STATUS_INDIRECT:
+ return true;
+
+ case CLOUD_STATUS_BANNED:
+ case CLOUD_STATUS_ONLINE:
+ return false;
+ }
+
+ return false;
+}
+
+typedef enum {
+ CLAIM_RESP_INFO,
+ CLAIM_RESP_ERROR,
+ CLAIM_RESP_ACTION_OK,
+ CLAIM_RESP_ACTION_FAILED,
+} CLAIM_RESPONSE;
+
+static void claim_add_user_info_command(BUFFER *wb) {
+ const char *filename = netdata_random_session_id_get_filename();
+ CLEAN_BUFFER *os_cmd = buffer_create(0, NULL);
+
+ const char *os_filename;
+ const char *os_prefix;
+ const char *os_quote;
+ const char *os_message;
+
+#if defined(OS_WINDOWS)
+ char win_path[MAX_PATH];
+ cygwin_conv_path(CCP_POSIX_TO_WIN_A, filename, win_path, sizeof(win_path));
+ os_filename = win_path;
+ os_prefix = "more";
+ os_message = "We need to verify this Windows server is yours. So, open a Command Prompt on this server to run the command. It will give you a UUID. Copy and paste this UUID to this box:";
+#else
+ os_filename = filename;
+ os_prefix = "sudo cat";
+ os_message = "We need to verify this server is yours. SSH to this server and run this command. It will give you a UUID. Copy and paste this UUID to this box:";
+#endif
+
+ // add quotes only when the filename has a space
+ if(strchr(os_filename, ' '))
+ os_quote = "\"";
+ else
+ os_quote = "";
+
+ buffer_sprintf(os_cmd, "%s %s%s%s", os_prefix, os_quote, os_filename, os_quote);
+
+ buffer_json_member_add_string(wb, "key_filename", os_filename);
+ buffer_json_member_add_string(wb, "cmd", buffer_tostring(os_cmd));
+ buffer_json_member_add_string(wb, "help", os_message);
+}
+
+static int claim_json_response(BUFFER *wb, CLAIM_RESPONSE response, const char *msg) {
+ time_t now_s = now_realtime_sec();
+ buffer_reset(wb);
+ buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
+
+ if(response != CLAIM_RESP_INFO) {
+ // this is not an info, so it needs a status report
+ buffer_json_member_add_boolean(wb, "success", response == CLAIM_RESP_ACTION_OK ? true : false);
+ buffer_json_member_add_string_or_empty(wb, "message", msg ? msg : "");
+ }
+
+ buffer_json_cloud_status(wb, now_s);
+
+ if(response != CLAIM_RESP_ACTION_OK) {
+ buffer_json_member_add_boolean(wb, "can_be_claimed", agent_can_be_claimed());
+ claim_add_user_info_command(wb);
+ }
+
+ buffer_json_agents_v2(wb, NULL, now_s, false, false);
+ buffer_json_finalize(wb);
+
+ return (response == CLAIM_RESP_ERROR) ? HTTP_RESP_BAD_REQUEST : HTTP_RESP_OK;
+}
+
+static int claim_txt_response(BUFFER *wb, const char *msg) {
+ buffer_reset(wb);
+ buffer_strcat(wb, msg);
+ return HTTP_RESP_BAD_REQUEST;
+}
+
+static int api_claim(uint8_t version, struct web_client *w, char *url) {
+ char *key = NULL;
+ char *token = NULL;
+ char *rooms = NULL;
+ char *base_url = NULL;
+
+ while (url) {
+ char *value = strsep_skip_consecutive_separators(&url, "&");
+ if (!value || !*value) continue;
+
+ char *name = strsep_skip_consecutive_separators(&value, "=");
+ if (!name || !*name) continue;
+ if (!value || !*value) continue;
+
+ if(!strcmp(name, "key"))
+ key = value;
+ else if(!strcmp(name, "token"))
+ token = value;
+ else if(!strcmp(name, "rooms"))
+ rooms = value;
+ else if(!strcmp(name, "url"))
+ base_url = value;
+ }
+
+ BUFFER *wb = w->response.data;
+
+ CLAIM_RESPONSE response = CLAIM_RESP_INFO;
+ const char *msg = NULL;
+ bool can_be_claimed = agent_can_be_claimed();
+
+ if(can_be_claimed && key) {
+ if(!netdata_random_session_id_matches(key)) {
+ netdata_random_session_id_generate(); // generate a new key, to avoid an attack to find it
+ if(version < 3) return claim_txt_response(wb, "invalid key");
+ return claim_json_response(wb, CLAIM_RESP_ERROR, "invalid key");
+ }
+
+ if(!token || !base_url || !check_claim_param(token) || !check_claim_param(base_url) || (rooms && !check_claim_param(rooms))) {
+ netdata_random_session_id_generate(); // generate a new key, to avoid an attack to find it
+ if(version < 3) return claim_txt_response(wb, "invalid parameters");
+ return claim_json_response(wb, CLAIM_RESP_ERROR, "invalid parameters");
+ }
+
+ netdata_random_session_id_generate(); // generate a new key, to avoid an attack to find it
+
+ if(claim_agent(base_url, token, rooms, cloud_config_proxy_get(), cloud_config_insecure_get())) {
+ msg = "ok";
+ can_be_claimed = false;
+ claim_reload_and_wait_online();
+ response = CLAIM_RESP_ACTION_OK;
+ }
+ else {
+ msg = claim_agent_failure_reason_get();
+ response = CLAIM_RESP_ACTION_FAILED;
+ }
+ }
+
+ return claim_json_response(wb, response, msg);
+}
+
+int api_v2_claim(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
+ return api_claim(2, w, url);
+}
+
+int api_v3_claim(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
+ return api_claim(3, w, url);
+}
diff --git a/src/web/api/v2/api_v2_contexts.c b/src/web/api/v2/api_v2_contexts.c
new file mode 100644
index 00000000..bbe36ab3
--- /dev/null
+++ b/src/web/api/v2/api_v2_contexts.c
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "api_v2_calls.h"
+
+// --------------------------------------------------------------------------------------------------------------------
+
+int api_v2_contexts_internal(RRDHOST *host __maybe_unused, struct web_client *w, char *url, CONTEXTS_V2_MODE mode) {
+ struct api_v2_contexts_request req = { 0 };
+
+ while(url) {
+ char *value = strsep_skip_consecutive_separators(&url, "&");
+ if(!value || !*value) continue;
+
+ char *name = strsep_skip_consecutive_separators(&value, "=");
+ if(!name || !*name) continue;
+ if(!value || !*value) continue;
+
+ // name and value are now the parameters
+ // they are not null and not empty
+
+ if(!strcmp(name, "scope_nodes"))
+ req.scope_nodes = value;
+ else if(!strcmp(name, "nodes"))
+ req.nodes = value;
+ else if((mode & (CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_SEARCH | CONTEXTS_V2_ALERTS | CONTEXTS_V2_ALERT_TRANSITIONS)) && !strcmp(name, "scope_contexts"))
+ req.scope_contexts = value;
+ else if((mode & (CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_SEARCH | CONTEXTS_V2_ALERTS | CONTEXTS_V2_ALERT_TRANSITIONS)) && !strcmp(name, "contexts"))
+ req.contexts = value;
+ else if((mode & CONTEXTS_V2_SEARCH) && !strcmp(name, "q"))
+ req.q = value;
+ else if(!strcmp(name, "options"))
+ req.options = contexts_options_str_to_id(value);
+ else if(!strcmp(name, "after"))
+ req.after = str2l(value);
+ else if(!strcmp(name, "before"))
+ req.before = str2l(value);
+ else if(!strcmp(name, "timeout"))
+ req.timeout_ms = str2l(value);
+ else if(mode & (CONTEXTS_V2_ALERTS | CONTEXTS_V2_ALERT_TRANSITIONS)) {
+ if (!strcmp(name, "alert"))
+ req.alerts.alert = value;
+ else if (!strcmp(name, "transition"))
+ req.alerts.transition = value;
+ else if(mode & CONTEXTS_V2_ALERTS) {
+ if (!strcmp(name, "status"))
+ req.alerts.status = contexts_alert_status_str_to_id(value);
+ }
+ else if(mode & CONTEXTS_V2_ALERT_TRANSITIONS) {
+ if (!strcmp(name, "last"))
+ req.alerts.last = strtoul(value, NULL, 0);
+ else if(!strcmp(name, "context"))
+ req.contexts = value;
+ else if (!strcmp(name, "anchor_gi")) {
+ req.alerts.global_id_anchor = str2ull(value, NULL);
+ }
+ else {
+ for(int i = 0; i < ATF_TOTAL_ENTRIES ;i++) {
+ if(!strcmp(name, alert_transition_facets[i].query_param))
+ req.alerts.facets[i] = value;
+ }
+ }
+ }
+ }
+ }
+
+ if ((mode & CONTEXTS_V2_ALERT_TRANSITIONS) && !req.alerts.last)
+ req.alerts.last = 1;
+
+ buffer_flush(w->response.data);
+ buffer_no_cacheable(w->response.data);
+ return rrdcontext_to_json_v2(w->response.data, &req, mode);
+}
+
+int api_v2_contexts(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
+ return api_v2_contexts_internal(
+ host, w, url, CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_NODES | CONTEXTS_V2_AGENTS | CONTEXTS_V2_VERSIONS);
+}
+
diff --git a/src/web/api/v2/api_v2_data.c b/src/web/api/v2/api_v2_data.c
new file mode 100644
index 00000000..4eb54e9a
--- /dev/null
+++ b/src/web/api/v2/api_v2_data.c
@@ -0,0 +1,302 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "api_v2_calls.h"
+
+#define GROUP_BY_KEY_MAX_LENGTH 30
+static struct {
+ char group_by[GROUP_BY_KEY_MAX_LENGTH + 1];
+ char aggregation[GROUP_BY_KEY_MAX_LENGTH + 1];
+ char group_by_label[GROUP_BY_KEY_MAX_LENGTH + 1];
+} group_by_keys[MAX_QUERY_GROUP_BY_PASSES];
+
+__attribute__((constructor)) void initialize_group_by_keys(void) {
+ for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) {
+ snprintfz(group_by_keys[g].group_by, GROUP_BY_KEY_MAX_LENGTH, "group_by[%zu]", g);
+ snprintfz(group_by_keys[g].aggregation, GROUP_BY_KEY_MAX_LENGTH, "aggregation[%zu]", g);
+ snprintfz(group_by_keys[g].group_by_label, GROUP_BY_KEY_MAX_LENGTH, "group_by_label[%zu]", g);
+ }
+}
+
+int api_v2_data(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
+ usec_t received_ut = now_monotonic_usec();
+
+ int ret = HTTP_RESP_BAD_REQUEST;
+
+ buffer_flush(w->response.data);
+
+ char *google_version = "0.6",
+ *google_reqId = "0",
+ *google_sig = "0",
+ *google_out = "json",
+ *responseHandler = NULL,
+ *outFileName = NULL;
+
+ time_t last_timestamp_in_data = 0, google_timestamp = 0;
+
+ char *scope_nodes = NULL;
+ char *scope_contexts = NULL;
+ char *nodes = NULL;
+ char *contexts = NULL;
+ char *instances = NULL;
+ char *dimensions = NULL;
+ char *before_str = NULL;
+ char *after_str = NULL;
+ char *resampling_time_str = NULL;
+ char *points_str = NULL;
+ char *timeout_str = NULL;
+ char *labels = NULL;
+ char *alerts = NULL;
+ char *time_group_options = NULL;
+ char *tier_str = NULL;
+ size_t tier = 0;
+ RRDR_TIME_GROUPING time_group = RRDR_GROUPING_AVERAGE;
+ DATASOURCE_FORMAT format = DATASOURCE_JSON2;
+ RRDR_OPTIONS options = RRDR_OPTION_VIRTUAL_POINTS | RRDR_OPTION_JSON_WRAP | RRDR_OPTION_RETURN_JWAR;
+
+ struct group_by_pass group_by[MAX_QUERY_GROUP_BY_PASSES] = {
+ {
+ .group_by = RRDR_GROUP_BY_DIMENSION,
+ .group_by_label = NULL,
+ .aggregation = RRDR_GROUP_BY_FUNCTION_AVERAGE,
+ },
+ };
+
+ size_t group_by_idx = 0, group_by_label_idx = 0, aggregation_idx = 0;
+
+ while(url) {
+ char *value = strsep_skip_consecutive_separators(&url, "&");
+ if(!value || !*value) continue;
+
+ char *name = strsep_skip_consecutive_separators(&value, "=");
+ if(!name || !*name) continue;
+ if(!value || !*value) continue;
+
+ // name and value are now the parameters
+ // they are not null and not empty
+
+ if(!strcmp(name, "scope_nodes")) scope_nodes = value;
+ else if(!strcmp(name, "scope_contexts")) scope_contexts = value;
+ else if(!strcmp(name, "nodes")) nodes = value;
+ else if(!strcmp(name, "contexts")) contexts = value;
+ else if(!strcmp(name, "instances")) instances = value;
+ else if(!strcmp(name, "dimensions")) dimensions = value;
+ else if(!strcmp(name, "labels")) labels = value;
+ else if(!strcmp(name, "alerts")) alerts = value;
+ else if(!strcmp(name, "after")) after_str = value;
+ else if(!strcmp(name, "before")) before_str = value;
+ else if(!strcmp(name, "points")) points_str = value;
+ else if(!strcmp(name, "timeout")) timeout_str = value;
+ else if(!strcmp(name, "group_by")) {
+ group_by[group_by_idx++].group_by = group_by_parse(value);
+ if(group_by_idx >= MAX_QUERY_GROUP_BY_PASSES)
+ group_by_idx = MAX_QUERY_GROUP_BY_PASSES - 1;
+ }
+ else if(!strcmp(name, "group_by_label")) {
+ group_by[group_by_label_idx++].group_by_label = value;
+ if(group_by_label_idx >= MAX_QUERY_GROUP_BY_PASSES)
+ group_by_label_idx = MAX_QUERY_GROUP_BY_PASSES - 1;
+ }
+ else if(!strcmp(name, "aggregation")) {
+ group_by[aggregation_idx++].aggregation = group_by_aggregate_function_parse(value);
+ if(aggregation_idx >= MAX_QUERY_GROUP_BY_PASSES)
+ aggregation_idx = MAX_QUERY_GROUP_BY_PASSES - 1;
+ }
+ else if(!strcmp(name, "format")) format = datasource_format_str_to_id(value);
+ else if(!strcmp(name, "options")) options |= rrdr_options_parse(value);
+ else if(!strcmp(name, "time_group")) time_group = time_grouping_parse(value, RRDR_GROUPING_AVERAGE);
+ else if(!strcmp(name, "time_group_options")) time_group_options = value;
+ else if(!strcmp(name, "time_resampling")) resampling_time_str = value;
+ else if(!strcmp(name, "tier")) tier_str = value;
+ else if(!strcmp(name, "callback")) responseHandler = value;
+ else if(!strcmp(name, "filename")) outFileName = value;
+ else if(!strcmp(name, "tqx")) {
+ // parse Google Visualization API options
+ // https://developers.google.com/chart/interactive/docs/dev/implementing_data_source
+ char *tqx_name, *tqx_value;
+
+ while(value) {
+ tqx_value = strsep_skip_consecutive_separators(&value, ";");
+ if(!tqx_value || !*tqx_value) continue;
+
+ tqx_name = strsep_skip_consecutive_separators(&tqx_value, ":");
+ if(!tqx_name || !*tqx_name) continue;
+ if(!tqx_value || !*tqx_value) continue;
+
+ if(!strcmp(tqx_name, "version"))
+ google_version = tqx_value;
+ else if(!strcmp(tqx_name, "reqId"))
+ google_reqId = tqx_value;
+ else if(!strcmp(tqx_name, "sig")) {
+ google_sig = tqx_value;
+ google_timestamp = strtoul(google_sig, NULL, 0);
+ }
+ else if(!strcmp(tqx_name, "out")) {
+ google_out = tqx_value;
+ format = google_data_format_str_to_id(google_out);
+ }
+ else if(!strcmp(tqx_name, "responseHandler"))
+ responseHandler = tqx_value;
+ else if(!strcmp(tqx_name, "outFileName"))
+ outFileName = tqx_value;
+ }
+ }
+ else {
+ for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) {
+ if(!strcmp(name, group_by_keys[g].group_by))
+ group_by[g].group_by = group_by_parse(value);
+ else if(!strcmp(name, group_by_keys[g].group_by_label))
+ group_by[g].group_by_label = value;
+ else if(!strcmp(name, group_by_keys[g].aggregation))
+ group_by[g].aggregation = group_by_aggregate_function_parse(value);
+ }
+ }
+ }
+
+ // validate the google parameters given
+ fix_google_param(google_out);
+ fix_google_param(google_sig);
+ fix_google_param(google_reqId);
+ fix_google_param(google_version);
+ fix_google_param(responseHandler);
+ fix_google_param(outFileName);
+
+ for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) {
+ if (group_by[g].group_by_label && *group_by[g].group_by_label)
+ group_by[g].group_by |= RRDR_GROUP_BY_LABEL;
+ }
+
+ if(group_by[0].group_by == RRDR_GROUP_BY_NONE)
+ group_by[0].group_by = RRDR_GROUP_BY_DIMENSION;
+
+ for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) {
+ if ((group_by[g].group_by & ~(RRDR_GROUP_BY_DIMENSION)) || (options & RRDR_OPTION_PERCENTAGE)) {
+ options |= RRDR_OPTION_ABSOLUTE;
+ break;
+ }
+ }
+
+ if(options & RRDR_OPTION_DEBUG)
+ options &= ~RRDR_OPTION_MINIFY;
+
+ if(tier_str && *tier_str) {
+ tier = str2ul(tier_str);
+ if(tier < storage_tiers)
+ options |= RRDR_OPTION_SELECTED_TIER;
+ else
+ tier = 0;
+ }
+
+ time_t before = (before_str && *before_str)?str2l(before_str):0;
+ time_t after = (after_str && *after_str) ?str2l(after_str):-600;
+ size_t points = (points_str && *points_str)?str2u(points_str):0;
+ int timeout = (timeout_str && *timeout_str)?str2i(timeout_str): 0;
+ time_t resampling_time = (resampling_time_str && *resampling_time_str) ? str2l(resampling_time_str) : 0;
+
+ QUERY_TARGET_REQUEST qtr = {
+ .version = 2,
+ .scope_nodes = scope_nodes,
+ .scope_contexts = scope_contexts,
+ .after = after,
+ .before = before,
+ .host = NULL,
+ .st = NULL,
+ .nodes = nodes,
+ .contexts = contexts,
+ .instances = instances,
+ .dimensions = dimensions,
+ .alerts = alerts,
+ .timeout_ms = timeout,
+ .points = points,
+ .format = format,
+ .options = options,
+ .time_group_method = time_group,
+ .time_group_options = time_group_options,
+ .resampling_time = resampling_time,
+ .tier = tier,
+ .chart_label_key = NULL,
+ .labels = labels,
+ .query_source = QUERY_SOURCE_API_DATA,
+ .priority = STORAGE_PRIORITY_NORMAL,
+ .received_ut = received_ut,
+
+ .interrupt_callback = web_client_interrupt_callback,
+ .interrupt_callback_data = w,
+
+ .transaction = &w->transaction,
+ };
+
+ for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++)
+ qtr.group_by[g] = group_by[g];
+
+ QUERY_TARGET *qt = query_target_create(&qtr);
+ ONEWAYALLOC *owa = NULL;
+
+ if(!qt) {
+ buffer_sprintf(w->response.data, "Failed to prepare the query.");
+ ret = HTTP_RESP_INTERNAL_SERVER_ERROR;
+ goto cleanup;
+ }
+
+ web_client_timeout_checkpoint_set(w, timeout);
+ if(web_client_timeout_checkpoint_and_check(w, NULL)) {
+ ret = w->response.code;
+ goto cleanup;
+ }
+
+ if(outFileName && *outFileName) {
+ buffer_sprintf(w->response.header, "Content-Disposition: attachment; filename=\"%s\"\r\n", outFileName);
+ netdata_log_debug(D_WEB_CLIENT, "%llu: generating outfilename header: '%s'", w->id, outFileName);
+ }
+
+ if(format == DATASOURCE_DATATABLE_JSONP) {
+ if(responseHandler == NULL)
+ responseHandler = "google.visualization.Query.setResponse";
+
+ netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: GOOGLE JSON/JSONP: version = '%s', reqId = '%s', sig = '%s', out = '%s', responseHandler = '%s', outFileName = '%s'",
+ w->id, google_version, google_reqId, google_sig, google_out, responseHandler, outFileName
+ );
+
+ buffer_sprintf(
+ w->response.data,
+ "%s({version:'%s',reqId:'%s',status:'ok',sig:'%"PRId64"',table:",
+ responseHandler,
+ google_version,
+ google_reqId,
+ (int64_t)now_realtime_sec());
+ }
+ else if(format == DATASOURCE_JSONP) {
+ if(responseHandler == NULL)
+ responseHandler = "callback";
+
+ buffer_strcat(w->response.data, responseHandler);
+ buffer_strcat(w->response.data, "(");
+ }
+
+ owa = onewayalloc_create(0);
+ ret = data_query_execute(owa, w->response.data, qt, &last_timestamp_in_data);
+
+ if(format == DATASOURCE_DATATABLE_JSONP) {
+ if(google_timestamp < last_timestamp_in_data)
+ buffer_strcat(w->response.data, "});");
+
+ else {
+ // the client already has the latest data
+ buffer_flush(w->response.data);
+ buffer_sprintf(w->response.data,
+ "%s({version:'%s',reqId:'%s',status:'error',errors:[{reason:'not_modified',message:'Data not modified'}]});",
+ responseHandler, google_version, google_reqId);
+ }
+ }
+ else if(format == DATASOURCE_JSONP)
+ buffer_strcat(w->response.data, ");");
+
+ if(qt->internal.relative)
+ buffer_no_cacheable(w->response.data);
+ else
+ buffer_cacheable(w->response.data);
+
+cleanup:
+ query_target_release(qt);
+ onewayalloc_destroy(owa);
+ return ret;
+}
diff --git a/src/web/api/v2/api_v2_functions.c b/src/web/api/v2/api_v2_functions.c
new file mode 100644
index 00000000..286efd13
--- /dev/null
+++ b/src/web/api/v2/api_v2_functions.c
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "api_v2_calls.h"
+
+int api_v2_functions(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
+ return api_v2_contexts_internal(
+ host, w, url, CONTEXTS_V2_FUNCTIONS | CONTEXTS_V2_NODES | CONTEXTS_V2_AGENTS | CONTEXTS_V2_VERSIONS);
+}
diff --git a/src/web/api/ilove/README.md b/src/web/api/v2/api_v2_ilove/README.md
index e69de29b..e69de29b 100644
--- a/src/web/api/ilove/README.md
+++ b/src/web/api/v2/api_v2_ilove/README.md
diff --git a/src/web/api/ilove/ilove.c b/src/web/api/v2/api_v2_ilove/ilove.c
index 67489ec4..501e0012 100644
--- a/src/web/api/ilove/ilove.c
+++ b/src/web/api/v2/api_v2_ilove/ilove.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later
-#include "ilove.h"
+#include "../api_v2_calls.h"
static const unsigned short int ibm_plex_sans_bold_250[128][128] = {
{0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* */, 0 /* ! */, 0 /* " */, 0 /* # */, 0 /* $ */, 0 /* % */, 0 /* & */, 0 /* ' */, 0 /* ( */, 0 /* ) */, 0 /* * */, 0 /* + */, 0 /* , */, 0 /* - */, 0 /* . */, 0 /* / */, 0 /* 0 */, 0 /* 1 */, 0 /* 2 */, 0 /* 3 */, 0 /* 4 */, 0 /* 5 */, 0 /* 6 */, 0 /* 7 */, 0 /* 8 */, 0 /* 9 */, 0 /* : */, 0 /* ; */, 0 /* < */, 0 /* = */, 0 /* > */, 0 /* ? */, 0 /* @ */, 0 /* A */, 0 /* B */, 0 /* C */, 0 /* D */, 0 /* E */, 0 /* F */, 0 /* G */, 0 /* H */, 0 /* I */, 0 /* J */, 0 /* K */, 0 /* L */, 0 /* M */, 0 /* N */, 0 /* O */, 0 /* P */, 0 /* Q */, 0 /* R */, 0 /* S */, 0 /* T */, 0 /* U */, 0 /* V */, 0 /* W */, 0 /* X */, 0 /* Y */, 0 /* Z */, 0 /* [ */, 0 /* \ */, 0 /* ] */, 0 /* ^ */, 0 /* _ */, 0 /* ` */, 0 /* a */, 0 /* b */, 0 /* c */, 0 /* d */, 0 /* e */, 0 /* f */, 0 /* g */, 0 /* h */, 0 /* i */, 0 /* j */, 0 /* k */, 0 /* l */, 0 /* m */, 0 /* n */, 0 /* o */, 0 /* p */, 0 /* q */, 0 /* r */, 0 /* s */, 0 /* t */, 0 /* u */, 0 /* v */, 0 /* w */, 0 /* x */, 0 /* y */, 0 /* z */, 0 /* { */, 0 /* | */, 0 /* } */, 0 /* ~ */},
@@ -277,7 +277,7 @@ static void generate_ilove_svg(BUFFER *wb, const char *love) {
wb->content_type = CT_IMAGE_SVG_XML;
}
-int web_client_api_request_v2_ilove(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
+int api_v2_ilove(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
char *love = "TROUBLE";
while(url) {
diff --git a/src/web/api/ilove/measure-text.js b/src/web/api/v2/api_v2_ilove/measure-text.js
index e2a2a6e9..e2a2a6e9 100644
--- a/src/web/api/ilove/measure-text.js
+++ b/src/web/api/v2/api_v2_ilove/measure-text.js
diff --git a/src/web/api/v2/api_v2_info.c b/src/web/api/v2/api_v2_info.c
new file mode 100644
index 00000000..fd2aba63
--- /dev/null
+++ b/src/web/api/v2/api_v2_info.c
@@ -0,0 +1,7 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "api_v2_calls.h"
+
+int api_v2_info(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
+ return api_v2_contexts_internal(host, w, url, CONTEXTS_V2_AGENTS | CONTEXTS_V2_AGENTS_INFO);
+}
diff --git a/src/web/api/v2/api_v2_node_instances.c b/src/web/api/v2/api_v2_node_instances.c
new file mode 100644
index 00000000..03719143
--- /dev/null
+++ b/src/web/api/v2/api_v2_node_instances.c
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "api_v2_calls.h"
+
+int api_v2_node_instances(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
+ return api_v2_contexts_internal(
+ host, w, url,
+ CONTEXTS_V2_NODES | CONTEXTS_V2_NODE_INSTANCES | CONTEXTS_V2_AGENTS |
+ CONTEXTS_V2_AGENTS_INFO | CONTEXTS_V2_VERSIONS);
+}
diff --git a/src/web/api/v2/api_v2_nodes.c b/src/web/api/v2/api_v2_nodes.c
new file mode 100644
index 00000000..3880f279
--- /dev/null
+++ b/src/web/api/v2/api_v2_nodes.c
@@ -0,0 +1,7 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "api_v2_calls.h"
+
+int api_v2_nodes(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
+ return api_v2_contexts_internal(host, w, url, CONTEXTS_V2_NODES | CONTEXTS_V2_NODES_INFO);
+}
diff --git a/src/web/api/v2/api_v2_progress.c b/src/web/api/v2/api_v2_progress.c
new file mode 100644
index 00000000..ebb53ca8
--- /dev/null
+++ b/src/web/api/v2/api_v2_progress.c
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "api_v2_calls.h"
+
+int api_v2_progress(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
+ char *transaction = NULL;
+
+ while(url) {
+ char *value = strsep_skip_consecutive_separators(&url, "&");
+ if(!value || !*value) continue;
+
+ char *name = strsep_skip_consecutive_separators(&value, "=");
+ if(!name || !*name) continue;
+ if(!value || !*value) continue;
+
+ // name and value are now the parameters
+ // they are not null and not empty
+
+ if(!strcmp(name, "transaction")) transaction = value;
+ }
+
+ nd_uuid_t tr;
+ uuid_parse_flexi(transaction, tr);
+
+ rrd_function_call_progresser(&tr);
+
+ return web_api_v2_report_progress(&tr, w->response.data);
+}
diff --git a/src/web/api/v2/api_v2_q.c b/src/web/api/v2/api_v2_q.c
new file mode 100644
index 00000000..57fcec7d
--- /dev/null
+++ b/src/web/api/v2/api_v2_q.c
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "api_v2_calls.h"
+
+int api_v2_q(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
+ return api_v2_contexts_internal(
+ host, w, url,
+ CONTEXTS_V2_SEARCH | CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_NODES | CONTEXTS_V2_AGENTS | CONTEXTS_V2_VERSIONS);
+}
diff --git a/src/web/api/v2/api_v2_versions.c b/src/web/api/v2/api_v2_versions.c
new file mode 100644
index 00000000..299e7a30
--- /dev/null
+++ b/src/web/api/v2/api_v2_versions.c
@@ -0,0 +1,7 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "api_v2_calls.h"
+
+int api_v2_versions(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
+ return api_v2_contexts_internal(host, w, url, CONTEXTS_V2_VERSIONS);
+}
diff --git a/src/web/api/v2/api_v2_webrtc.c b/src/web/api/v2/api_v2_webrtc.c
new file mode 100644
index 00000000..dcd383d4
--- /dev/null
+++ b/src/web/api/v2/api_v2_webrtc.c
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "api_v2_calls.h"
+#include "../../rtc/webrtc.h"
+
+int api_v2_webrtc(RRDHOST *host __maybe_unused, struct web_client *w, char *url __maybe_unused) {
+ return webrtc_new_connection(buffer_tostring(w->payload), w->response.data);
+}
diff --git a/src/web/api/v2/api_v2_weights.c b/src/web/api/v2/api_v2_weights.c
new file mode 100644
index 00000000..442c8b75
--- /dev/null
+++ b/src/web/api/v2/api_v2_weights.c
@@ -0,0 +1,152 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "api_v2_calls.h"
+
+int web_client_api_request_weights(RRDHOST *host, struct web_client *w, char *url, WEIGHTS_METHOD method, WEIGHTS_FORMAT format, size_t api_version) {
+ if (!netdata_ready)
+ return HTTP_RESP_SERVICE_UNAVAILABLE;
+
+ time_t baseline_after = 0, baseline_before = 0, after = 0, before = 0;
+ size_t points = 0;
+ RRDR_OPTIONS options = 0;
+ RRDR_TIME_GROUPING time_group_method = RRDR_GROUPING_AVERAGE;
+ time_t timeout_ms = 0;
+ size_t tier = 0;
+ const char *time_group_options = NULL, *scope_contexts = NULL, *scope_nodes = NULL, *contexts = NULL, *nodes = NULL,
+ *instances = NULL, *dimensions = NULL, *labels = NULL, *alerts = NULL;
+
+ struct group_by_pass group_by = {
+ .group_by = RRDR_GROUP_BY_NONE,
+ .group_by_label = NULL,
+ .aggregation = RRDR_GROUP_BY_FUNCTION_AVERAGE,
+ };
+
+ while (url) {
+ char *value = strsep_skip_consecutive_separators(&url, "&");
+ if (!value || !*value)
+ continue;
+
+ char *name = strsep_skip_consecutive_separators(&value, "=");
+ if (!name || !*name)
+ continue;
+ if (!value || !*value)
+ continue;
+
+ if (!strcmp(name, "baseline_after"))
+ baseline_after = str2l(value);
+
+ else if (!strcmp(name, "baseline_before"))
+ baseline_before = str2l(value);
+
+ else if (!strcmp(name, "after") || !strcmp(name, "highlight_after"))
+ after = str2l(value);
+
+ else if (!strcmp(name, "before") || !strcmp(name, "highlight_before"))
+ before = str2l(value);
+
+ else if (!strcmp(name, "points") || !strcmp(name, "max_points"))
+ points = str2ul(value);
+
+ else if (!strcmp(name, "timeout"))
+ timeout_ms = str2l(value);
+
+ else if((api_version == 1 && !strcmp(name, "group")) || (api_version >= 2 && !strcmp(name, "time_group")))
+ time_group_method = time_grouping_parse(value, RRDR_GROUPING_AVERAGE);
+
+ else if((api_version == 1 && !strcmp(name, "group_options")) || (api_version >= 2 && !strcmp(name, "time_group_options")))
+ time_group_options = value;
+
+ else if(!strcmp(name, "options"))
+ options |= rrdr_options_parse(value);
+
+ else if(!strcmp(name, "method"))
+ method = weights_string_to_method(value);
+
+ else if(api_version == 1 && (!strcmp(name, "context") || !strcmp(name, "contexts")))
+ scope_contexts = value;
+
+ else if(api_version >= 2 && !strcmp(name, "scope_nodes")) scope_nodes = value;
+ else if(api_version >= 2 && !strcmp(name, "scope_contexts")) scope_contexts = value;
+ else if(api_version >= 2 && !strcmp(name, "nodes")) nodes = value;
+ else if(api_version >= 2 && !strcmp(name, "contexts")) contexts = value;
+ else if(api_version >= 2 && !strcmp(name, "instances")) instances = value;
+ else if(api_version >= 2 && !strcmp(name, "dimensions")) dimensions = value;
+ else if(api_version >= 2 && !strcmp(name, "labels")) labels = value;
+ else if(api_version >= 2 && !strcmp(name, "alerts")) alerts = value;
+ else if(api_version >= 2 && (!strcmp(name, "group_by") || !strcmp(name, "group_by[0]"))) {
+ group_by.group_by = group_by_parse(value);
+ }
+ else if(api_version >= 2 && (!strcmp(name, "group_by_label") || !strcmp(name, "group_by_label[0]"))) {
+ group_by.group_by_label = value;
+ }
+ else if(api_version >= 2 && (!strcmp(name, "aggregation") || !strcmp(name, "aggregation[0]"))) {
+ group_by.aggregation = group_by_aggregate_function_parse(value);
+ }
+
+ else if(!strcmp(name, "tier")) {
+ tier = str2ul(value);
+ if(tier < storage_tiers)
+ options |= RRDR_OPTION_SELECTED_TIER;
+ else
+ tier = 0;
+ }
+ }
+
+ if(options == 0)
+ // the user did not set any options
+ options = RRDR_OPTION_NOT_ALIGNED | RRDR_OPTION_NULL2ZERO | RRDR_OPTION_NONZERO;
+ else
+ // the user set some options, add also these
+ options |= RRDR_OPTION_NOT_ALIGNED | RRDR_OPTION_NULL2ZERO;
+
+ if(options & RRDR_OPTION_PERCENTAGE)
+ options |= RRDR_OPTION_ABSOLUTE;
+
+ if(options & RRDR_OPTION_DEBUG)
+ options &= ~RRDR_OPTION_MINIFY;
+
+ BUFFER *wb = w->response.data;
+ buffer_flush(wb);
+ wb->content_type = CT_APPLICATION_JSON;
+
+ QUERY_WEIGHTS_REQUEST qwr = {
+ .version = api_version,
+ .host = (api_version == 1) ? NULL : host,
+ .scope_nodes = scope_nodes,
+ .scope_contexts = scope_contexts,
+ .nodes = nodes,
+ .contexts = contexts,
+ .instances = instances,
+ .dimensions = dimensions,
+ .labels = labels,
+ .alerts = alerts,
+ .group_by = {
+ .group_by = group_by.group_by,
+ .group_by_label = group_by.group_by_label,
+ .aggregation = group_by.aggregation,
+ },
+ .method = method,
+ .format = format,
+ .time_group_method = time_group_method,
+ .time_group_options = time_group_options,
+ .baseline_after = baseline_after,
+ .baseline_before = baseline_before,
+ .after = after,
+ .before = before,
+ .points = points,
+ .options = options,
+ .tier = tier,
+ .timeout_ms = timeout_ms,
+
+ .interrupt_callback = web_client_interrupt_callback,
+ .interrupt_callback_data = w,
+
+ .transaction = &w->transaction,
+ };
+
+ return web_api_v12_weights(wb, &qwr);
+}
+
+int api_v2_weights(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
+ return web_client_api_request_weights(host, w, url, WEIGHTS_METHOD_VALUE, WEIGHTS_FORMAT_MULTINODE, 2);
+}
diff --git a/src/web/api/v3/api_v3_calls.h b/src/web/api/v3/api_v3_calls.h
new file mode 100644
index 00000000..4cee766f
--- /dev/null
+++ b/src/web/api/v3/api_v3_calls.h
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_API_V3_CALLS_H
+#define NETDATA_API_V3_CALLS_H
+
+#include "../web_api_v3.h"
+
+int api_v3_settings(RRDHOST *host, struct web_client *w, char *url);
+int api_v3_me(RRDHOST *host, struct web_client *w, char *url);
+
+#endif //NETDATA_API_V3_CALLS_H
diff --git a/src/web/api/v3/api_v3_me.c b/src/web/api/v3/api_v3_me.c
new file mode 100644
index 00000000..39ba2c29
--- /dev/null
+++ b/src/web/api/v3/api_v3_me.c
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "api_v3_calls.h"
+
+int api_v3_me(RRDHOST *host __maybe_unused, struct web_client *w, char *url __maybe_unused) {
+ BUFFER *wb = w->response.data;
+ buffer_reset(wb);
+ buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
+
+ const char *auth;
+ switch(web_client_flag_check(w, WEB_CLIENT_FLAG_AUTH_CLOUD|WEB_CLIENT_FLAG_AUTH_BEARER|WEB_CLIENT_FLAG_AUTH_GOD)) {
+ case WEB_CLIENT_FLAG_AUTH_CLOUD:
+ auth = "cloud";
+ break;
+
+ case WEB_CLIENT_FLAG_AUTH_BEARER:
+ auth = "bearer";
+ break;
+
+ case WEB_CLIENT_FLAG_AUTH_GOD:
+ auth = "god";
+ break;
+
+ default:
+ auth = "none";
+ break;
+ }
+ buffer_json_member_add_string(wb, "auth", auth);
+
+ buffer_json_member_add_uuid(wb, "cloud_account_id", w->auth.cloud_account_id);
+ buffer_json_member_add_string(wb, "client_name", w->auth.client_name);
+ http_access2buffer_json_array(wb, "access", w->access);
+ buffer_json_member_add_string(wb, "user_role", http_id2user_role(w->user_role));
+
+ buffer_json_finalize(wb);
+ return HTTP_RESP_OK;
+}
diff --git a/src/web/api/v3/api_v3_settings.c b/src/web/api/v3/api_v3_settings.c
new file mode 100644
index 00000000..3b02e6b6
--- /dev/null
+++ b/src/web/api/v3/api_v3_settings.c
@@ -0,0 +1,285 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+/*
+ * /api/v3/settings
+ *
+ * QUERY STRING PARAMETERS:
+ * - file=a file name (alphanumerics, dashes, underscores)
+ * When the user is not authenticated with a bearer token
+ * only the 'default' file is allowed.
+ * Authenticated users can create, store and update any
+ * settings file.
+ *
+ * HTTP METHODS
+ * - GET to retrieve a file
+ * - PUT to create or update a file
+ *
+ * PAYLOAD
+ * - The payload MUST have the member 'version'.
+ * - The payload MAY have anything else.
+ * - The maximum payload size in JSON is 20MiB.
+ * - When updating the payload, the caller must specify the
+ * version of the existing file. If this check fails,
+ * Netdata will return 409 (conflict).
+ * When the caller receives 409, it means there are updates
+ * in the payload outside its control and the object MUST
+ * be loaded again to find its current version to update it.
+ * After loading it, the caller must reapply the changes and
+ * PUT it again.
+ * - Netdata will increase the version on every PUT action.
+ * So, the payload MUST specify the version found on disk
+ * but, Netdata will increment the version before saving it.
+ */
+
+#include "api_v3_calls.h"
+
+#define MAX_SETTINGS_SIZE_BYTES (20 * 1024 * 1024)
+
+// we need an r/w spinlock to ensure that reads and write do not happen
+// concurrently for settings files
+static RW_SPINLOCK settings_spinlock = NETDATA_RW_SPINLOCK_INITIALIZER;
+
+static inline void settings_path(char out[FILENAME_MAX]) {
+ filename_from_path_entry(out, netdata_configured_varlib_dir, "settings", NULL);
+}
+
+static inline void settings_filename(char out[FILENAME_MAX], const char *file, const char *extension) {
+ char path[FILENAME_MAX];
+ settings_path(path);
+ filename_from_path_entry(out, path, file, extension);
+}
+
+static inline bool settings_ensure_path_exists(void) {
+ char path[FILENAME_MAX];
+ settings_path(path);
+ return filename_is_dir(path, true);
+}
+
+static inline size_t settings_extract_json_version(const char *json) {
+ if(!json || !*json) return 0;
+
+ // Parse the JSON string into a JSON-C object
+ CLEAN_JSON_OBJECT *jobj = json_tokener_parse(json);
+ if (jobj == NULL)
+ return 0;
+
+ // Access the "version" field
+ struct json_object *version_obj;
+ if (json_object_object_get_ex(jobj, "version", &version_obj))
+ // Extract the integer value of the version
+ return (size_t)json_object_get_int(version_obj);
+
+ return 0;
+}
+
+static inline void settings_initial_version(BUFFER *wb) {
+ buffer_reset(wb);
+ buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
+ buffer_json_member_add_uint64(wb, "version", 1);
+ buffer_json_finalize(wb);
+}
+
+static inline void settings_get(BUFFER *wb, const char *file, bool have_lock) {
+ char filename[FILENAME_MAX];
+ settings_filename(filename, file, NULL);
+
+ buffer_reset(wb);
+
+ if(!have_lock)
+ rw_spinlock_read_lock(&settings_spinlock);
+
+ bool rc = read_txt_file_to_buffer(filename, wb, MAX_SETTINGS_SIZE_BYTES);
+
+ if(!have_lock)
+ rw_spinlock_read_unlock(&settings_spinlock);
+
+ if(rc) {
+ size_t version = settings_extract_json_version(buffer_tostring(wb));
+ if (!version) {
+ nd_log(NDLS_DAEMON, NDLP_ERR, "file '%s' cannot be parsed to extract version", filename);
+ settings_initial_version(wb);
+ }
+ else {
+ wb->content_type = CT_APPLICATION_JSON;
+ buffer_no_cacheable(wb);
+ }
+ }
+ else
+ settings_initial_version(wb);
+}
+
+static inline size_t settings_get_version(const char *path, bool have_lock) {
+ CLEAN_BUFFER *wb = buffer_create(0, NULL);
+ settings_get(wb, path, have_lock);
+
+ return settings_extract_json_version(buffer_tostring(wb));
+}
+
+static inline int settings_put(struct web_client *w, char *file) {
+ rw_spinlock_write_lock(&settings_spinlock);
+
+ if(!settings_ensure_path_exists()) {
+ rw_spinlock_write_unlock(&settings_spinlock);
+ return rrd_call_function_error(
+ w->response.data,
+ "Settings path cannot be created or accessed.",
+ HTTP_RESP_BAD_REQUEST);
+ }
+
+ size_t old_version = settings_get_version(file, true);
+
+ // Parse the JSON string into a JSON-C object
+ CLEAN_JSON_OBJECT *jobj = json_tokener_parse(buffer_tostring(w->payload));
+ if (jobj == NULL) {
+ rw_spinlock_write_unlock(&settings_spinlock);
+ return rrd_call_function_error(
+ w->response.data,
+ "Payload cannot be parsed as a JSON object",
+ HTTP_RESP_BAD_REQUEST);
+ }
+
+ // Access the "version" field
+ struct json_object *version_obj;
+ if (!json_object_object_get_ex(jobj, "version", &version_obj)) {
+ rw_spinlock_write_unlock(&settings_spinlock);
+ return rrd_call_function_error(
+ w->response.data,
+ "Field version is not found in payload",
+ HTTP_RESP_BAD_REQUEST);
+ }
+
+ size_t new_version = (size_t)json_object_get_int(version_obj);
+
+ if (old_version != new_version) {
+ rw_spinlock_write_unlock(&settings_spinlock);
+ return rrd_call_function_error(
+ w->response.data,
+ "Payload version does not match the version of the stored object",
+ HTTP_RESP_CONFLICT);
+ }
+
+ new_version++;
+ // Set the new version back into the JSON object
+ json_object_object_add(jobj, "version", json_object_new_int((int)new_version));
+
+ // Convert the updated JSON object back to a string
+ const char *updated_json_str = json_object_to_json_string(jobj);
+
+ char tmp_filename[FILENAME_MAX];
+ settings_filename(tmp_filename, file, "new");
+
+ // Save the updated JSON string to a file
+ FILE *fp = fopen(tmp_filename, "w");
+ if (fp == NULL) {
+ rw_spinlock_write_unlock(&settings_spinlock);
+ nd_log(NDLS_DAEMON, NDLP_ERR, "cannot open/create settings file '%s'", tmp_filename);
+ return rrd_call_function_error(
+ w->response.data,
+ "Cannot create payload file '%s'",
+ HTTP_RESP_INTERNAL_SERVER_ERROR);
+ }
+ size_t len = strlen(updated_json_str);
+ if(fwrite(updated_json_str, 1, len, fp) != len) {
+ fclose(fp);
+ unlink(tmp_filename);
+ rw_spinlock_write_unlock(&settings_spinlock);
+ nd_log(NDLS_DAEMON, NDLP_ERR, "cannot save settings to file '%s'", tmp_filename);
+ return rrd_call_function_error(
+ w->response.data,
+ "Cannot save payload to file '%s'",
+ HTTP_RESP_INTERNAL_SERVER_ERROR);
+ }
+ fclose(fp);
+
+ char filename[FILENAME_MAX];
+ settings_filename(filename, file, NULL);
+
+ bool renamed = rename(tmp_filename, filename) == 0;
+
+ rw_spinlock_write_unlock(&settings_spinlock);
+
+ if(!renamed) {
+ nd_log(NDLS_DAEMON, NDLP_ERR, "cannot rename file '%s' to '%s'", tmp_filename, filename);
+ return rrd_call_function_error(
+ w->response.data,
+ "Failed to move the payload file to its final location",
+ HTTP_RESP_INTERNAL_SERVER_ERROR);
+ }
+
+ return rrd_call_function_error(
+ w->response.data,
+ "OK",
+ HTTP_RESP_OK);
+}
+
+static inline bool is_settings_file_valid(char *file) {
+ char *s = file;
+
+ if(!s || !*s)
+ return false;
+
+ while(*s) {
+ if(!isalnum((uint8_t)*s) && *s != '-' && *s != '_')
+ return false;
+ s++;
+ }
+
+ return true;
+}
+
+int api_v3_settings(RRDHOST *host, struct web_client *w, char *url) {
+ char *file = NULL;
+
+ while(url) {
+ char *value = strsep_skip_consecutive_separators(&url, "&");
+ if(!value || !*value) continue;
+
+ char *name = strsep_skip_consecutive_separators(&value, "=");
+ if(!name || !*name) continue;
+ if(!value || !*value) continue;
+
+ // name and value are now the parameters
+ // they are not null and not empty
+
+ if(!strcmp(name, "file"))
+ file = value;
+ }
+
+ if(!is_settings_file_valid(file))
+ return rrd_call_function_error(
+ w->response.data,
+ "Invalid settings file given.",
+ HTTP_RESP_BAD_REQUEST);
+
+ if(host != localhost)
+ return rrd_call_function_error(
+ w->response.data,
+ "Settings API is only allowed for the agent node.",
+ HTTP_RESP_BAD_REQUEST);
+
+ if(web_client_flags_check_auth(w) != WEB_CLIENT_FLAG_AUTH_BEARER && strcmp(file, "default") != 0)
+ return rrd_call_function_error(
+ w->response.data,
+ "Only the 'default' settings file is allowed for anonymous users",
+ HTTP_RESP_BAD_REQUEST);
+
+ switch(w->mode) {
+ case HTTP_REQUEST_MODE_GET:
+ settings_get(w->response.data, file, false);
+ return HTTP_RESP_OK;
+
+ case HTTP_REQUEST_MODE_PUT:
+ if(!w->payload || !buffer_strlen(w->payload))
+ return rrd_call_function_error(
+ w->response.data,
+ "Settings API PUT action requires a payload.",
+ HTTP_RESP_BAD_REQUEST);
+
+ return settings_put(w, file);
+
+ default:
+ return rrd_call_function_error(w->response.data,
+ "Invalid HTTP mode. HTTP modes GET and PUT are supported.",
+ HTTP_RESP_BAD_REQUEST);
+ }
+}
diff --git a/src/web/api/web_api.c b/src/web/api/web_api.c
index 4e936be5..88be32a4 100644
--- a/src/web/api/web_api.c
+++ b/src/web/api/web_api.c
@@ -2,6 +2,12 @@
#include "web_api.h"
+void host_labels2json(RRDHOST *host, BUFFER *wb, const char *key) {
+ buffer_json_member_add_object(wb, key);
+ rrdlabels_to_buffer_json_members(host->rrdlabels, wb);
+ buffer_json_object_close(wb);
+}
+
int web_client_api_request_vX(RRDHOST *host, struct web_client *w, char *url_path_endpoint, struct web_api_command *api_commands) {
buffer_no_cacheable(w->response.data);
@@ -11,15 +17,16 @@ int web_client_api_request_vX(RRDHOST *host, struct web_client *w, char *url_pat
internal_fatal(!web_client_flags_check_auth(w) && (w->access & HTTP_ACCESS_SIGNED_ID),
"signed-in permission is set, but it shouldn't");
+#ifdef NETDATA_GOD_MODE
+ web_client_set_permissions(w, HTTP_ACCESS_ALL, HTTP_USER_ROLE_ADMIN, WEB_CLIENT_FLAG_AUTH_GOD);
+#else
if(!web_client_flags_check_auth(w)) {
- w->user_role = (netdata_is_protected_by_bearer) ? HTTP_USER_ROLE_NONE : HTTP_USER_ROLE_ANY;
- w->access = (netdata_is_protected_by_bearer) ? HTTP_ACCESS_NONE : HTTP_ACCESS_ANONYMOUS_DATA;
+ web_client_set_permissions(
+ w,
+ (netdata_is_protected_by_bearer) ? HTTP_ACCESS_NONE : HTTP_ACCESS_ANONYMOUS_DATA,
+ (netdata_is_protected_by_bearer) ? HTTP_USER_ROLE_NONE : HTTP_USER_ROLE_ANY,
+ 0);
}
-
-#ifdef NETDATA_GOD_MODE
- web_client_flag_set(w, WEB_CLIENT_FLAG_AUTH_GOD);
- w->user_role = HTTP_USER_ROLE_ADMIN;
- w->access = HTTP_ACCESS_ALL;
#endif
if(unlikely(!url_path_endpoint || !*url_path_endpoint)) {
@@ -110,156 +117,66 @@ RRDCONTEXT_TO_JSON_OPTIONS rrdcontext_to_json_parse_options(char *o) {
return options;
}
-int web_client_api_request_weights(RRDHOST *host, struct web_client *w, char *url, WEIGHTS_METHOD method, WEIGHTS_FORMAT format, size_t api_version) {
- if (!netdata_ready)
- return HTTP_RESP_SERVICE_UNAVAILABLE;
-
- time_t baseline_after = 0, baseline_before = 0, after = 0, before = 0;
- size_t points = 0;
- RRDR_OPTIONS options = 0;
- RRDR_TIME_GROUPING time_group_method = RRDR_GROUPING_AVERAGE;
- time_t timeout_ms = 0;
- size_t tier = 0;
- const char *time_group_options = NULL, *scope_contexts = NULL, *scope_nodes = NULL, *contexts = NULL, *nodes = NULL,
- *instances = NULL, *dimensions = NULL, *labels = NULL, *alerts = NULL;
-
- struct group_by_pass group_by = {
- .group_by = RRDR_GROUP_BY_NONE,
- .group_by_label = NULL,
- .aggregation = RRDR_GROUP_BY_FUNCTION_AVERAGE,
- };
-
- while (url) {
- char *value = strsep_skip_consecutive_separators(&url, "&");
- if (!value || !*value)
- continue;
-
- char *name = strsep_skip_consecutive_separators(&value, "=");
- if (!name || !*name)
- continue;
- if (!value || !*value)
- continue;
-
- if (!strcmp(name, "baseline_after"))
- baseline_after = str2l(value);
-
- else if (!strcmp(name, "baseline_before"))
- baseline_before = str2l(value);
-
- else if (!strcmp(name, "after") || !strcmp(name, "highlight_after"))
- after = str2l(value);
-
- else if (!strcmp(name, "before") || !strcmp(name, "highlight_before"))
- before = str2l(value);
-
- else if (!strcmp(name, "points") || !strcmp(name, "max_points"))
- points = str2ul(value);
-
- else if (!strcmp(name, "timeout"))
- timeout_ms = str2l(value);
-
- else if((api_version == 1 && !strcmp(name, "group")) || (api_version >= 2 && !strcmp(name, "time_group")))
- time_group_method = time_grouping_parse(value, RRDR_GROUPING_AVERAGE);
-
- else if((api_version == 1 && !strcmp(name, "group_options")) || (api_version >= 2 && !strcmp(name, "time_group_options")))
- time_group_options = value;
-
- else if(!strcmp(name, "options"))
- options |= rrdr_options_parse(value);
-
- else if(!strcmp(name, "method"))
- method = weights_string_to_method(value);
-
- else if(api_version == 1 && (!strcmp(name, "context") || !strcmp(name, "contexts")))
- scope_contexts = value;
-
- else if(api_version >= 2 && !strcmp(name, "scope_nodes")) scope_nodes = value;
- else if(api_version >= 2 && !strcmp(name, "scope_contexts")) scope_contexts = value;
- else if(api_version >= 2 && !strcmp(name, "nodes")) nodes = value;
- else if(api_version >= 2 && !strcmp(name, "contexts")) contexts = value;
- else if(api_version >= 2 && !strcmp(name, "instances")) instances = value;
- else if(api_version >= 2 && !strcmp(name, "dimensions")) dimensions = value;
- else if(api_version >= 2 && !strcmp(name, "labels")) labels = value;
- else if(api_version >= 2 && !strcmp(name, "alerts")) alerts = value;
- else if(api_version >= 2 && (!strcmp(name, "group_by") || !strcmp(name, "group_by[0]"))) {
- group_by.group_by = group_by_parse(value);
- }
- else if(api_version >= 2 && (!strcmp(name, "group_by_label") || !strcmp(name, "group_by_label[0]"))) {
- group_by.group_by_label = value;
- }
- else if(api_version >= 2 && (!strcmp(name, "aggregation") || !strcmp(name, "aggregation[0]"))) {
- group_by.aggregation = group_by_aggregate_function_parse(value);
- }
- else if(!strcmp(name, "tier")) {
- tier = str2ul(value);
- if(tier < storage_tiers)
- options |= RRDR_OPTION_SELECTED_TIER;
- else
- tier = 0;
- }
- }
+bool web_client_interrupt_callback(void *data) {
+ struct web_client *w = data;
- if(options == 0)
- // the user did not set any options
- options = RRDR_OPTION_NOT_ALIGNED | RRDR_OPTION_NULL2ZERO | RRDR_OPTION_NONZERO;
+ bool ret;
+ if(w->interrupt.callback)
+ ret = w->interrupt.callback(w, w->interrupt.callback_data);
else
- // the user set some options, add also these
- options |= RRDR_OPTION_NOT_ALIGNED | RRDR_OPTION_NULL2ZERO;
-
- if(options & RRDR_OPTION_PERCENTAGE)
- options |= RRDR_OPTION_ABSOLUTE;
-
- if(options & RRDR_OPTION_DEBUG)
- options &= ~RRDR_OPTION_MINIFY;
-
- BUFFER *wb = w->response.data;
- buffer_flush(wb);
- wb->content_type = CT_APPLICATION_JSON;
-
- QUERY_WEIGHTS_REQUEST qwr = {
- .version = api_version,
- .host = (api_version == 1) ? NULL : host,
- .scope_nodes = scope_nodes,
- .scope_contexts = scope_contexts,
- .nodes = nodes,
- .contexts = contexts,
- .instances = instances,
- .dimensions = dimensions,
- .labels = labels,
- .alerts = alerts,
- .group_by = {
- .group_by = group_by.group_by,
- .group_by_label = group_by.group_by_label,
- .aggregation = group_by.aggregation,
- },
- .method = method,
- .format = format,
- .time_group_method = time_group_method,
- .time_group_options = time_group_options,
- .baseline_after = baseline_after,
- .baseline_before = baseline_before,
- .after = after,
- .before = before,
- .points = points,
- .options = options,
- .tier = tier,
- .timeout_ms = timeout_ms,
-
- .interrupt_callback = web_client_interrupt_callback,
- .interrupt_callback_data = w,
-
- .transaction = &w->transaction,
- };
-
- return web_api_v12_weights(wb, &qwr);
+ ret = is_socket_closed(w->ofd);
+
+ return ret;
}
-bool web_client_interrupt_callback(void *data) {
- struct web_client *w = data;
+void nd_web_api_init(void) {
+ contexts_alert_statuses_init();
+ rrdr_options_init();
+ contexts_options_init();
+ datasource_formats_init();
+ time_grouping_init();
+}
- if(w->interrupt.callback)
- return w->interrupt.callback(w, w->interrupt.callback_data);
- return sock_has_output_error(w->ofd);
+bool request_source_is_cloud(const char *source) {
+ return source && *source && strstartswith(source, "method=NC,");
+}
+
+void web_client_api_request_vX_source_to_buffer(struct web_client *w, BUFFER *source) {
+ if(web_client_flag_check(w, WEB_CLIENT_FLAG_AUTH_CLOUD))
+ buffer_sprintf(source, "method=NC");
+ else if(web_client_flag_check(w, WEB_CLIENT_FLAG_AUTH_BEARER))
+ buffer_sprintf(source, "method=api-bearer");
+ else
+ buffer_sprintf(source, "method=api");
+
+ if(web_client_flag_check(w, WEB_CLIENT_FLAG_AUTH_GOD))
+ buffer_strcat(source, ",role=god");
+ else
+ buffer_sprintf(source, ",role=%s", http_id2user_role(w->user_role));
+
+ buffer_sprintf(source, ",permissions="HTTP_ACCESS_FORMAT, (HTTP_ACCESS_FORMAT_CAST)w->access);
+
+ if(w->auth.client_name[0])
+ buffer_sprintf(source, ",user=%s", w->auth.client_name);
+
+ if(!uuid_is_null(w->auth.cloud_account_id)) {
+ char uuid_str[UUID_COMPACT_STR_LEN];
+ uuid_unparse_lower_compact(w->auth.cloud_account_id, uuid_str);
+ buffer_sprintf(source, ",account=%s", uuid_str);
+ }
+
+ if(w->client_ip[0])
+ buffer_sprintf(source, ",ip=%s", w->client_ip);
+
+ if(w->forwarded_for)
+ buffer_sprintf(source, ",forwarded_for=%s", w->forwarded_for);
}
+
+void web_client_progress_functions_update(void *data, size_t done, size_t all) {
+ // handle progress updates from the plugin
+ struct web_client *w = data;
+ query_progress_functions_update(&w->transaction, done, all);
+}
+
diff --git a/src/web/api/web_api.h b/src/web/api/web_api.h
index 634e5965..cb694a33 100644
--- a/src/web/api/web_api.h
+++ b/src/web/api/web_api.h
@@ -3,14 +3,28 @@
#ifndef NETDATA_WEB_API_H
#define NETDATA_WEB_API_H 1
+#define ENABLE_API_V1 1
+#define ENABLE_API_v2 1
+
+struct web_client;
+
#include "daemon/common.h"
+#include "maps/maps.h"
+#include "functions/functions.h"
+
#include "web/api/http_header.h"
#include "web/api/http_auth.h"
-#include "web/api/badges/web_buffer_svg.h"
-#include "web/api/ilove/ilove.h"
#include "web/api/formatters/rrd2json.h"
#include "web/api/queries/weights.h"
+void nd_web_api_init(void);
+
+bool request_source_is_cloud(const char *source);
+void web_client_api_request_vX_source_to_buffer(struct web_client *w, BUFFER *source);
+void web_client_progress_functions_update(void *data, size_t done, size_t all);
+
+void host_labels2json(RRDHOST *host, BUFFER *wb, const char *key);
+
struct web_api_command {
const char *api;
uint32_t hash;
@@ -37,7 +51,11 @@ int web_client_api_request_weights(RRDHOST *host, struct web_client *w, char *ur
bool web_client_interrupt_callback(void *data);
+char *format_value_and_unit(char *value_string, size_t value_string_len,
+ NETDATA_DOUBLE value, const char *units, int precision);
+
#include "web_api_v1.h"
#include "web_api_v2.h"
+#include "web_api_v3.h"
#endif //NETDATA_WEB_API_H
diff --git a/src/web/api/web_api_v1.c b/src/web/api/web_api_v1.c
index bfaa4f6f..8d38e01d 100644
--- a/src/web/api/web_api_v1.c
+++ b/src/web/api/web_api_v1.c
@@ -1,1958 +1,232 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#include "web_api_v1.h"
-
-char *api_secret;
-
-static struct {
- const char *name;
- uint32_t hash;
- RRDR_OPTIONS value;
-} rrdr_options[] = {
- { "nonzero" , 0 , RRDR_OPTION_NONZERO}
- , {"flip" , 0 , RRDR_OPTION_REVERSED}
- , {"reversed" , 0 , RRDR_OPTION_REVERSED}
- , {"reverse" , 0 , RRDR_OPTION_REVERSED}
- , {"jsonwrap" , 0 , RRDR_OPTION_JSON_WRAP}
- , {"min2max" , 0 , RRDR_OPTION_DIMS_MIN2MAX} // rrdr2value() only
- , {"average" , 0 , RRDR_OPTION_DIMS_AVERAGE} // rrdr2value() only
- , {"min" , 0 , RRDR_OPTION_DIMS_MIN} // rrdr2value() only
- , {"max" , 0 , RRDR_OPTION_DIMS_MAX} // rrdr2value() only
- , {"ms" , 0 , RRDR_OPTION_MILLISECONDS}
- , {"milliseconds" , 0 , RRDR_OPTION_MILLISECONDS}
- , {"absolute" , 0 , RRDR_OPTION_ABSOLUTE}
- , {"abs" , 0 , RRDR_OPTION_ABSOLUTE}
- , {"absolute_sum" , 0 , RRDR_OPTION_ABSOLUTE}
- , {"absolute-sum" , 0 , RRDR_OPTION_ABSOLUTE}
- , {"display_absolute" , 0 , RRDR_OPTION_DISPLAY_ABS}
- , {"display-absolute" , 0 , RRDR_OPTION_DISPLAY_ABS}
- , {"seconds" , 0 , RRDR_OPTION_SECONDS}
- , {"null2zero" , 0 , RRDR_OPTION_NULL2ZERO}
- , {"objectrows" , 0 , RRDR_OPTION_OBJECTSROWS}
- , {"google_json" , 0 , RRDR_OPTION_GOOGLE_JSON}
- , {"google-json" , 0 , RRDR_OPTION_GOOGLE_JSON}
- , {"percentage" , 0 , RRDR_OPTION_PERCENTAGE}
- , {"unaligned" , 0 , RRDR_OPTION_NOT_ALIGNED}
- , {"match_ids" , 0 , RRDR_OPTION_MATCH_IDS}
- , {"match-ids" , 0 , RRDR_OPTION_MATCH_IDS}
- , {"match_names" , 0 , RRDR_OPTION_MATCH_NAMES}
- , {"match-names" , 0 , RRDR_OPTION_MATCH_NAMES}
- , {"anomaly-bit" , 0 , RRDR_OPTION_ANOMALY_BIT}
- , {"selected-tier" , 0 , RRDR_OPTION_SELECTED_TIER}
- , {"raw" , 0 , RRDR_OPTION_RETURN_RAW}
- , {"jw-anomaly-rates" , 0 , RRDR_OPTION_RETURN_JWAR}
- , {"natural-points" , 0 , RRDR_OPTION_NATURAL_POINTS}
- , {"virtual-points" , 0 , RRDR_OPTION_VIRTUAL_POINTS}
- , {"all-dimensions" , 0 , RRDR_OPTION_ALL_DIMENSIONS}
- , {"details" , 0 , RRDR_OPTION_SHOW_DETAILS}
- , {"debug" , 0 , RRDR_OPTION_DEBUG}
- , {"plan" , 0 , RRDR_OPTION_DEBUG}
- , {"minify" , 0 , RRDR_OPTION_MINIFY}
- , {"group-by-labels" , 0 , RRDR_OPTION_GROUP_BY_LABELS}
- , {"label-quotes" , 0 , RRDR_OPTION_LABEL_QUOTES}
- , {NULL , 0 , 0}
-};
-
-static struct {
- const char *name;
- uint32_t hash;
- CONTEXTS_V2_OPTIONS value;
-} contexts_v2_options[] = {
- {"minify" , 0 , CONTEXT_V2_OPTION_MINIFY}
- , {"debug" , 0 , CONTEXT_V2_OPTION_DEBUG}
- , {"config" , 0 , CONTEXT_V2_OPTION_ALERTS_WITH_CONFIGURATIONS}
- , {"instances" , 0 , CONTEXT_V2_OPTION_ALERTS_WITH_INSTANCES}
- , {"values" , 0 , CONTEXT_V2_OPTION_ALERTS_WITH_VALUES}
- , {"summary" , 0 , CONTEXT_V2_OPTION_ALERTS_WITH_SUMMARY}
- , {NULL , 0 , 0}
-};
-
-static struct {
- const char *name;
- uint32_t hash;
- CONTEXTS_V2_ALERT_STATUS value;
-} contexts_v2_alert_status[] = {
- {"uninitialized" , 0 , CONTEXT_V2_ALERT_UNINITIALIZED}
- , {"undefined" , 0 , CONTEXT_V2_ALERT_UNDEFINED}
- , {"clear" , 0 , CONTEXT_V2_ALERT_CLEAR}
- , {"raised" , 0 , CONTEXT_V2_ALERT_RAISED}
- , {"active" , 0 , CONTEXT_V2_ALERT_RAISED}
- , {"warning" , 0 , CONTEXT_V2_ALERT_WARNING}
- , {"critical" , 0 , CONTEXT_V2_ALERT_CRITICAL}
- , {NULL , 0 , 0}
-};
-
-static struct {
- const char *name;
- uint32_t hash;
- DATASOURCE_FORMAT value;
-} api_v1_data_formats[] = {
- { DATASOURCE_FORMAT_DATATABLE_JSON , 0 , DATASOURCE_DATATABLE_JSON}
- , {DATASOURCE_FORMAT_DATATABLE_JSONP, 0 , DATASOURCE_DATATABLE_JSONP}
- , {DATASOURCE_FORMAT_JSON , 0 , DATASOURCE_JSON}
- , {DATASOURCE_FORMAT_JSON2 , 0 , DATASOURCE_JSON2}
- , {DATASOURCE_FORMAT_JSONP , 0 , DATASOURCE_JSONP}
- , {DATASOURCE_FORMAT_SSV , 0 , DATASOURCE_SSV}
- , {DATASOURCE_FORMAT_CSV , 0 , DATASOURCE_CSV}
- , {DATASOURCE_FORMAT_TSV , 0 , DATASOURCE_TSV}
- , {"tsv-excel" , 0 , DATASOURCE_TSV}
- , {DATASOURCE_FORMAT_HTML , 0 , DATASOURCE_HTML}
- , {DATASOURCE_FORMAT_JS_ARRAY , 0 , DATASOURCE_JS_ARRAY}
- , {DATASOURCE_FORMAT_SSV_COMMA , 0 , DATASOURCE_SSV_COMMA}
- , {DATASOURCE_FORMAT_CSV_JSON_ARRAY , 0 , DATASOURCE_CSV_JSON_ARRAY}
- , {DATASOURCE_FORMAT_CSV_MARKDOWN , 0 , DATASOURCE_CSV_MARKDOWN}
-
- // terminator
- , {NULL, 0, 0}
-};
-
-static struct {
- const char *name;
- uint32_t hash;
- DATASOURCE_FORMAT value;
-} api_v1_data_google_formats[] = {
- // this is not an error - when Google requests json, it expects javascript
- // https://developers.google.com/chart/interactive/docs/dev/implementing_data_source#responseformat
- {"json", 0, DATASOURCE_DATATABLE_JSONP}
- , {"html", 0, DATASOURCE_HTML}
- , {"csv", 0, DATASOURCE_CSV}
- , {"tsv-excel", 0, DATASOURCE_TSV}
-
- // terminator
- , {NULL, 0, 0}
-};
-
-void web_client_api_v1_init(void) {
- int i;
-
- for(i = 0; contexts_v2_alert_status[i].name ; i++)
- contexts_v2_alert_status[i].hash = simple_hash(contexts_v2_alert_status[i].name);
-
- for(i = 0; rrdr_options[i].name ; i++)
- rrdr_options[i].hash = simple_hash(rrdr_options[i].name);
-
- for(i = 0; contexts_v2_options[i].name ; i++)
- contexts_v2_options[i].hash = simple_hash(contexts_v2_options[i].name);
-
- for(i = 0; api_v1_data_formats[i].name ; i++)
- api_v1_data_formats[i].hash = simple_hash(api_v1_data_formats[i].name);
-
- for(i = 0; api_v1_data_google_formats[i].name ; i++)
- api_v1_data_google_formats[i].hash = simple_hash(api_v1_data_google_formats[i].name);
-
- time_grouping_init();
-
- nd_uuid_t uuid;
-
- // generate
- uuid_generate(uuid);
-
- // unparse (to string)
- char uuid_str[37];
- uuid_unparse_lower(uuid, uuid_str);
-}
-
-char *get_mgmt_api_key(void) {
- char filename[FILENAME_MAX + 1];
- snprintfz(filename, FILENAME_MAX, "%s/netdata.api.key", netdata_configured_varlib_dir);
- char *api_key_filename=config_get(CONFIG_SECTION_REGISTRY, "netdata management api key file", filename);
- static char guid[GUID_LEN + 1] = "";
-
- if(likely(guid[0]))
- return guid;
-
- // read it from disk
- int fd = open(api_key_filename, O_RDONLY | O_CLOEXEC);
- if(fd != -1) {
- char buf[GUID_LEN + 1];
- if(read(fd, buf, GUID_LEN) != GUID_LEN)
- netdata_log_error("Failed to read management API key from '%s'", api_key_filename);
- else {
- buf[GUID_LEN] = '\0';
- if(regenerate_guid(buf, guid) == -1) {
- netdata_log_error("Failed to validate management API key '%s' from '%s'.",
- buf, api_key_filename);
-
- guid[0] = '\0';
- }
- }
- close(fd);
- }
-
- // generate a new one?
- if(!guid[0]) {
- nd_uuid_t uuid;
-
- uuid_generate_time(uuid);
- uuid_unparse_lower(uuid, guid);
- guid[GUID_LEN] = '\0';
-
- // save it
- fd = open(api_key_filename, O_WRONLY|O_CREAT|O_TRUNC | O_CLOEXEC, 444);
- if(fd == -1) {
- netdata_log_error("Cannot create unique management API key file '%s'. Please adjust config parameter 'netdata management api key file' to a proper path and file.", api_key_filename);
- goto temp_key;
- }
-
- if(write(fd, guid, GUID_LEN) != GUID_LEN) {
- netdata_log_error("Cannot write the unique management API key file '%s'. Please adjust config parameter 'netdata management api key file' to a proper path and file with enough space left.", api_key_filename);
- close(fd);
- goto temp_key;
- }
-
- close(fd);
- }
-
- return guid;
-
-temp_key:
- netdata_log_info("You can still continue to use the alarm management API using the authorization token %s during this Netdata session only.", guid);
- return guid;
-}
-
-void web_client_api_v1_management_init(void) {
- api_secret = get_mgmt_api_key();
-}
-
-inline RRDR_OPTIONS rrdr_options_parse_one(const char *o) {
- RRDR_OPTIONS ret = 0;
-
- if(!o || !*o) return ret;
-
- uint32_t hash = simple_hash(o);
- int i;
- for(i = 0; rrdr_options[i].name ; i++) {
- if (unlikely(hash == rrdr_options[i].hash && !strcmp(o, rrdr_options[i].name))) {
- ret |= rrdr_options[i].value;
- break;
- }
- }
-
- return ret;
-}
-
-inline RRDR_OPTIONS rrdr_options_parse(char *o) {
- RRDR_OPTIONS ret = 0;
- char *tok;
-
- while(o && *o && (tok = strsep_skip_consecutive_separators(&o, ", |"))) {
- if(!*tok) continue;
- ret |= rrdr_options_parse_one(tok);
- }
-
- return ret;
-}
-
-inline CONTEXTS_V2_OPTIONS web_client_api_request_v2_context_options(char *o) {
- CONTEXTS_V2_OPTIONS ret = 0;
- char *tok;
-
- while(o && *o && (tok = strsep_skip_consecutive_separators(&o, ", |"))) {
- if(!*tok) continue;
-
- uint32_t hash = simple_hash(tok);
- int i;
- for(i = 0; contexts_v2_options[i].name ; i++) {
- if (unlikely(hash == contexts_v2_options[i].hash && !strcmp(tok, contexts_v2_options[i].name))) {
- ret |= contexts_v2_options[i].value;
- break;
- }
- }
- }
-
- return ret;
-}
-
-inline CONTEXTS_V2_ALERT_STATUS web_client_api_request_v2_alert_status(char *o) {
- CONTEXTS_V2_ALERT_STATUS ret = 0;
- char *tok;
-
- while(o && *o && (tok = strsep_skip_consecutive_separators(&o, ", |"))) {
- if(!*tok) continue;
-
- uint32_t hash = simple_hash(tok);
- int i;
- for(i = 0; contexts_v2_alert_status[i].name ; i++) {
- if (unlikely(hash == contexts_v2_alert_status[i].hash && !strcmp(tok, contexts_v2_alert_status[i].name))) {
- ret |= contexts_v2_alert_status[i].value;
- break;
- }
- }
- }
-
- return ret;
-}
-
-void web_client_api_request_v2_contexts_alerts_status_to_buffer_json_array(BUFFER *wb, const char *key, CONTEXTS_V2_ALERT_STATUS options) {
- buffer_json_member_add_array(wb, key);
-
- RRDR_OPTIONS used = 0; // to prevent adding duplicates
- for(int i = 0; contexts_v2_alert_status[i].name ; i++) {
- if (unlikely((contexts_v2_alert_status[i].value & options) && !(contexts_v2_alert_status[i].value & used))) {
- const char *name = contexts_v2_alert_status[i].name;
- used |= contexts_v2_alert_status[i].value;
-
- buffer_json_add_array_item_string(wb, name);
- }
- }
-
- buffer_json_array_close(wb);
-}
-
-void web_client_api_request_v2_contexts_options_to_buffer_json_array(BUFFER *wb, const char *key, CONTEXTS_V2_OPTIONS options) {
- buffer_json_member_add_array(wb, key);
-
- RRDR_OPTIONS used = 0; // to prevent adding duplicates
- for(int i = 0; contexts_v2_options[i].name ; i++) {
- if (unlikely((contexts_v2_options[i].value & options) && !(contexts_v2_options[i].value & used))) {
- const char *name = contexts_v2_options[i].name;
- used |= contexts_v2_options[i].value;
-
- buffer_json_add_array_item_string(wb, name);
- }
- }
-
- buffer_json_array_close(wb);
-}
-
-void rrdr_options_to_buffer_json_array(BUFFER *wb, const char *key, RRDR_OPTIONS options) {
- buffer_json_member_add_array(wb, key);
-
- RRDR_OPTIONS used = 0; // to prevent adding duplicates
- for(int i = 0; rrdr_options[i].name ; i++) {
- if (unlikely((rrdr_options[i].value & options) && !(rrdr_options[i].value & used))) {
- const char *name = rrdr_options[i].name;
- used |= rrdr_options[i].value;
-
- buffer_json_add_array_item_string(wb, name);
- }
- }
-
- buffer_json_array_close(wb);
-}
-
-void rrdr_options_to_buffer(BUFFER *wb, RRDR_OPTIONS options) {
- RRDR_OPTIONS used = 0; // to prevent adding duplicates
- size_t added = 0;
- for(int i = 0; rrdr_options[i].name ; i++) {
- if (unlikely((rrdr_options[i].value & options) && !(rrdr_options[i].value & used))) {
- const char *name = rrdr_options[i].name;
- used |= rrdr_options[i].value;
-
- if(added++) buffer_strcat(wb, " ");
- buffer_strcat(wb, name);
- }
- }
-}
-
-void web_client_api_request_v1_data_options_to_string(char *buf, size_t size, RRDR_OPTIONS options) {
- char *write = buf;
- char *end = &buf[size - 1];
-
- RRDR_OPTIONS used = 0; // to prevent adding duplicates
- int added = 0;
- for(int i = 0; rrdr_options[i].name ; i++) {
- if (unlikely((rrdr_options[i].value & options) && !(rrdr_options[i].value & used))) {
- const char *name = rrdr_options[i].name;
- used |= rrdr_options[i].value;
-
- if(added && write < end)
- *write++ = ',';
-
- while(*name && write < end)
- *write++ = *name++;
-
- added++;
- }
- }
- *write = *end = '\0';
-}
-
-inline uint32_t web_client_api_request_v1_data_format(char *name) {
- uint32_t hash = simple_hash(name);
- int i;
-
- for(i = 0; api_v1_data_formats[i].name ; i++) {
- if (unlikely(hash == api_v1_data_formats[i].hash && !strcmp(name, api_v1_data_formats[i].name))) {
- return api_v1_data_formats[i].value;
- }
- }
-
- return DATASOURCE_JSON;
-}
-
-inline uint32_t web_client_api_request_v1_data_google_format(char *name) {
- uint32_t hash = simple_hash(name);
- int i;
-
- for(i = 0; api_v1_data_google_formats[i].name ; i++) {
- if (unlikely(hash == api_v1_data_google_formats[i].hash && !strcmp(name, api_v1_data_google_formats[i].name))) {
- return api_v1_data_google_formats[i].value;
- }
- }
-
- return DATASOURCE_JSON;
-}
-
-int web_client_api_request_v1_alarms_select (char *url) {
- int all = 0;
- while(url) {
- char *value = strsep_skip_consecutive_separators(&url, "&");
- if (!value || !*value) continue;
-
- if(!strcmp(value, "all") || !strcmp(value, "all=true")) all = 1;
- else if(!strcmp(value, "active") || !strcmp(value, "active=true")) all = 0;
- }
-
- return all;
-}
-
-inline int web_client_api_request_v1_alarms(RRDHOST *host, struct web_client *w, char *url) {
- int all = web_client_api_request_v1_alarms_select(url);
-
- buffer_flush(w->response.data);
- w->response.data->content_type = CT_APPLICATION_JSON;
- health_alarms2json(host, w->response.data, all);
- buffer_no_cacheable(w->response.data);
- return HTTP_RESP_OK;
-}
-
-inline int web_client_api_request_v1_alarms_values(RRDHOST *host, struct web_client *w, char *url) {
- int all = web_client_api_request_v1_alarms_select(url);
-
- buffer_flush(w->response.data);
- w->response.data->content_type = CT_APPLICATION_JSON;
- health_alarms_values2json(host, w->response.data, all);
- buffer_no_cacheable(w->response.data);
- return HTTP_RESP_OK;
-}
-
-inline int web_client_api_request_v1_alarm_count(RRDHOST *host, struct web_client *w, char *url) {
- RRDCALC_STATUS status = RRDCALC_STATUS_RAISED;
- BUFFER *contexts = NULL;
-
- buffer_flush(w->response.data);
- buffer_sprintf(w->response.data, "[");
-
- while(url) {
- char *value = strsep_skip_consecutive_separators(&url, "&");
- if(!value || !*value) continue;
-
- char *name = strsep_skip_consecutive_separators(&value, "=");
- if(!name || !*name) continue;
- if(!value || !*value) continue;
-
- netdata_log_debug(D_WEB_CLIENT, "%llu: API v1 alarm_count query param '%s' with value '%s'", w->id, name, value);
-
- char* p = value;
- if(!strcmp(name, "status")) {
- while ((*p = toupper(*p))) p++;
- if (!strcmp("CRITICAL", value)) status = RRDCALC_STATUS_CRITICAL;
- else if (!strcmp("WARNING", value)) status = RRDCALC_STATUS_WARNING;
- else if (!strcmp("UNINITIALIZED", value)) status = RRDCALC_STATUS_UNINITIALIZED;
- else if (!strcmp("UNDEFINED", value)) status = RRDCALC_STATUS_UNDEFINED;
- else if (!strcmp("REMOVED", value)) status = RRDCALC_STATUS_REMOVED;
- else if (!strcmp("CLEAR", value)) status = RRDCALC_STATUS_CLEAR;
- }
- else if(!strcmp(name, "context") || !strcmp(name, "ctx")) {
- if(!contexts) contexts = buffer_create(255, &netdata_buffers_statistics.buffers_api);
- buffer_strcat(contexts, "|");
- buffer_strcat(contexts, value);
- }
- }
-
- health_aggregate_alarms(host, w->response.data, contexts, status);
-
- buffer_sprintf(w->response.data, "]\n");
- w->response.data->content_type = CT_APPLICATION_JSON;
- buffer_no_cacheable(w->response.data);
-
- buffer_free(contexts);
- return 200;
-}
-
-inline int web_client_api_request_v1_alarm_log(RRDHOST *host, struct web_client *w, char *url) {
- time_t after = 0;
- char *chart = NULL;
-
- while(url) {
- char *value = strsep_skip_consecutive_separators(&url, "&");
- if (!value || !*value) continue;
-
- char *name = strsep_skip_consecutive_separators(&value, "=");
- if(!name || !*name) continue;
- if(!value || !*value) continue;
-
- if (!strcmp(name, "after")) after = (time_t) strtoul(value, NULL, 0);
- else if (!strcmp(name, "chart")) chart = value;
- }
-
- buffer_flush(w->response.data);
- w->response.data->content_type = CT_APPLICATION_JSON;
- sql_health_alarm_log2json(host, w->response.data, after, chart);
- return HTTP_RESP_OK;
-}
-
-inline int web_client_api_request_single_chart(RRDHOST *host, struct web_client *w, char *url, void callback(RRDSET *st, BUFFER *buf)) {
- int ret = HTTP_RESP_BAD_REQUEST;
- char *chart = NULL;
-
- buffer_flush(w->response.data);
-
- while(url) {
- char *value = strsep_skip_consecutive_separators(&url, "&");
- if(!value || !*value) continue;
-
- char *name = strsep_skip_consecutive_separators(&value, "=");
- if(!name || !*name) continue;
- if(!value || !*value) continue;
-
- // name and value are now the parameters
- // they are not null and not empty
-
- if(!strcmp(name, "chart")) chart = value;
- //else {
- /// buffer_sprintf(w->response.data, "Unknown parameter '%s' in request.", name);
- // goto cleanup;
- //}
- }
-
- if(!chart || !*chart) {
- buffer_sprintf(w->response.data, "No chart id is given at the request.");
- goto cleanup;
- }
-
- RRDSET *st = rrdset_find(host, chart);
- if(!st) st = rrdset_find_byname(host, chart);
- if(!st) {
- buffer_strcat(w->response.data, "Chart is not found: ");
- buffer_strcat_htmlescape(w->response.data, chart);
- ret = HTTP_RESP_NOT_FOUND;
- goto cleanup;
- }
-
- w->response.data->content_type = CT_APPLICATION_JSON;
- st->last_accessed_time_s = now_realtime_sec();
- callback(st, w->response.data);
- return HTTP_RESP_OK;
-
- cleanup:
- return ret;
-}
-
-static inline int web_client_api_request_variable(RRDHOST *host, struct web_client *w, char *url) {
- int ret = HTTP_RESP_BAD_REQUEST;
- char *chart = NULL;
- char *variable = NULL;
-
- buffer_flush(w->response.data);
-
- while(url) {
- char *value = strsep_skip_consecutive_separators(&url, "&");
- if(!value || !*value) continue;
-
- char *name = strsep_skip_consecutive_separators(&value, "=");
- if(!name || !*name) continue;
- if(!value || !*value) continue;
-
- // name and value are now the parameters
- // they are not null and not empty
-
- if(!strcmp(name, "chart")) chart = value;
- else if(!strcmp(name, "variable")) variable = value;
- }
-
- if(!chart || !*chart || !variable || !*variable) {
- buffer_sprintf(w->response.data, "A chart= and a variable= are required.");
- goto cleanup;
- }
-
- RRDSET *st = rrdset_find(host, chart);
- if(!st) st = rrdset_find_byname(host, chart);
- if(!st) {
- buffer_strcat(w->response.data, "Chart is not found: ");
- buffer_strcat_htmlescape(w->response.data, chart);
- ret = HTTP_RESP_NOT_FOUND;
- goto cleanup;
- }
-
- w->response.data->content_type = CT_APPLICATION_JSON;
- st->last_accessed_time_s = now_realtime_sec();
- alert_variable_lookup_trace(host, st, variable, w->response.data);
-
- return HTTP_RESP_OK;
-
-cleanup:
- return ret;
-}
-
-inline int web_client_api_request_v1_alarm_variables(RRDHOST *host, struct web_client *w, char *url) {
- return web_client_api_request_single_chart(host, w, url, health_api_v1_chart_variables2json);
-}
-
-static int web_client_api_request_v1_context(RRDHOST *host, struct web_client *w, char *url) {
- char *context = NULL;
- RRDCONTEXT_TO_JSON_OPTIONS options = RRDCONTEXT_OPTION_NONE;
- time_t after = 0, before = 0;
- const char *chart_label_key = NULL, *chart_labels_filter = NULL;
- BUFFER *dimensions = NULL;
-
- buffer_flush(w->response.data);
-
- while(url) {
- char *value = strsep_skip_consecutive_separators(&url, "&");
- if(!value || !*value) continue;
-
- char *name = strsep_skip_consecutive_separators(&value, "=");
- if(!name || !*name) continue;
- if(!value || !*value) continue;
-
- // name and value are now the parameters
- // they are not null and not empty
-
- if(!strcmp(name, "context") || !strcmp(name, "ctx")) context = value;
- else if(!strcmp(name, "after")) after = str2l(value);
- else if(!strcmp(name, "before")) before = str2l(value);
- else if(!strcmp(name, "options")) options = rrdcontext_to_json_parse_options(value);
- else if(!strcmp(name, "chart_label_key")) chart_label_key = value;
- else if(!strcmp(name, "chart_labels_filter")) chart_labels_filter = value;
- else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) {
- if(!dimensions) dimensions = buffer_create(100, &netdata_buffers_statistics.buffers_api);
- buffer_strcat(dimensions, "|");
- buffer_strcat(dimensions, value);
- }
- }
-
- if(!context || !*context) {
- buffer_sprintf(w->response.data, "No context is given at the request.");
- return HTTP_RESP_BAD_REQUEST;
- }
-
- SIMPLE_PATTERN *chart_label_key_pattern = NULL;
- SIMPLE_PATTERN *chart_labels_filter_pattern = NULL;
- SIMPLE_PATTERN *chart_dimensions_pattern = NULL;
-
- if(chart_label_key)
- chart_label_key_pattern = simple_pattern_create(chart_label_key, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT, true);
-
- if(chart_labels_filter)
- chart_labels_filter_pattern = simple_pattern_create(chart_labels_filter, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT,
- true);
-
- if(dimensions) {
- chart_dimensions_pattern = simple_pattern_create(buffer_tostring(dimensions), ",|\t\r\n\f\v",
- SIMPLE_PATTERN_EXACT, true);
- buffer_free(dimensions);
- }
-
- w->response.data->content_type = CT_APPLICATION_JSON;
- int ret = rrdcontext_to_json(host, w->response.data, after, before, options, context, chart_label_key_pattern, chart_labels_filter_pattern, chart_dimensions_pattern);
-
- simple_pattern_free(chart_label_key_pattern);
- simple_pattern_free(chart_labels_filter_pattern);
- simple_pattern_free(chart_dimensions_pattern);
-
- return ret;
-}
-
-static int web_client_api_request_v1_contexts(RRDHOST *host, struct web_client *w, char *url) {
- RRDCONTEXT_TO_JSON_OPTIONS options = RRDCONTEXT_OPTION_NONE;
- time_t after = 0, before = 0;
- const char *chart_label_key = NULL, *chart_labels_filter = NULL;
- BUFFER *dimensions = NULL;
-
- buffer_flush(w->response.data);
-
- while(url) {
- char *value = strsep_skip_consecutive_separators(&url, "&");
- if(!value || !*value) continue;
-
- char *name = strsep_skip_consecutive_separators(&value, "=");
- if(!name || !*name) continue;
- if(!value || !*value) continue;
-
- // name and value are now the parameters
- // they are not null and not empty
-
- if(!strcmp(name, "after")) after = str2l(value);
- else if(!strcmp(name, "before")) before = str2l(value);
- else if(!strcmp(name, "options")) options = rrdcontext_to_json_parse_options(value);
- else if(!strcmp(name, "chart_label_key")) chart_label_key = value;
- else if(!strcmp(name, "chart_labels_filter")) chart_labels_filter = value;
- else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) {
- if(!dimensions) dimensions = buffer_create(100, &netdata_buffers_statistics.buffers_api);
- buffer_strcat(dimensions, "|");
- buffer_strcat(dimensions, value);
- }
- }
-
- SIMPLE_PATTERN *chart_label_key_pattern = NULL;
- SIMPLE_PATTERN *chart_labels_filter_pattern = NULL;
- SIMPLE_PATTERN *chart_dimensions_pattern = NULL;
-
- if(chart_label_key)
- chart_label_key_pattern = simple_pattern_create(chart_label_key, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT, true);
-
- if(chart_labels_filter)
- chart_labels_filter_pattern = simple_pattern_create(chart_labels_filter, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT,
- true);
-
- if(dimensions) {
- chart_dimensions_pattern = simple_pattern_create(buffer_tostring(dimensions), ",|\t\r\n\f\v",
- SIMPLE_PATTERN_EXACT, true);
- buffer_free(dimensions);
- }
-
- w->response.data->content_type = CT_APPLICATION_JSON;
- int ret = rrdcontexts_to_json(host, w->response.data, after, before, options, chart_label_key_pattern, chart_labels_filter_pattern, chart_dimensions_pattern);
-
- simple_pattern_free(chart_label_key_pattern);
- simple_pattern_free(chart_labels_filter_pattern);
- simple_pattern_free(chart_dimensions_pattern);
-
- return ret;
-}
-
-inline int web_client_api_request_v1_charts(RRDHOST *host, struct web_client *w, char *url) {
- (void)url;
-
- buffer_flush(w->response.data);
- w->response.data->content_type = CT_APPLICATION_JSON;
- charts2json(host, w->response.data);
- return HTTP_RESP_OK;
-}
-
-inline int web_client_api_request_v1_chart(RRDHOST *host, struct web_client *w, char *url) {
- return web_client_api_request_single_chart(host, w, url, rrd_stats_api_v1_chart);
-}
-
-// returns the HTTP code
-static inline int web_client_api_request_v1_data(RRDHOST *host, struct web_client *w, char *url) {
- netdata_log_debug(D_WEB_CLIENT, "%llu: API v1 data with URL '%s'", w->id, url);
-
- int ret = HTTP_RESP_BAD_REQUEST;
- BUFFER *dimensions = NULL;
-
- buffer_flush(w->response.data);
-
- char *google_version = "0.6",
- *google_reqId = "0",
- *google_sig = "0",
- *google_out = "json",
- *responseHandler = NULL,
- *outFileName = NULL;
-
- time_t last_timestamp_in_data = 0, google_timestamp = 0;
-
- char *chart = NULL;
- char *before_str = NULL;
- char *after_str = NULL;
- char *group_time_str = NULL;
- char *points_str = NULL;
- char *timeout_str = NULL;
- char *context = NULL;
- char *chart_label_key = NULL;
- char *chart_labels_filter = NULL;
- char *group_options = NULL;
- size_t tier = 0;
- RRDR_TIME_GROUPING group = RRDR_GROUPING_AVERAGE;
- DATASOURCE_FORMAT format = DATASOURCE_JSON;
- RRDR_OPTIONS options = 0;
-
- while(url) {
- char *value = strsep_skip_consecutive_separators(&url, "&");
- if(!value || !*value) continue;
-
- char *name = strsep_skip_consecutive_separators(&value, "=");
- if(!name || !*name) continue;
- if(!value || !*value) continue;
-
- netdata_log_debug(D_WEB_CLIENT, "%llu: API v1 data query param '%s' with value '%s'", w->id, name, value);
-
- // name and value are now the parameters
- // they are not null and not empty
-
- if(!strcmp(name, "context")) context = value;
- else if(!strcmp(name, "chart_label_key")) chart_label_key = value;
- else if(!strcmp(name, "chart_labels_filter")) chart_labels_filter = value;
- else if(!strcmp(name, "chart")) chart = value;
- else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) {
- if(!dimensions) dimensions = buffer_create(100, &netdata_buffers_statistics.buffers_api);
- buffer_strcat(dimensions, "|");
- buffer_strcat(dimensions, value);
- }
- else if(!strcmp(name, "show_dimensions")) options |= RRDR_OPTION_ALL_DIMENSIONS;
- else if(!strcmp(name, "after")) after_str = value;
- else if(!strcmp(name, "before")) before_str = value;
- else if(!strcmp(name, "points")) points_str = value;
- else if(!strcmp(name, "timeout")) timeout_str = value;
- else if(!strcmp(name, "gtime")) group_time_str = value;
- else if(!strcmp(name, "group_options")) group_options = value;
- else if(!strcmp(name, "group")) {
- group = time_grouping_parse(value, RRDR_GROUPING_AVERAGE);
- }
- else if(!strcmp(name, "format")) {
- format = web_client_api_request_v1_data_format(value);
- }
- else if(!strcmp(name, "options")) {
- options |= rrdr_options_parse(value);
- }
- else if(!strcmp(name, "callback")) {
- responseHandler = value;
- }
- else if(!strcmp(name, "filename")) {
- outFileName = value;
- }
- else if(!strcmp(name, "tqx")) {
- // parse Google Visualization API options
- // https://developers.google.com/chart/interactive/docs/dev/implementing_data_source
- char *tqx_name, *tqx_value;
-
- while(value) {
- tqx_value = strsep_skip_consecutive_separators(&value, ";");
- if(!tqx_value || !*tqx_value) continue;
-
- tqx_name = strsep_skip_consecutive_separators(&tqx_value, ":");
- if(!tqx_name || !*tqx_name) continue;
- if(!tqx_value || !*tqx_value) continue;
-
- if(!strcmp(tqx_name, "version"))
- google_version = tqx_value;
- else if(!strcmp(tqx_name, "reqId"))
- google_reqId = tqx_value;
- else if(!strcmp(tqx_name, "sig")) {
- google_sig = tqx_value;
- google_timestamp = strtoul(google_sig, NULL, 0);
- }
- else if(!strcmp(tqx_name, "out")) {
- google_out = tqx_value;
- format = web_client_api_request_v1_data_google_format(google_out);
- }
- else if(!strcmp(tqx_name, "responseHandler"))
- responseHandler = tqx_value;
- else if(!strcmp(tqx_name, "outFileName"))
- outFileName = tqx_value;
- }
- }
- else if(!strcmp(name, "tier")) {
- tier = str2ul(value);
- if(tier < storage_tiers)
- options |= RRDR_OPTION_SELECTED_TIER;
- else
- tier = 0;
- }
- }
-
- // validate the google parameters given
- fix_google_param(google_out);
- fix_google_param(google_sig);
- fix_google_param(google_reqId);
- fix_google_param(google_version);
- fix_google_param(responseHandler);
- fix_google_param(outFileName);
-
- RRDSET *st = NULL;
- ONEWAYALLOC *owa = onewayalloc_create(0);
- QUERY_TARGET *qt = NULL;
-
- if(!is_valid_sp(chart) && !is_valid_sp(context)) {
- buffer_sprintf(w->response.data, "No chart or context is given.");
- goto cleanup;
- }
-
- if(chart && !context) {
- // check if this is a specific chart
- st = rrdset_find(host, chart);
- if (!st) st = rrdset_find_byname(host, chart);
- }
-
- long long before = (before_str && *before_str)?str2l(before_str):0;
- long long after = (after_str && *after_str) ?str2l(after_str):-600;
- int points = (points_str && *points_str)?str2i(points_str):0;
- int timeout = (timeout_str && *timeout_str)?str2i(timeout_str): 0;
- long group_time = (group_time_str && *group_time_str)?str2l(group_time_str):0;
-
- QUERY_TARGET_REQUEST qtr = {
- .version = 1,
- .after = after,
- .before = before,
- .host = host,
- .st = st,
- .nodes = NULL,
- .contexts = context,
- .instances = chart,
- .dimensions = (dimensions)?buffer_tostring(dimensions):NULL,
- .timeout_ms = timeout,
- .points = points,
- .format = format,
- .options = options,
- .time_group_method = group,
- .time_group_options = group_options,
- .resampling_time = group_time,
- .tier = tier,
- .chart_label_key = chart_label_key,
- .labels = chart_labels_filter,
- .query_source = QUERY_SOURCE_API_DATA,
- .priority = STORAGE_PRIORITY_NORMAL,
- .interrupt_callback = web_client_interrupt_callback,
- .interrupt_callback_data = w,
- .transaction = &w->transaction,
- };
- qt = query_target_create(&qtr);
-
- if(!qt || !qt->query.used) {
- buffer_sprintf(w->response.data, "No metrics where matched to query.");
- ret = HTTP_RESP_NOT_FOUND;
- goto cleanup;
- }
-
- web_client_timeout_checkpoint_set(w, timeout);
- if(web_client_timeout_checkpoint_and_check(w, NULL)) {
- ret = w->response.code;
- goto cleanup;
- }
-
- if(outFileName && *outFileName) {
- buffer_sprintf(w->response.header, "Content-Disposition: attachment; filename=\"%s\"\r\n", outFileName);
- netdata_log_debug(D_WEB_CLIENT, "%llu: generating outfilename header: '%s'", w->id, outFileName);
- }
-
- if(format == DATASOURCE_DATATABLE_JSONP) {
- if(responseHandler == NULL)
- responseHandler = "google.visualization.Query.setResponse";
-
- netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: GOOGLE JSON/JSONP: version = '%s', reqId = '%s', sig = '%s', out = '%s', responseHandler = '%s', outFileName = '%s'",
- w->id, google_version, google_reqId, google_sig, google_out, responseHandler, outFileName
- );
-
- buffer_sprintf(
- w->response.data,
- "%s({version:'%s',reqId:'%s',status:'ok',sig:'%"PRId64"',table:",
- responseHandler,
- google_version,
- google_reqId,
- (int64_t)(st ? st->last_updated.tv_sec : 0));
- }
- else if(format == DATASOURCE_JSONP) {
- if(responseHandler == NULL)
- responseHandler = "callback";
-
- buffer_strcat(w->response.data, responseHandler);
- buffer_strcat(w->response.data, "(");
- }
-
- ret = data_query_execute(owa, w->response.data, qt, &last_timestamp_in_data);
-
- if(format == DATASOURCE_DATATABLE_JSONP) {
- if(google_timestamp < last_timestamp_in_data)
- buffer_strcat(w->response.data, "});");
-
- else {
- // the client already has the latest data
- buffer_flush(w->response.data);
- buffer_sprintf(w->response.data,
- "%s({version:'%s',reqId:'%s',status:'error',errors:[{reason:'not_modified',message:'Data not modified'}]});",
- responseHandler, google_version, google_reqId);
- }
- }
- else if(format == DATASOURCE_JSONP)
- buffer_strcat(w->response.data, ");");
-
- if(qt->internal.relative)
- buffer_no_cacheable(w->response.data);
- else
- buffer_cacheable(w->response.data);
-
-cleanup:
- query_target_release(qt);
- onewayalloc_destroy(owa);
- buffer_free(dimensions);
- return ret;
-}
-
-// Pings a netdata server:
-// /api/v1/registry?action=hello
-//
-// Access to a netdata registry:
-// /api/v1/registry?action=access&machine=${machine_guid}&name=${hostname}&url=${url}
-//
-// Delete from a netdata registry:
-// /api/v1/registry?action=delete&machine=${machine_guid}&name=${hostname}&url=${url}&delete_url=${delete_url}
-//
-// Search for the URLs of a machine:
-// /api/v1/registry?action=search&for=${machine_guid}
-//
-// Impersonate:
-// /api/v1/registry?action=switch&machine=${machine_guid}&name=${hostname}&url=${url}&to=${new_person_guid}
-inline int web_client_api_request_v1_registry(RRDHOST *host, struct web_client *w, char *url) {
- static uint32_t hash_action = 0, hash_access = 0, hash_hello = 0, hash_delete = 0, hash_search = 0,
- hash_switch = 0, hash_machine = 0, hash_url = 0, hash_name = 0, hash_delete_url = 0, hash_for = 0,
- hash_to = 0 /*, hash_redirects = 0 */;
-
- if(unlikely(!hash_action)) {
- hash_action = simple_hash("action");
- hash_access = simple_hash("access");
- hash_hello = simple_hash("hello");
- hash_delete = simple_hash("delete");
- hash_search = simple_hash("search");
- hash_switch = simple_hash("switch");
- hash_machine = simple_hash("machine");
- hash_url = simple_hash("url");
- hash_name = simple_hash("name");
- hash_delete_url = simple_hash("delete_url");
- hash_for = simple_hash("for");
- hash_to = simple_hash("to");
-/*
- hash_redirects = simple_hash("redirects");
-*/
- }
-
- netdata_log_debug(D_WEB_CLIENT, "%llu: API v1 registry with URL '%s'", w->id, url);
-
- // TODO
- // The browser may send multiple cookies with our id
-
- char person_guid[UUID_STR_LEN] = "";
- char *cookie = strstr(w->response.data->buffer, NETDATA_REGISTRY_COOKIE_NAME "=");
- if(cookie)
- strncpyz(person_guid, &cookie[sizeof(NETDATA_REGISTRY_COOKIE_NAME)], UUID_STR_LEN - 1);
- else if(!extract_bearer_token_from_request(w, person_guid, sizeof(person_guid)))
- person_guid[0] = '\0';
-
- char action = '\0';
- char *machine_guid = NULL,
- *machine_url = NULL,
- *url_name = NULL,
- *search_machine_guid = NULL,
- *delete_url = NULL,
- *to_person_guid = NULL;
-/*
- int redirects = 0;
-*/
-
- // Don't cache registry responses
- buffer_no_cacheable(w->response.data);
-
- while(url) {
- char *value = strsep_skip_consecutive_separators(&url, "&");
- if (!value || !*value) continue;
-
- char *name = strsep_skip_consecutive_separators(&value, "=");
- if (!name || !*name) continue;
- if (!value || !*value) continue;
-
- netdata_log_debug(D_WEB_CLIENT, "%llu: API v1 registry query param '%s' with value '%s'", w->id, name, value);
-
- uint32_t hash = simple_hash(name);
-
- if(hash == hash_action && !strcmp(name, "action")) {
- uint32_t vhash = simple_hash(value);
-
- if(vhash == hash_access && !strcmp(value, "access")) action = 'A';
- else if(vhash == hash_hello && !strcmp(value, "hello")) action = 'H';
- else if(vhash == hash_delete && !strcmp(value, "delete")) action = 'D';
- else if(vhash == hash_search && !strcmp(value, "search")) action = 'S';
- else if(vhash == hash_switch && !strcmp(value, "switch")) action = 'W';
-#ifdef NETDATA_INTERNAL_CHECKS
- else netdata_log_error("unknown registry action '%s'", value);
-#endif /* NETDATA_INTERNAL_CHECKS */
- }
-/*
- else if(hash == hash_redirects && !strcmp(name, "redirects"))
- redirects = atoi(value);
-*/
- else if(hash == hash_machine && !strcmp(name, "machine"))
- machine_guid = value;
-
- else if(hash == hash_url && !strcmp(name, "url"))
- machine_url = value;
-
- else if(action == 'A') {
- if(hash == hash_name && !strcmp(name, "name"))
- url_name = value;
- }
- else if(action == 'D') {
- if(hash == hash_delete_url && !strcmp(name, "delete_url"))
- delete_url = value;
- }
- else if(action == 'S') {
- if(hash == hash_for && !strcmp(name, "for"))
- search_machine_guid = value;
- }
- else if(action == 'W') {
- if(hash == hash_to && !strcmp(name, "to"))
- to_person_guid = value;
- }
-#ifdef NETDATA_INTERNAL_CHECKS
- else netdata_log_error("unused registry URL parameter '%s' with value '%s'", name, value);
-#endif /* NETDATA_INTERNAL_CHECKS */
- }
-
- bool do_not_track = respect_web_browser_do_not_track_policy && web_client_has_donottrack(w);
-
- if(unlikely(action == 'H')) {
- // HELLO request, dashboard ACL
- analytics_log_dashboard();
- if(unlikely(!http_can_access_dashboard(w)))
- return web_client_permission_denied_acl(w);
- }
- else {
- // everything else, registry ACL
- if(unlikely(!http_can_access_registry(w)))
- return web_client_permission_denied_acl(w);
-
- if(unlikely(do_not_track)) {
- buffer_flush(w->response.data);
- buffer_sprintf(w->response.data, "Your web browser is sending 'DNT: 1' (Do Not Track). The registry requires persistent cookies on your browser to work.");
- return HTTP_RESP_BAD_REQUEST;
- }
- }
-
- buffer_no_cacheable(w->response.data);
-
- switch(action) {
- case 'A':
- if(unlikely(!machine_guid || !machine_url || !url_name)) {
- netdata_log_error("Invalid registry request - access requires these parameters: machine ('%s'), url ('%s'), name ('%s')", machine_guid ? machine_guid : "UNSET", machine_url ? machine_url : "UNSET", url_name ? url_name : "UNSET");
- buffer_flush(w->response.data);
- buffer_strcat(w->response.data, "Invalid registry Access request.");
- return HTTP_RESP_BAD_REQUEST;
- }
-
- web_client_enable_tracking_required(w);
- return registry_request_access_json(host, w, person_guid, machine_guid, machine_url, url_name, now_realtime_sec());
-
- case 'D':
- if(unlikely(!machine_guid || !machine_url || !delete_url)) {
- netdata_log_error("Invalid registry request - delete requires these parameters: machine ('%s'), url ('%s'), delete_url ('%s')", machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", delete_url?delete_url:"UNSET");
- buffer_flush(w->response.data);
- buffer_strcat(w->response.data, "Invalid registry Delete request.");
- return HTTP_RESP_BAD_REQUEST;
- }
-
- web_client_enable_tracking_required(w);
- return registry_request_delete_json(host, w, person_guid, machine_guid, machine_url, delete_url, now_realtime_sec());
-
- case 'S':
- if(unlikely(!search_machine_guid)) {
- netdata_log_error("Invalid registry request - search requires these parameters: for ('%s')", search_machine_guid?search_machine_guid:"UNSET");
- buffer_flush(w->response.data);
- buffer_strcat(w->response.data, "Invalid registry Search request.");
- return HTTP_RESP_BAD_REQUEST;
- }
-
- web_client_enable_tracking_required(w);
- return registry_request_search_json(host, w, person_guid, search_machine_guid);
-
- case 'W':
- if(unlikely(!machine_guid || !machine_url || !to_person_guid)) {
- netdata_log_error("Invalid registry request - switching identity requires these parameters: machine ('%s'), url ('%s'), to ('%s')", machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", to_person_guid?to_person_guid:"UNSET");
- buffer_flush(w->response.data);
- buffer_strcat(w->response.data, "Invalid registry Switch request.");
- return HTTP_RESP_BAD_REQUEST;
- }
-
- web_client_enable_tracking_required(w);
- return registry_request_switch_json(host, w, person_guid, machine_guid, machine_url, to_person_guid, now_realtime_sec());
-
- case 'H':
- return registry_request_hello_json(host, w, do_not_track);
-
- default:
- buffer_flush(w->response.data);
- buffer_strcat(w->response.data, "Invalid registry request - you need to set an action: hello, access, delete, search");
- return HTTP_RESP_BAD_REQUEST;
- }
-}
-
-void web_client_api_request_v1_info_summary_alarm_statuses(RRDHOST *host, BUFFER *wb, const char *key) {
- buffer_json_member_add_object(wb, key);
-
- size_t normal = 0, warning = 0, critical = 0;
- RRDCALC *rc;
- foreach_rrdcalc_in_rrdhost_read(host, rc) {
- if(unlikely(!rc->rrdset || !rc->rrdset->last_collected_time.tv_sec))
- continue;
-
- switch(rc->status) {
- case RRDCALC_STATUS_WARNING:
- warning++;
- break;
- case RRDCALC_STATUS_CRITICAL:
- critical++;
- break;
- default:
- normal++;
- }
- }
- foreach_rrdcalc_in_rrdhost_done(rc);
-
- buffer_json_member_add_uint64(wb, "normal", normal);
- buffer_json_member_add_uint64(wb, "warning", warning);
- buffer_json_member_add_uint64(wb, "critical", critical);
-
- buffer_json_object_close(wb);
-}
-
-static inline void web_client_api_request_v1_info_mirrored_hosts_status(BUFFER *wb, RRDHOST *host) {
- buffer_json_add_array_item_object(wb);
-
- buffer_json_member_add_string(wb, "hostname", rrdhost_hostname(host));
- buffer_json_member_add_uint64(wb, "hops", host->system_info ? host->system_info->hops : (host == localhost) ? 0 : 1);
- buffer_json_member_add_boolean(wb, "reachable", (host == localhost || !rrdhost_flag_check(host, RRDHOST_FLAG_ORPHAN)));
-
- buffer_json_member_add_string(wb, "guid", host->machine_guid);
- buffer_json_member_add_uuid(wb, "node_id", host->node_id);
- rrdhost_aclk_state_lock(host);
- buffer_json_member_add_string(wb, "claim_id", host->aclk_state.claimed_id);
- rrdhost_aclk_state_unlock(host);
-
- buffer_json_object_close(wb);
-}
-
-static inline void web_client_api_request_v1_info_mirrored_hosts(BUFFER *wb) {
- RRDHOST *host;
-
- rrd_rdlock();
-
- buffer_json_member_add_array(wb, "mirrored_hosts");
- rrdhost_foreach_read(host)
- buffer_json_add_array_item_string(wb, rrdhost_hostname(host));
- buffer_json_array_close(wb);
-
- buffer_json_member_add_array(wb, "mirrored_hosts_status");
- rrdhost_foreach_read(host) {
- if ((host == localhost || !rrdhost_flag_check(host, RRDHOST_FLAG_ORPHAN))) {
- web_client_api_request_v1_info_mirrored_hosts_status(wb, host);
- }
- }
- rrdhost_foreach_read(host) {
- if ((host != localhost && rrdhost_flag_check(host, RRDHOST_FLAG_ORPHAN))) {
- web_client_api_request_v1_info_mirrored_hosts_status(wb, host);
- }
- }
- buffer_json_array_close(wb);
-
- rrd_rdunlock();
-}
-
-void host_labels2json(RRDHOST *host, BUFFER *wb, const char *key) {
- buffer_json_member_add_object(wb, key);
- rrdlabels_to_buffer_json_members(host->rrdlabels, wb);
- buffer_json_object_close(wb);
-}
-
-static void host_collectors(RRDHOST *host, BUFFER *wb) {
- buffer_json_member_add_array(wb, "collectors");
-
- DICTIONARY *dict = dictionary_create(DICT_OPTION_SINGLE_THREADED|DICT_OPTION_DONT_OVERWRITE_VALUE);
- RRDSET *st;
- char name[500];
-
- time_t now = now_realtime_sec();
-
- rrdset_foreach_read(st, host) {
- if (!rrdset_is_available_for_viewers(st))
- continue;
-
- sprintf(name, "%s:%s", rrdset_plugin_name(st), rrdset_module_name(st));
-
- bool old = 0;
- bool *set = dictionary_set(dict, name, &old, sizeof(bool));
- if(!*set) {
- *set = true;
- st->last_accessed_time_s = now;
- buffer_json_add_array_item_object(wb);
- buffer_json_member_add_string(wb, "plugin", rrdset_plugin_name(st));
- buffer_json_member_add_string(wb, "module", rrdset_module_name(st));
- buffer_json_object_close(wb);
- }
- }
- rrdset_foreach_done(st);
- dictionary_destroy(dict);
-
- buffer_json_array_close(wb);
-}
-
-extern int aclk_connected;
-inline int web_client_api_request_v1_info_fill_buffer(RRDHOST *host, BUFFER *wb) {
- buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
-
- buffer_json_member_add_string(wb, "version", rrdhost_program_version(host));
- buffer_json_member_add_string(wb, "uid", host->machine_guid);
-
- buffer_json_member_add_uint64(wb, "hosts-available", rrdhost_hosts_available());
- web_client_api_request_v1_info_mirrored_hosts(wb);
-
- web_client_api_request_v1_info_summary_alarm_statuses(host, wb, "alarms");
-
- buffer_json_member_add_string_or_empty(wb, "os_name", host->system_info->host_os_name);
- buffer_json_member_add_string_or_empty(wb, "os_id", host->system_info->host_os_id);
- buffer_json_member_add_string_or_empty(wb, "os_id_like", host->system_info->host_os_id_like);
- buffer_json_member_add_string_or_empty(wb, "os_version", host->system_info->host_os_version);
- buffer_json_member_add_string_or_empty(wb, "os_version_id", host->system_info->host_os_version_id);
- buffer_json_member_add_string_or_empty(wb, "os_detection", host->system_info->host_os_detection);
- buffer_json_member_add_string_or_empty(wb, "cores_total", host->system_info->host_cores);
- buffer_json_member_add_string_or_empty(wb, "total_disk_space", host->system_info->host_disk_space);
- buffer_json_member_add_string_or_empty(wb, "cpu_freq", host->system_info->host_cpu_freq);
- buffer_json_member_add_string_or_empty(wb, "ram_total", host->system_info->host_ram_total);
-
- buffer_json_member_add_string_or_omit(wb, "container_os_name", host->system_info->container_os_name);
- buffer_json_member_add_string_or_omit(wb, "container_os_id", host->system_info->container_os_id);
- buffer_json_member_add_string_or_omit(wb, "container_os_id_like", host->system_info->container_os_id_like);
- buffer_json_member_add_string_or_omit(wb, "container_os_version", host->system_info->container_os_version);
- buffer_json_member_add_string_or_omit(wb, "container_os_version_id", host->system_info->container_os_version_id);
- buffer_json_member_add_string_or_omit(wb, "container_os_detection", host->system_info->container_os_detection);
- buffer_json_member_add_string_or_omit(wb, "is_k8s_node", host->system_info->is_k8s_node);
-
- buffer_json_member_add_string_or_empty(wb, "kernel_name", host->system_info->kernel_name);
- buffer_json_member_add_string_or_empty(wb, "kernel_version", host->system_info->kernel_version);
- buffer_json_member_add_string_or_empty(wb, "architecture", host->system_info->architecture);
- buffer_json_member_add_string_or_empty(wb, "virtualization", host->system_info->virtualization);
- buffer_json_member_add_string_or_empty(wb, "virt_detection", host->system_info->virt_detection);
- buffer_json_member_add_string_or_empty(wb, "container", host->system_info->container);
- buffer_json_member_add_string_or_empty(wb, "container_detection", host->system_info->container_detection);
-
- buffer_json_member_add_string_or_omit(wb, "cloud_provider_type", host->system_info->cloud_provider_type);
- buffer_json_member_add_string_or_omit(wb, "cloud_instance_type", host->system_info->cloud_instance_type);
- buffer_json_member_add_string_or_omit(wb, "cloud_instance_region", host->system_info->cloud_instance_region);
-
- host_labels2json(host, wb, "host_labels");
- host_functions2json(host, wb);
- host_collectors(host, wb);
-
- buffer_json_member_add_boolean(wb, "cloud-enabled", netdata_cloud_enabled);
-
-#ifdef ENABLE_ACLK
- buffer_json_member_add_boolean(wb, "cloud-available", true);
-#else
- buffer_json_member_add_boolean(wb, "cloud-available", false);
-#endif
-
- char *agent_id = get_agent_claimid();
- buffer_json_member_add_boolean(wb, "agent-claimed", agent_id != NULL);
- freez(agent_id);
-
-#ifdef ENABLE_ACLK
- buffer_json_member_add_boolean(wb, "aclk-available", aclk_connected);
-#else
- buffer_json_member_add_boolean(wb, "aclk-available", false);
-#endif
-
- buffer_json_member_add_string(wb, "memory-mode", rrd_memory_mode_name(host->rrd_memory_mode));
-#ifdef ENABLE_DBENGINE
- buffer_json_member_add_uint64(wb, "multidb-disk-quota", default_multidb_disk_quota_mb);
- buffer_json_member_add_uint64(wb, "page-cache-size", default_rrdeng_page_cache_mb);
-#endif // ENABLE_DBENGINE
- buffer_json_member_add_boolean(wb, "web-enabled", web_server_mode != WEB_SERVER_MODE_NONE);
- buffer_json_member_add_boolean(wb, "stream-enabled", default_rrdpush_enabled);
-
- buffer_json_member_add_boolean(wb, "stream-compression",
- host->sender && host->sender->compressor.initialized);
-
-#ifdef ENABLE_HTTPS
- buffer_json_member_add_boolean(wb, "https-enabled", true);
-#else
- buffer_json_member_add_boolean(wb, "https-enabled", false);
-#endif
-
- buffer_json_member_add_quoted_string(wb, "buildinfo", analytics_data.netdata_buildinfo);
- buffer_json_member_add_quoted_string(wb, "release-channel", analytics_data.netdata_config_release_channel);
- buffer_json_member_add_quoted_string(wb, "notification-methods", analytics_data.netdata_notification_methods);
-
- buffer_json_member_add_boolean(wb, "exporting-enabled", analytics_data.exporting_enabled);
- buffer_json_member_add_quoted_string(wb, "exporting-connectors", analytics_data.netdata_exporting_connectors);
-
- buffer_json_member_add_uint64(wb, "allmetrics-prometheus-used", analytics_data.prometheus_hits);
- buffer_json_member_add_uint64(wb, "allmetrics-shell-used", analytics_data.shell_hits);
- buffer_json_member_add_uint64(wb, "allmetrics-json-used", analytics_data.json_hits);
- buffer_json_member_add_uint64(wb, "dashboard-used", analytics_data.dashboard_hits);
-
- buffer_json_member_add_uint64(wb, "charts-count", analytics_data.charts_count);
- buffer_json_member_add_uint64(wb, "metrics-count", analytics_data.metrics_count);
-
-#if defined(ENABLE_ML)
- buffer_json_member_add_object(wb, "ml-info");
- ml_host_get_info(host, wb);
- buffer_json_object_close(wb);
-#endif
-
- buffer_json_finalize(wb);
- return 0;
-}
-
-#if defined(ENABLE_ML)
-int web_client_api_request_v1_ml_info(RRDHOST *host, struct web_client *w, char *url) {
- (void) url;
-
- if (!netdata_ready)
- return HTTP_RESP_SERVICE_UNAVAILABLE;
-
- BUFFER *wb = w->response.data;
- buffer_flush(wb);
- wb->content_type = CT_APPLICATION_JSON;
-
- buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
- ml_host_get_detection_info(host, wb);
- buffer_json_finalize(wb);
-
- buffer_no_cacheable(wb);
-
- return HTTP_RESP_OK;
-}
-#endif // ENABLE_ML
-
-inline int web_client_api_request_v1_info(RRDHOST *host, struct web_client *w, char *url) {
- (void)url;
- if (!netdata_ready) return HTTP_RESP_SERVICE_UNAVAILABLE;
- BUFFER *wb = w->response.data;
- buffer_flush(wb);
- wb->content_type = CT_APPLICATION_JSON;
-
- web_client_api_request_v1_info_fill_buffer(host, wb);
-
- buffer_no_cacheable(wb);
- return HTTP_RESP_OK;
-}
-
-static int web_client_api_request_v1_aclk_state(RRDHOST *host, struct web_client *w, char *url) {
- UNUSED(url);
- UNUSED(host);
- if (!netdata_ready) return HTTP_RESP_SERVICE_UNAVAILABLE;
-
- BUFFER *wb = w->response.data;
- buffer_flush(wb);
-#ifdef ENABLE_ACLK
- char *str = aclk_state_json();
- buffer_strcat(wb, str);
- freez(str);
-#else
- buffer_strcat(wb, "{\"aclk-available\":false}");
-#endif
-
- wb->content_type = CT_APPLICATION_JSON;
- buffer_no_cacheable(wb);
- return HTTP_RESP_OK;
-}
-
-int web_client_api_request_v1_metric_correlations(RRDHOST *host, struct web_client *w, char *url) {
- return web_client_api_request_weights(host, w, url, default_metric_correlations_method, WEIGHTS_FORMAT_CHARTS, 1);
-}
-
-int web_client_api_request_v1_weights(RRDHOST *host, struct web_client *w, char *url) {
- return web_client_api_request_weights(host, w, url, WEIGHTS_METHOD_ANOMALY_RATE, WEIGHTS_FORMAT_CONTEXTS, 1);
-}
-
-static void web_client_progress_functions_update(void *data, size_t done, size_t all) {
- // handle progress updates from the plugin
- struct web_client *w = data;
- query_progress_functions_update(&w->transaction, done, all);
-}
-
-int web_client_api_request_v1_function(RRDHOST *host, struct web_client *w, char *url) {
- if (!netdata_ready)
- return HTTP_RESP_SERVICE_UNAVAILABLE;
-
- int timeout = 0;
- const char *function = NULL;
-
- while (url) {
- char *value = strsep_skip_consecutive_separators(&url, "&");
- if (!value || !*value)
- continue;
-
- char *name = strsep_skip_consecutive_separators(&value, "=");
- if (!name || !*name)
- continue;
-
- if (!strcmp(name, "function"))
- function = value;
-
- else if (!strcmp(name, "timeout"))
- timeout = (int) strtoul(value, NULL, 0);
- }
-
- BUFFER *wb = w->response.data;
- buffer_flush(wb);
- wb->content_type = CT_APPLICATION_JSON;
- buffer_no_cacheable(wb);
-
- char transaction[UUID_COMPACT_STR_LEN];
- uuid_unparse_lower_compact(w->transaction, transaction);
-
- CLEAN_BUFFER *source = buffer_create(100, NULL);
- web_client_source2buffer(w, source);
-
- return rrd_function_run(host, wb, timeout, w->access, function, true, transaction,
- NULL, NULL,
- web_client_progress_functions_update, w,
- web_client_interrupt_callback, w, NULL,
- buffer_tostring(source));
-}
-
-int web_client_api_request_v1_functions(RRDHOST *host, struct web_client *w, char *url __maybe_unused) {
- if (!netdata_ready)
- return HTTP_RESP_SERVICE_UNAVAILABLE;
-
- BUFFER *wb = w->response.data;
- buffer_flush(wb);
- wb->content_type = CT_APPLICATION_JSON;
- buffer_no_cacheable(wb);
-
- buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
- host_functions2json(host, wb);
- buffer_json_finalize(wb);
-
- return HTTP_RESP_OK;
-}
-
-void web_client_source2buffer(struct web_client *w, BUFFER *source) {
- if(web_client_flag_check(w, WEB_CLIENT_FLAG_AUTH_CLOUD))
- buffer_sprintf(source, "method=NC");
- else if(web_client_flag_check(w, WEB_CLIENT_FLAG_AUTH_BEARER))
- buffer_sprintf(source, "method=api-bearer");
- else
- buffer_sprintf(source, "method=api");
-
- if(web_client_flag_check(w, WEB_CLIENT_FLAG_AUTH_GOD))
- buffer_strcat(source, ",role=god");
- else
- buffer_sprintf(source, ",role=%s", http_id2user_role(w->user_role));
-
- buffer_sprintf(source, ",permissions="HTTP_ACCESS_FORMAT, (HTTP_ACCESS_FORMAT_CAST)w->access);
-
- if(w->auth.client_name[0])
- buffer_sprintf(source, ",user=%s", w->auth.client_name);
-
- if(!uuid_is_null(w->auth.cloud_account_id)) {
- char uuid_str[UUID_COMPACT_STR_LEN];
- uuid_unparse_lower_compact(w->auth.cloud_account_id, uuid_str);
- buffer_sprintf(source, ",account=%s", uuid_str);
- }
-
- if(w->client_ip[0])
- buffer_sprintf(source, ",ip=%s", w->client_ip);
-
- if(w->forwarded_for)
- buffer_sprintf(source, ",forwarded_for=%s", w->forwarded_for);
-}
-
-static int web_client_api_request_v1_config(RRDHOST *host, struct web_client *w, char *url __maybe_unused) {
- char *action = "tree";
- char *path = "/";
- char *id = NULL;
- char *add_name = NULL;
- int timeout = 120;
-
- while(url) {
- char *value = strsep_skip_consecutive_separators(&url, "&");
- if(!value || !*value) continue;
-
- char *name = strsep_skip_consecutive_separators(&value, "=");
- if(!name || !*name) continue;
- if(!value || !*value) continue;
-
- // name and value are now the parameters
- // they are not null and not empty
-
- if(!strcmp(name, "action"))
- action = value;
- else if(!strcmp(name, "path"))
- path = value;
- else if(!strcmp(name, "id"))
- id = value;
- else if(!strcmp(name, "name"))
- add_name = value;
- else if(!strcmp(name, "timeout")) {
- timeout = (int)strtol(value, NULL, 10);
- if(timeout < 10)
- timeout = 10;
- }
- }
-
- char transaction[UUID_COMPACT_STR_LEN];
- uuid_unparse_lower_compact(w->transaction, transaction);
-
- size_t len = strlen(action) + (id ? strlen(id) : 0) + strlen(path) + (add_name ? strlen(add_name) : 0) + 100;
-
- char cmd[len];
- if(strcmp(action, "tree") == 0)
- snprintfz(cmd, sizeof(cmd), PLUGINSD_FUNCTION_CONFIG " tree '%s' '%s'", path, id?id:"");
- else {
- DYNCFG_CMDS c = dyncfg_cmds2id(action);
- if(!id || !*id || !dyncfg_is_valid_id(id)) {
- rrd_call_function_error(w->response.data, "invalid id given", HTTP_RESP_BAD_REQUEST);
- return HTTP_RESP_BAD_REQUEST;
- }
-
- if(c == DYNCFG_CMD_NONE) {
- rrd_call_function_error(w->response.data, "invalid action given", HTTP_RESP_BAD_REQUEST);
- return HTTP_RESP_BAD_REQUEST;
- }
-
- if(c == DYNCFG_CMD_ADD || c == DYNCFG_CMD_USERCONFIG || c == DYNCFG_CMD_TEST) {
- if(c == DYNCFG_CMD_TEST && (!add_name || !*add_name)) {
- // backwards compatibility for TEST without a name
- char *colon = strrchr(id, ':');
- if(colon) {
- *colon = '\0';
- add_name = ++colon;
- }
- else
- add_name = "test";
- }
-
- if(!add_name || !*add_name || !dyncfg_is_valid_id(add_name)) {
- rrd_call_function_error(w->response.data, "invalid name given", HTTP_RESP_BAD_REQUEST);
- return HTTP_RESP_BAD_REQUEST;
- }
- snprintfz(cmd, sizeof(cmd), PLUGINSD_FUNCTION_CONFIG " %s %s %s", id, dyncfg_id2cmd_one(c), add_name);
- }
- else
- snprintfz(cmd, sizeof(cmd), PLUGINSD_FUNCTION_CONFIG " %s %s", id, dyncfg_id2cmd_one(c));
- }
-
- CLEAN_BUFFER *source = buffer_create(100, NULL);
- web_client_source2buffer(w, source);
-
- buffer_flush(w->response.data);
- int code = rrd_function_run(host, w->response.data, timeout, w->access, cmd,
- true, transaction,
- NULL, NULL,
- web_client_progress_functions_update, w,
- web_client_interrupt_callback, w,
- w->payload, buffer_tostring(source));
-
- return code;
-}
-
-#ifndef ENABLE_DBENGINE
-int web_client_api_request_v1_dbengine_stats(RRDHOST *host __maybe_unused, struct web_client *w __maybe_unused, char *url __maybe_unused) {
- return HTTP_RESP_NOT_FOUND;
-}
-#else
-static void web_client_api_v1_dbengine_stats_for_tier(BUFFER *wb, size_t tier) {
- RRDENG_SIZE_STATS stats = rrdeng_size_statistics(multidb_ctx[tier]);
-
- buffer_sprintf(wb,
- "\n\t\t\"default_granularity_secs\":%zu"
- ",\n\t\t\"sizeof_datafile\":%zu"
- ",\n\t\t\"sizeof_page_in_cache\":%zu"
- ",\n\t\t\"sizeof_point_data\":%zu"
- ",\n\t\t\"sizeof_page_data\":%zu"
- ",\n\t\t\"pages_per_extent\":%zu"
- ",\n\t\t\"datafiles\":%zu"
- ",\n\t\t\"extents\":%zu"
- ",\n\t\t\"extents_pages\":%zu"
- ",\n\t\t\"points\":%zu"
- ",\n\t\t\"metrics\":%zu"
- ",\n\t\t\"metrics_pages\":%zu"
- ",\n\t\t\"extents_compressed_bytes\":%zu"
- ",\n\t\t\"pages_uncompressed_bytes\":%zu"
- ",\n\t\t\"pages_duration_secs\":%lld"
- ",\n\t\t\"single_point_pages\":%zu"
- ",\n\t\t\"first_t\":%ld"
- ",\n\t\t\"last_t\":%ld"
- ",\n\t\t\"database_retention_secs\":%lld"
- ",\n\t\t\"average_compression_savings\":%0.2f"
- ",\n\t\t\"average_point_duration_secs\":%0.2f"
- ",\n\t\t\"average_metric_retention_secs\":%0.2f"
- ",\n\t\t\"ephemeral_metrics_per_day_percent\":%0.2f"
- ",\n\t\t\"average_page_size_bytes\":%0.2f"
- ",\n\t\t\"estimated_concurrently_collected_metrics\":%zu"
- ",\n\t\t\"currently_collected_metrics\":%zu"
- ",\n\t\t\"disk_space\":%zu"
- ",\n\t\t\"max_disk_space\":%zu"
- , stats.default_granularity_secs
- , stats.sizeof_datafile
- , stats.sizeof_page_in_cache
- , stats.sizeof_point_data
- , stats.sizeof_page_data
- , stats.pages_per_extent
- , stats.datafiles
- , stats.extents
- , stats.extents_pages
- , stats.points
- , stats.metrics
- , stats.metrics_pages
- , stats.extents_compressed_bytes
- , stats.pages_uncompressed_bytes
- , (long long)stats.pages_duration_secs
- , stats.single_point_pages
- , stats.first_time_s
- , stats.last_time_s
- , (long long)stats.database_retention_secs
- , stats.average_compression_savings
- , stats.average_point_duration_secs
- , stats.average_metric_retention_secs
- , stats.ephemeral_metrics_per_day_percent
- , stats.average_page_size_bytes
- , stats.estimated_concurrently_collected_metrics
- , stats.currently_collected_metrics
- , stats.disk_space
- , stats.max_disk_space
- );
-}
-int web_client_api_request_v1_dbengine_stats(RRDHOST *host __maybe_unused, struct web_client *w, char *url __maybe_unused) {
- if (!netdata_ready)
- return HTTP_RESP_SERVICE_UNAVAILABLE;
-
- BUFFER *wb = w->response.data;
- buffer_flush(wb);
-
- if(!dbengine_enabled) {
- buffer_strcat(wb, "dbengine is not enabled");
- return HTTP_RESP_NOT_FOUND;
- }
-
- wb->content_type = CT_APPLICATION_JSON;
- buffer_no_cacheable(wb);
- buffer_strcat(wb, "{");
- for(size_t tier = 0; tier < storage_tiers ;tier++) {
- buffer_sprintf(wb, "%s\n\t\"tier%zu\": {", tier?",":"", tier);
- web_client_api_v1_dbengine_stats_for_tier(wb, tier);
- buffer_strcat(wb, "\n\t}");
- }
- buffer_strcat(wb, "\n}");
-
- return HTTP_RESP_OK;
-}
-#endif
-
-#define HLT_MGM "manage/health"
-int web_client_api_request_v1_mgmt(RRDHOST *host, struct web_client *w, char *url) {
- const char *haystack = buffer_tostring(w->url_path_decoded);
- char *needle;
-
- buffer_flush(w->response.data);
-
- if ((needle = strstr(haystack, HLT_MGM)) == NULL) {
- buffer_strcat(w->response.data, "Invalid management request. Curently only 'health' is supported.");
- return HTTP_RESP_NOT_FOUND;
- }
- needle += strlen(HLT_MGM);
- if (*needle != '\0') {
- buffer_strcat(w->response.data, "Invalid management request. Currently only 'health' is supported.");
- return HTTP_RESP_NOT_FOUND;
- }
- return web_client_api_request_v1_mgmt_health(host, w, url);
-}
+#include "v1/api_v1_calls.h"
+#include "v2/api_v2_calls.h"
+#include "v3/api_v3_calls.h"
static struct web_api_command api_commands_v1[] = {
// time-series data APIs
{
.api = "data",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_METRICS,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v1_data,
+ .callback = api_v1_data,
.allow_subpaths = 0
},
+#if defined(ENABLE_API_V1)
{
.api = "weights",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_METRICS,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v1_weights,
+ .callback = api_v1_weights,
.allow_subpaths = 0
},
{
// deprecated - do not use anymore - use "weights"
.api = "metric_correlations",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_METRICS,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v1_metric_correlations,
+ .callback = api_v1_metric_correlations,
.allow_subpaths = 0
},
+#endif
{
- // exporting API
- .api = "allmetrics",
+ .api = "badge.svg",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_BADGES,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v1_allmetrics,
+ .callback = api_v1_badge,
.allow_subpaths = 0
},
{
- // badges can be fetched with both dashboard and badge ACL
- .api = "badge.svg",
+ // exporting API
+ .api = "allmetrics",
.hash = 0,
- .acl = HTTP_ACL_BADGES,
+ .acl = HTTP_ACL_METRICS,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v1_badge,
+ .callback = api_v1_allmetrics,
.allow_subpaths = 0
},
// alerts APIs
+#if defined(ENABLE_API_V1)
{
.api = "alarms",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_ALERTS,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v1_alarms,
+ .callback = api_v1_alarms,
.allow_subpaths = 0
},
{
.api = "alarms_values",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_ALERTS,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v1_alarms_values,
+ .callback = api_v1_alarms_values,
.allow_subpaths = 0
},
{
.api = "alarm_log",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_ALERTS,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v1_alarm_log,
+ .callback = api_v1_alarm_log,
.allow_subpaths = 0
},
{
.api = "alarm_variables",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_ALERTS,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v1_alarm_variables,
+ .callback = api_v1_alarm_variables,
.allow_subpaths = 0
},
{
.api = "variable",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_ALERTS,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_variable,
+ .callback = api_v1_variable,
.allow_subpaths = 0
},
{
.api = "alarm_count",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_ALERTS,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v1_alarm_count,
+ .callback = api_v1_alarm_count,
.allow_subpaths = 0
},
+#endif
// functions APIs - they check permissions per function call
{
.api = "function",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_FUNCTIONS,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v1_function,
+ .callback = api_v1_function,
.allow_subpaths = 0
},
+
+#if defined(ENABLE_API_V1)
{
.api = "functions",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_FUNCTIONS,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v1_functions,
+ .callback = api_v1_functions,
.allow_subpaths = 0
},
+#endif
// time-series metadata APIs
+#if defined(ENABLE_API_V1)
{
.api = "chart",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_METRICS,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v1_chart,
+ .callback = api_v1_chart,
.allow_subpaths = 0
},
+#endif
{
.api = "charts",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_METRICS,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v1_charts,
+ .callback = api_v1_charts,
.allow_subpaths = 0
},
{
.api = "context",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_METRICS,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v1_context,
+ .callback = api_v1_context,
.allow_subpaths = 0
},
{
.api = "contexts",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_METRICS,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v1_contexts,
+ .callback = api_v1_contexts,
.allow_subpaths = 0
},
// registry APIs
+#if defined(ENABLE_API_V1)
{
// registry checks the ACL by itself, so we allow everything
.api = "registry",
.hash = 0,
.acl = HTTP_ACL_NONE, // it manages acl by itself
.access = HTTP_ACCESS_NONE, // it manages access by itself
- .callback = web_client_api_request_v1_registry,
+ .callback = api_v1_registry,
.allow_subpaths = 0
},
+#endif
// agent information APIs
+#if defined(ENABLE_API_V1)
{
.api = "info",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_NODES,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v1_info,
+ .callback = api_v1_info,
.allow_subpaths = 0
},
{
.api = "aclk",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_NODES,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v1_aclk_state,
+ .callback = api_v1_aclk,
.allow_subpaths = 0
},
{
// deprecated - use /api/v2/info
.api = "dbengine_stats",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_NODES,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v1_dbengine_stats,
+ .callback = api_v1_dbengine_stats,
.allow_subpaths = 0
},
-
- // dyncfg APIs
{
- .api = "config",
+ .api = "ml_info",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_NODES,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v1_config,
+ .callback = api_v1_ml_info,
.allow_subpaths = 0
},
-
-#if defined(ENABLE_ML)
{
- .api = "ml_info",
- .hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
- .access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v1_ml_info,
- .allow_subpaths = 0
+ .api = "manage",
+ .hash = 0,
+ .acl = HTTP_ACL_MANAGEMENT,
+ .access = HTTP_ACCESS_NONE, // it manages access by itself
+ .callback = api_v1_manage,
+ .allow_subpaths = 1
},
#endif
+ // dyncfg APIs
{
- // deprecated
- .api = "manage",
+ .api = "config",
.hash = 0,
- .acl = HTTP_ACL_MANAGEMENT,
- .access = HTTP_ACCESS_NONE, // it manages access by itself
- .callback = web_client_api_request_v1_mgmt,
- .allow_subpaths = 1
+ .acl = HTTP_ACL_DYNCFG,
+ .access = HTTP_ACCESS_ANONYMOUS_DATA,
+ .callback = api_v1_config,
+ .allow_subpaths = 0
},
{
diff --git a/src/web/api/web_api_v1.h b/src/web/api/web_api_v1.h
index cf0efbd1..c102ac75 100644
--- a/src/web/api/web_api_v1.h
+++ b/src/web/api/web_api_v1.h
@@ -5,43 +5,7 @@
#include "web_api.h"
-struct web_client;
-
-CONTEXTS_V2_OPTIONS web_client_api_request_v2_context_options(char *o);
-CONTEXTS_V2_ALERT_STATUS web_client_api_request_v2_alert_status(char *o);
-void web_client_api_request_v2_contexts_options_to_buffer_json_array(BUFFER *wb, const char *key, CONTEXTS_V2_OPTIONS options);
-void web_client_api_request_v2_contexts_alerts_status_to_buffer_json_array(BUFFER *wb, const char *key, CONTEXTS_V2_ALERT_STATUS options);
-
-RRDR_OPTIONS rrdr_options_parse(char *o);
-RRDR_OPTIONS rrdr_options_parse_one(const char *o);
-
-void rrdr_options_to_buffer(BUFFER *wb, RRDR_OPTIONS options);
-void rrdr_options_to_buffer_json_array(BUFFER *wb, const char *key, RRDR_OPTIONS options);
-void web_client_api_request_v1_data_options_to_string(char *buf, size_t size, RRDR_OPTIONS options);
-
-uint32_t web_client_api_request_v1_data_format(char *name);
-uint32_t web_client_api_request_v1_data_google_format(char *name);
-
-int web_client_api_request_v1_alarms(RRDHOST *host, struct web_client *w, char *url);
-int web_client_api_request_v1_alarms_values(RRDHOST *host, struct web_client *w, char *url);
-int web_client_api_request_v1_alarm_log(RRDHOST *host, struct web_client *w, char *url);
-int web_client_api_request_single_chart(RRDHOST *host, struct web_client *w, char *url, void callback(RRDSET *st, BUFFER *buf));
-int web_client_api_request_v1_alarm_variables(RRDHOST *host, struct web_client *w, char *url);
-int web_client_api_request_v1_alarm_count(RRDHOST *host, struct web_client *w, char *url);
-int web_client_api_request_v1_charts(RRDHOST *host, struct web_client *w, char *url);
-int web_client_api_request_v1_chart(RRDHOST *host, struct web_client *w, char *url);
-int web_client_api_request_v1_registry(RRDHOST *host, struct web_client *w, char *url);
-int web_client_api_request_v1_info(RRDHOST *host, struct web_client *w, char *url);
int web_client_api_request_v1(RRDHOST *host, struct web_client *w, char *url_path_endpoint);
-int web_client_api_request_v1_info_fill_buffer(RRDHOST *host, BUFFER *wb);
-
-void web_client_api_v1_init(void);
-void web_client_api_v1_management_init(void);
-
-void host_labels2json(RRDHOST *host, BUFFER *wb, const char *key);
-void web_client_api_request_v1_info_summary_alarm_statuses(RRDHOST *host, BUFFER *wb, const char *key);
-
-void web_client_source2buffer(struct web_client *w, BUFFER *source);
extern char *api_secret;
diff --git a/src/web/api/web_api_v2.c b/src/web/api/web_api_v2.c
index c62ed9ed..f8e52a94 100644
--- a/src/web/api/web_api_v2.c
+++ b/src/web/api/web_api_v2.c
@@ -1,604 +1,27 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#include "web_api_v2.h"
-#include "../rtc/webrtc.h"
-
-static bool verify_agent_uuids(const char *machine_guid, const char *node_id, const char *claim_id) {
- if(!machine_guid || !node_id || !claim_id)
- return false;
-
- if(strcmp(machine_guid, localhost->machine_guid) != 0)
- return false;
-
- char *agent_claim_id = get_agent_claimid();
-
- bool not_verified = (!agent_claim_id || strcmp(claim_id, agent_claim_id) != 0);
- freez(agent_claim_id);
-
- if(not_verified || !localhost->node_id)
- return false;
-
- char buf[UUID_STR_LEN];
- uuid_unparse_lower(*localhost->node_id, buf);
-
- if(strcmp(node_id, buf) != 0)
- return false;
-
- return true;
-}
-
-int api_v2_bearer_protection(RRDHOST *host __maybe_unused, struct web_client *w __maybe_unused, char *url) {
- char *machine_guid = NULL;
- char *claim_id = NULL;
- char *node_id = NULL;
- bool protection = netdata_is_protected_by_bearer;
-
- while (url) {
- char *value = strsep_skip_consecutive_separators(&url, "&");
- if (!value || !*value) continue;
-
- char *name = strsep_skip_consecutive_separators(&value, "=");
- if (!name || !*name) continue;
- if (!value || !*value) continue;
-
- if(!strcmp(name, "bearer_protection")) {
- if(!strcmp(value, "on") || !strcmp(value, "true") || !strcmp(value, "yes"))
- protection = true;
- else
- protection = false;
- }
- else if(!strcmp(name, "machine_guid"))
- machine_guid = value;
- else if(!strcmp(name, "claim_id"))
- claim_id = value;
- else if(!strcmp(name, "node_id"))
- node_id = value;
- }
-
- if(!verify_agent_uuids(machine_guid, node_id, claim_id)) {
- buffer_flush(w->response.data);
- buffer_strcat(w->response.data, "The request is missing or not matching local UUIDs");
- return HTTP_RESP_BAD_REQUEST;
- }
-
- netdata_is_protected_by_bearer = protection;
-
- BUFFER *wb = w->response.data;
- buffer_flush(wb);
- buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
- buffer_json_member_add_boolean(wb, "bearer_protection", netdata_is_protected_by_bearer);
- buffer_json_finalize(wb);
-
- return HTTP_RESP_OK;
-}
-
-int api_v2_bearer_token(RRDHOST *host __maybe_unused, struct web_client *w __maybe_unused, char *url __maybe_unused) {
- char *machine_guid = NULL;
- char *claim_id = NULL;
- char *node_id = NULL;
-
- while(url) {
- char *value = strsep_skip_consecutive_separators(&url, "&");
- if (!value || !*value) continue;
-
- char *name = strsep_skip_consecutive_separators(&value, "=");
- if (!name || !*name) continue;
- if (!value || !*value) continue;
-
- if(!strcmp(name, "machine_guid"))
- machine_guid = value;
- else if(!strcmp(name, "claim_id"))
- claim_id = value;
- else if(!strcmp(name, "node_id"))
- node_id = value;
- }
-
- if(!verify_agent_uuids(machine_guid, node_id, claim_id)) {
- buffer_flush(w->response.data);
- buffer_strcat(w->response.data, "The request is missing or not matching local UUIDs");
- return HTTP_RESP_BAD_REQUEST;
- }
-
- nd_uuid_t uuid;
- time_t expires_s = bearer_create_token(&uuid, w);
-
- BUFFER *wb = w->response.data;
- buffer_flush(wb);
- buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
- buffer_json_member_add_string(wb, "mg", localhost->machine_guid);
- buffer_json_member_add_boolean(wb, "bearer_protection", netdata_is_protected_by_bearer);
- buffer_json_member_add_uuid(wb, "token", &uuid);
- buffer_json_member_add_time_t(wb, "expiration", expires_s);
- buffer_json_finalize(wb);
-
- return HTTP_RESP_OK;
-}
-
-static int web_client_api_request_v2_contexts_internal(RRDHOST *host __maybe_unused, struct web_client *w, char *url, CONTEXTS_V2_MODE mode) {
- struct api_v2_contexts_request req = { 0 };
-
- while(url) {
- char *value = strsep_skip_consecutive_separators(&url, "&");
- if(!value || !*value) continue;
-
- char *name = strsep_skip_consecutive_separators(&value, "=");
- if(!name || !*name) continue;
- if(!value || !*value) continue;
-
- // name and value are now the parameters
- // they are not null and not empty
-
- if(!strcmp(name, "scope_nodes"))
- req.scope_nodes = value;
- else if(!strcmp(name, "nodes"))
- req.nodes = value;
- else if((mode & (CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_SEARCH | CONTEXTS_V2_ALERTS | CONTEXTS_V2_ALERT_TRANSITIONS)) && !strcmp(name, "scope_contexts"))
- req.scope_contexts = value;
- else if((mode & (CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_SEARCH | CONTEXTS_V2_ALERTS | CONTEXTS_V2_ALERT_TRANSITIONS)) && !strcmp(name, "contexts"))
- req.contexts = value;
- else if((mode & CONTEXTS_V2_SEARCH) && !strcmp(name, "q"))
- req.q = value;
- else if(!strcmp(name, "options"))
- req.options = web_client_api_request_v2_context_options(value);
- else if(!strcmp(name, "after"))
- req.after = str2l(value);
- else if(!strcmp(name, "before"))
- req.before = str2l(value);
- else if(!strcmp(name, "timeout"))
- req.timeout_ms = str2l(value);
- else if(mode & (CONTEXTS_V2_ALERTS | CONTEXTS_V2_ALERT_TRANSITIONS)) {
- if (!strcmp(name, "alert"))
- req.alerts.alert = value;
- else if (!strcmp(name, "transition"))
- req.alerts.transition = value;
- else if(mode & CONTEXTS_V2_ALERTS) {
- if (!strcmp(name, "status"))
- req.alerts.status = web_client_api_request_v2_alert_status(value);
- }
- else if(mode & CONTEXTS_V2_ALERT_TRANSITIONS) {
- if (!strcmp(name, "last"))
- req.alerts.last = strtoul(value, NULL, 0);
- else if(!strcmp(name, "context"))
- req.contexts = value;
- else if (!strcmp(name, "anchor_gi")) {
- req.alerts.global_id_anchor = str2ull(value, NULL);
- }
- else {
- for(int i = 0; i < ATF_TOTAL_ENTRIES ;i++) {
- if(!strcmp(name, alert_transition_facets[i].query_param))
- req.alerts.facets[i] = value;
- }
- }
- }
- }
- }
-
- if ((mode & CONTEXTS_V2_ALERT_TRANSITIONS) && !req.alerts.last)
- req.alerts.last = 1;
-
- buffer_flush(w->response.data);
- buffer_no_cacheable(w->response.data);
- return rrdcontext_to_json_v2(w->response.data, &req, mode);
-}
-
-static int web_client_api_request_v2_alert_transitions(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
- return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_ALERT_TRANSITIONS | CONTEXTS_V2_NODES);
-}
-
-static int web_client_api_request_v2_alerts(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
- return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_ALERTS | CONTEXTS_V2_NODES);
-}
-
-static int web_client_api_request_v2_functions(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
- return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_FUNCTIONS | CONTEXTS_V2_NODES | CONTEXTS_V2_AGENTS | CONTEXTS_V2_VERSIONS);
-}
-
-static int web_client_api_request_v2_versions(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
- return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_VERSIONS);
-}
-
-static int web_client_api_request_v2_q(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
- return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_SEARCH | CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_NODES | CONTEXTS_V2_AGENTS | CONTEXTS_V2_VERSIONS);
-}
-
-static int web_client_api_request_v2_contexts(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
- return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_NODES | CONTEXTS_V2_AGENTS | CONTEXTS_V2_VERSIONS);
-}
-
-static int web_client_api_request_v2_nodes(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
- return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_NODES | CONTEXTS_V2_NODES_INFO);
-}
-
-static int web_client_api_request_v2_info(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
- return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_AGENTS | CONTEXTS_V2_AGENTS_INFO);
-}
-
-static int web_client_api_request_v2_node_instances(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
- return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_NODES | CONTEXTS_V2_NODE_INSTANCES | CONTEXTS_V2_AGENTS | CONTEXTS_V2_AGENTS_INFO | CONTEXTS_V2_VERSIONS);
-}
-
-static int web_client_api_request_v2_weights(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
- return web_client_api_request_weights(host, w, url, WEIGHTS_METHOD_VALUE, WEIGHTS_FORMAT_MULTINODE, 2);
-}
-
-static int web_client_api_request_v2_claim(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
- return api_v2_claim(w, url);
-}
-
-static int web_client_api_request_v2_alert_config(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
- const char *config = NULL;
-
- while(url) {
- char *value = strsep_skip_consecutive_separators(&url, "&");
- if(!value || !*value) continue;
-
- char *name = strsep_skip_consecutive_separators(&value, "=");
- if(!name || !*name) continue;
- if(!value || !*value) continue;
-
- // name and value are now the parameters
- // they are not null and not empty
-
- if(!strcmp(name, "config"))
- config = value;
- }
-
- buffer_flush(w->response.data);
-
- if(!config) {
- w->response.data->content_type = CT_TEXT_PLAIN;
- buffer_strcat(w->response.data, "A config hash ID is required. Add ?config=UUID query param");
- return HTTP_RESP_BAD_REQUEST;
- }
-
- return contexts_v2_alert_config_to_json(w, config);
-}
-
-
-#define GROUP_BY_KEY_MAX_LENGTH 30
-static struct {
- char group_by[GROUP_BY_KEY_MAX_LENGTH + 1];
- char aggregation[GROUP_BY_KEY_MAX_LENGTH + 1];
- char group_by_label[GROUP_BY_KEY_MAX_LENGTH + 1];
-} group_by_keys[MAX_QUERY_GROUP_BY_PASSES];
-
-__attribute__((constructor)) void initialize_group_by_keys(void) {
- for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) {
- snprintfz(group_by_keys[g].group_by, GROUP_BY_KEY_MAX_LENGTH, "group_by[%zu]", g);
- snprintfz(group_by_keys[g].aggregation, GROUP_BY_KEY_MAX_LENGTH, "aggregation[%zu]", g);
- snprintfz(group_by_keys[g].group_by_label, GROUP_BY_KEY_MAX_LENGTH, "group_by_label[%zu]", g);
- }
-}
-
-static int web_client_api_request_v2_data(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
- usec_t received_ut = now_monotonic_usec();
-
- int ret = HTTP_RESP_BAD_REQUEST;
-
- buffer_flush(w->response.data);
-
- char *google_version = "0.6",
- *google_reqId = "0",
- *google_sig = "0",
- *google_out = "json",
- *responseHandler = NULL,
- *outFileName = NULL;
-
- time_t last_timestamp_in_data = 0, google_timestamp = 0;
-
- char *scope_nodes = NULL;
- char *scope_contexts = NULL;
- char *nodes = NULL;
- char *contexts = NULL;
- char *instances = NULL;
- char *dimensions = NULL;
- char *before_str = NULL;
- char *after_str = NULL;
- char *resampling_time_str = NULL;
- char *points_str = NULL;
- char *timeout_str = NULL;
- char *labels = NULL;
- char *alerts = NULL;
- char *time_group_options = NULL;
- char *tier_str = NULL;
- size_t tier = 0;
- RRDR_TIME_GROUPING time_group = RRDR_GROUPING_AVERAGE;
- DATASOURCE_FORMAT format = DATASOURCE_JSON2;
- RRDR_OPTIONS options = RRDR_OPTION_VIRTUAL_POINTS | RRDR_OPTION_JSON_WRAP | RRDR_OPTION_RETURN_JWAR;
-
- struct group_by_pass group_by[MAX_QUERY_GROUP_BY_PASSES] = {
- {
- .group_by = RRDR_GROUP_BY_DIMENSION,
- .group_by_label = NULL,
- .aggregation = RRDR_GROUP_BY_FUNCTION_AVERAGE,
- },
- };
-
- size_t group_by_idx = 0, group_by_label_idx = 0, aggregation_idx = 0;
-
- while(url) {
- char *value = strsep_skip_consecutive_separators(&url, "&");
- if(!value || !*value) continue;
-
- char *name = strsep_skip_consecutive_separators(&value, "=");
- if(!name || !*name) continue;
- if(!value || !*value) continue;
-
- // name and value are now the parameters
- // they are not null and not empty
-
- if(!strcmp(name, "scope_nodes")) scope_nodes = value;
- else if(!strcmp(name, "scope_contexts")) scope_contexts = value;
- else if(!strcmp(name, "nodes")) nodes = value;
- else if(!strcmp(name, "contexts")) contexts = value;
- else if(!strcmp(name, "instances")) instances = value;
- else if(!strcmp(name, "dimensions")) dimensions = value;
- else if(!strcmp(name, "labels")) labels = value;
- else if(!strcmp(name, "alerts")) alerts = value;
- else if(!strcmp(name, "after")) after_str = value;
- else if(!strcmp(name, "before")) before_str = value;
- else if(!strcmp(name, "points")) points_str = value;
- else if(!strcmp(name, "timeout")) timeout_str = value;
- else if(!strcmp(name, "group_by")) {
- group_by[group_by_idx++].group_by = group_by_parse(value);
- if(group_by_idx >= MAX_QUERY_GROUP_BY_PASSES)
- group_by_idx = MAX_QUERY_GROUP_BY_PASSES - 1;
- }
- else if(!strcmp(name, "group_by_label")) {
- group_by[group_by_label_idx++].group_by_label = value;
- if(group_by_label_idx >= MAX_QUERY_GROUP_BY_PASSES)
- group_by_label_idx = MAX_QUERY_GROUP_BY_PASSES - 1;
- }
- else if(!strcmp(name, "aggregation")) {
- group_by[aggregation_idx++].aggregation = group_by_aggregate_function_parse(value);
- if(aggregation_idx >= MAX_QUERY_GROUP_BY_PASSES)
- aggregation_idx = MAX_QUERY_GROUP_BY_PASSES - 1;
- }
- else if(!strcmp(name, "format")) format = web_client_api_request_v1_data_format(value);
- else if(!strcmp(name, "options")) options |= rrdr_options_parse(value);
- else if(!strcmp(name, "time_group")) time_group = time_grouping_parse(value, RRDR_GROUPING_AVERAGE);
- else if(!strcmp(name, "time_group_options")) time_group_options = value;
- else if(!strcmp(name, "time_resampling")) resampling_time_str = value;
- else if(!strcmp(name, "tier")) tier_str = value;
- else if(!strcmp(name, "callback")) responseHandler = value;
- else if(!strcmp(name, "filename")) outFileName = value;
- else if(!strcmp(name, "tqx")) {
- // parse Google Visualization API options
- // https://developers.google.com/chart/interactive/docs/dev/implementing_data_source
- char *tqx_name, *tqx_value;
-
- while(value) {
- tqx_value = strsep_skip_consecutive_separators(&value, ";");
- if(!tqx_value || !*tqx_value) continue;
-
- tqx_name = strsep_skip_consecutive_separators(&tqx_value, ":");
- if(!tqx_name || !*tqx_name) continue;
- if(!tqx_value || !*tqx_value) continue;
-
- if(!strcmp(tqx_name, "version"))
- google_version = tqx_value;
- else if(!strcmp(tqx_name, "reqId"))
- google_reqId = tqx_value;
- else if(!strcmp(tqx_name, "sig")) {
- google_sig = tqx_value;
- google_timestamp = strtoul(google_sig, NULL, 0);
- }
- else if(!strcmp(tqx_name, "out")) {
- google_out = tqx_value;
- format = web_client_api_request_v1_data_google_format(google_out);
- }
- else if(!strcmp(tqx_name, "responseHandler"))
- responseHandler = tqx_value;
- else if(!strcmp(tqx_name, "outFileName"))
- outFileName = tqx_value;
- }
- }
- else {
- for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) {
- if(!strcmp(name, group_by_keys[g].group_by))
- group_by[g].group_by = group_by_parse(value);
- else if(!strcmp(name, group_by_keys[g].group_by_label))
- group_by[g].group_by_label = value;
- else if(!strcmp(name, group_by_keys[g].aggregation))
- group_by[g].aggregation = group_by_aggregate_function_parse(value);
- }
- }
- }
-
- // validate the google parameters given
- fix_google_param(google_out);
- fix_google_param(google_sig);
- fix_google_param(google_reqId);
- fix_google_param(google_version);
- fix_google_param(responseHandler);
- fix_google_param(outFileName);
-
- for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) {
- if (group_by[g].group_by_label && *group_by[g].group_by_label)
- group_by[g].group_by |= RRDR_GROUP_BY_LABEL;
- }
-
- if(group_by[0].group_by == RRDR_GROUP_BY_NONE)
- group_by[0].group_by = RRDR_GROUP_BY_DIMENSION;
-
- for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) {
- if ((group_by[g].group_by & ~(RRDR_GROUP_BY_DIMENSION)) || (options & RRDR_OPTION_PERCENTAGE)) {
- options |= RRDR_OPTION_ABSOLUTE;
- break;
- }
- }
-
- if(options & RRDR_OPTION_DEBUG)
- options &= ~RRDR_OPTION_MINIFY;
-
- if(tier_str && *tier_str) {
- tier = str2ul(tier_str);
- if(tier < storage_tiers)
- options |= RRDR_OPTION_SELECTED_TIER;
- else
- tier = 0;
- }
-
- time_t before = (before_str && *before_str)?str2l(before_str):0;
- time_t after = (after_str && *after_str) ?str2l(after_str):-600;
- size_t points = (points_str && *points_str)?str2u(points_str):0;
- int timeout = (timeout_str && *timeout_str)?str2i(timeout_str): 0;
- time_t resampling_time = (resampling_time_str && *resampling_time_str) ? str2l(resampling_time_str) : 0;
-
- QUERY_TARGET_REQUEST qtr = {
- .version = 2,
- .scope_nodes = scope_nodes,
- .scope_contexts = scope_contexts,
- .after = after,
- .before = before,
- .host = NULL,
- .st = NULL,
- .nodes = nodes,
- .contexts = contexts,
- .instances = instances,
- .dimensions = dimensions,
- .alerts = alerts,
- .timeout_ms = timeout,
- .points = points,
- .format = format,
- .options = options,
- .time_group_method = time_group,
- .time_group_options = time_group_options,
- .resampling_time = resampling_time,
- .tier = tier,
- .chart_label_key = NULL,
- .labels = labels,
- .query_source = QUERY_SOURCE_API_DATA,
- .priority = STORAGE_PRIORITY_NORMAL,
- .received_ut = received_ut,
-
- .interrupt_callback = web_client_interrupt_callback,
- .interrupt_callback_data = w,
-
- .transaction = &w->transaction,
- };
-
- for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++)
- qtr.group_by[g] = group_by[g];
-
- QUERY_TARGET *qt = query_target_create(&qtr);
- ONEWAYALLOC *owa = NULL;
-
- if(!qt) {
- buffer_sprintf(w->response.data, "Failed to prepare the query.");
- ret = HTTP_RESP_INTERNAL_SERVER_ERROR;
- goto cleanup;
- }
-
- web_client_timeout_checkpoint_set(w, timeout);
- if(web_client_timeout_checkpoint_and_check(w, NULL)) {
- ret = w->response.code;
- goto cleanup;
- }
-
- if(outFileName && *outFileName) {
- buffer_sprintf(w->response.header, "Content-Disposition: attachment; filename=\"%s\"\r\n", outFileName);
- netdata_log_debug(D_WEB_CLIENT, "%llu: generating outfilename header: '%s'", w->id, outFileName);
- }
-
- if(format == DATASOURCE_DATATABLE_JSONP) {
- if(responseHandler == NULL)
- responseHandler = "google.visualization.Query.setResponse";
-
- netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: GOOGLE JSON/JSONP: version = '%s', reqId = '%s', sig = '%s', out = '%s', responseHandler = '%s', outFileName = '%s'",
- w->id, google_version, google_reqId, google_sig, google_out, responseHandler, outFileName
- );
-
- buffer_sprintf(
- w->response.data,
- "%s({version:'%s',reqId:'%s',status:'ok',sig:'%"PRId64"',table:",
- responseHandler,
- google_version,
- google_reqId,
- (int64_t)now_realtime_sec());
- }
- else if(format == DATASOURCE_JSONP) {
- if(responseHandler == NULL)
- responseHandler = "callback";
-
- buffer_strcat(w->response.data, responseHandler);
- buffer_strcat(w->response.data, "(");
- }
-
- owa = onewayalloc_create(0);
- ret = data_query_execute(owa, w->response.data, qt, &last_timestamp_in_data);
-
- if(format == DATASOURCE_DATATABLE_JSONP) {
- if(google_timestamp < last_timestamp_in_data)
- buffer_strcat(w->response.data, "});");
-
- else {
- // the client already has the latest data
- buffer_flush(w->response.data);
- buffer_sprintf(w->response.data,
- "%s({version:'%s',reqId:'%s',status:'error',errors:[{reason:'not_modified',message:'Data not modified'}]});",
- responseHandler, google_version, google_reqId);
- }
- }
- else if(format == DATASOURCE_JSONP)
- buffer_strcat(w->response.data, ");");
-
- if(qt->internal.relative)
- buffer_no_cacheable(w->response.data);
- else
- buffer_cacheable(w->response.data);
-
-cleanup:
- query_target_release(qt);
- onewayalloc_destroy(owa);
- return ret;
-}
-
-static int web_client_api_request_v2_webrtc(RRDHOST *host __maybe_unused, struct web_client *w, char *url __maybe_unused) {
- return webrtc_new_connection(buffer_tostring(w->payload), w->response.data);
-}
-
-static int web_client_api_request_v2_progress(RRDHOST *host __maybe_unused, struct web_client *w, char *url) {
- char *transaction = NULL;
-
- while(url) {
- char *value = strsep_skip_consecutive_separators(&url, "&");
- if(!value || !*value) continue;
-
- char *name = strsep_skip_consecutive_separators(&value, "=");
- if(!name || !*name) continue;
- if(!value || !*value) continue;
-
- // name and value are now the parameters
- // they are not null and not empty
-
- if(!strcmp(name, "transaction")) transaction = value;
- }
-
- nd_uuid_t tr;
- uuid_parse_flexi(transaction, tr);
-
- rrd_function_call_progresser(&tr);
-
- return web_api_v2_report_progress(&tr, w->response.data);
-}
+#include "v1/api_v1_calls.h"
+#include "v2/api_v2_calls.h"
+#include "v3/api_v3_calls.h"
static struct web_api_command api_commands_v2[] = {
+#if defined(ENABLE_API_v2)
// time-series multi-node multi-instance data APIs
{
.api = "data",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_METRICS,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v2_data,
+ .callback = api_v2_data,
.allow_subpaths = 0
},
{
.api = "weights",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_METRICS,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v2_weights,
+ .callback = api_v2_weights,
.allow_subpaths = 0
},
@@ -606,18 +29,18 @@ static struct web_api_command api_commands_v2[] = {
{
.api = "contexts",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_METRICS,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v2_contexts,
+ .callback = api_v2_contexts,
.allow_subpaths = 0
},
{
// full text search
.api = "q",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_METRICS,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v2_q,
+ .callback = api_v2_q,
.allow_subpaths = 0
},
@@ -625,25 +48,25 @@ static struct web_api_command api_commands_v2[] = {
{
.api = "alerts",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_ALERTS,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v2_alerts,
+ .callback = api_v2_alerts,
.allow_subpaths = 0
},
{
.api = "alert_transitions",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_ALERTS,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v2_alert_transitions,
+ .callback = api_v2_alert_transitions,
.allow_subpaths = 0
},
{
.api = "alert_config",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_ALERTS,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v2_alert_config,
+ .callback = api_v2_alert_config,
.allow_subpaths = 0
},
@@ -651,41 +74,41 @@ static struct web_api_command api_commands_v2[] = {
{
.api = "info",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_NOCHECK,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v2_info,
+ .callback = api_v2_info,
.allow_subpaths = 0
},
{
.api = "nodes",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_NODES,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v2_nodes,
+ .callback = api_v2_nodes,
.allow_subpaths = 0
},
{
.api = "node_instances",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_NODES,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v2_node_instances,
+ .callback = api_v2_node_instances,
.allow_subpaths = 0
},
{
.api = "versions",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_NODES,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v2_versions,
+ .callback = api_v2_versions,
.allow_subpaths = 0
},
{
.api = "progress",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_NOCHECK,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v2_progress,
+ .callback = api_v2_progress,
.allow_subpaths = 0
},
@@ -693,9 +116,9 @@ static struct web_api_command api_commands_v2[] = {
{
.api = "functions",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_FUNCTIONS,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v2_functions,
+ .callback = api_v2_functions,
.allow_subpaths = 0
},
@@ -705,7 +128,7 @@ static struct web_api_command api_commands_v2[] = {
.hash = 0,
.acl = HTTP_ACL_ACLK | ACL_DEV_OPEN_ACCESS,
.access = HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE,
- .callback = web_client_api_request_v2_webrtc,
+ .callback = api_v2_webrtc,
.allow_subpaths = 0
},
@@ -715,7 +138,7 @@ static struct web_api_command api_commands_v2[] = {
.hash = 0,
.acl = HTTP_ACL_NOCHECK,
.access = HTTP_ACCESS_NONE,
- .callback = web_client_api_request_v2_claim,
+ .callback = api_v2_claim,
.allow_subpaths = 0
},
{
@@ -731,17 +154,18 @@ static struct web_api_command api_commands_v2[] = {
.hash = 0,
.acl = HTTP_ACL_ACLK | ACL_DEV_OPEN_ACCESS,
.access = HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE,
- .callback = api_v2_bearer_token,
+ .callback = api_v2_bearer_get_token,
.allow_subpaths = 0
},
+#endif
// Netdata branding APIs
{
.api = "ilove.svg",
.hash = 0,
- .acl = HTTP_ACL_DASHBOARD,
+ .acl = HTTP_ACL_NOCHECK,
.access = HTTP_ACCESS_ANONYMOUS_DATA,
- .callback = web_client_api_request_v2_ilove,
+ .callback = api_v2_ilove,
.allow_subpaths = 0
},
diff --git a/src/web/api/web_api_v3.c b/src/web/api/web_api_v3.c
new file mode 100644
index 00000000..a1092471
--- /dev/null
+++ b/src/web/api/web_api_v3.c
@@ -0,0 +1,263 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "web_api_v3.h"
+#include "v1/api_v1_calls.h"
+#include "v2/api_v2_calls.h"
+#include "v3/api_v3_calls.h"
+
+static struct web_api_command api_commands_v3[] = {
+ // time-series multi-node multi-instance data APIs
+ {
+ .api = "data",
+ .hash = 0,
+ .acl = HTTP_ACL_METRICS,
+ .access = HTTP_ACCESS_ANONYMOUS_DATA,
+ .callback = api_v2_data,
+ .allow_subpaths = 0
+ },
+ // badges
+ {
+ .api = "badge.svg",
+ .hash = 0,
+ .acl = HTTP_ACL_BADGES,
+ .access = HTTP_ACCESS_ANONYMOUS_DATA,
+ .callback = api_v1_badge,
+ .allow_subpaths = 0
+ },
+ // scoring engine
+ {
+ .api = "weights",
+ .hash = 0,
+ .acl = HTTP_ACL_METRICS,
+ .access = HTTP_ACCESS_ANONYMOUS_DATA,
+ .callback = api_v2_weights,
+ .allow_subpaths = 0
+ },
+ // exporting API
+ {
+ .api = "allmetrics",
+ .hash = 0,
+ .acl = HTTP_ACL_METRICS,
+ .access = HTTP_ACCESS_ANONYMOUS_DATA,
+ .callback = api_v1_allmetrics,
+ .allow_subpaths = 0
+ },
+
+ // time-series multi-node multi-instance metadata APIs
+ {
+ .api = "context",
+ .hash = 0,
+ .acl = HTTP_ACL_METRICS,
+ .access = HTTP_ACCESS_ANONYMOUS_DATA,
+ .callback = api_v1_context,
+ .allow_subpaths = 0
+ },
+ {
+ .api = "contexts",
+ .hash = 0,
+ .acl = HTTP_ACL_METRICS,
+ .access = HTTP_ACCESS_ANONYMOUS_DATA,
+ .callback = api_v2_contexts,
+ .allow_subpaths = 0
+ },
+
+ // fulltext search
+ {
+ .api = "q",
+ .hash = 0,
+ .acl = HTTP_ACL_METRICS,
+ .access = HTTP_ACCESS_ANONYMOUS_DATA,
+ .callback = api_v2_q,
+ .allow_subpaths = 0
+ },
+
+ // multi-node multi-instance alerts APIs
+ {
+ .api = "alerts",
+ .hash = 0,
+ .acl = HTTP_ACL_ALERTS,
+ .access = HTTP_ACCESS_ANONYMOUS_DATA,
+ .callback = api_v2_alerts,
+ .allow_subpaths = 0
+ },
+ {
+ .api = "alert_transitions",
+ .hash = 0,
+ .acl = HTTP_ACL_ALERTS,
+ .access = HTTP_ACCESS_ANONYMOUS_DATA,
+ .callback = api_v2_alert_transitions,
+ .allow_subpaths = 0
+ },
+ {
+ .api = "alert_config",
+ .hash = 0,
+ .acl = HTTP_ACL_ALERTS,
+ .access = HTTP_ACCESS_ANONYMOUS_DATA,
+ .callback = api_v2_alert_config,
+ .allow_subpaths = 0
+ },
+ {
+ .api = "variable",
+ .hash = 0,
+ .acl = HTTP_ACL_ALERTS,
+ .access = HTTP_ACCESS_ANONYMOUS_DATA,
+ .callback = api_v1_variable,
+ .allow_subpaths = 0
+ },
+
+ // agent information APIs
+ {
+ .api = "info",
+ .hash = 0,
+ .acl = HTTP_ACL_NOCHECK,
+ .access = HTTP_ACCESS_NONE,
+ .callback = api_v2_info,
+ .allow_subpaths = 0
+ },
+ {
+ .api = "nodes",
+ .hash = 0,
+ .acl = HTTP_ACL_NODES,
+ .access = HTTP_ACCESS_ANONYMOUS_DATA,
+ .callback = api_v2_nodes,
+ .allow_subpaths = 0
+ },
+ {
+ .api = "node_instances",
+ .hash = 0,
+ .acl = HTTP_ACL_NODES,
+ .access = HTTP_ACCESS_ANONYMOUS_DATA,
+ .callback = api_v2_node_instances,
+ .allow_subpaths = 0
+ },
+ {
+ .api = "versions",
+ .hash = 0,
+ .acl = HTTP_ACL_NOCHECK,
+ .access = HTTP_ACCESS_ANONYMOUS_DATA,
+ .callback = api_v2_versions,
+ .allow_subpaths = 0
+ },
+ {
+ .api = "progress",
+ .hash = 0,
+ .acl = HTTP_ACL_NOCHECK,
+ .access = HTTP_ACCESS_ANONYMOUS_DATA,
+ .callback = api_v2_progress,
+ .allow_subpaths = 0
+ },
+
+ // functions APIs
+ {
+ .api = "function",
+ .hash = 0,
+ .acl = HTTP_ACL_FUNCTIONS,
+ .access = HTTP_ACCESS_ANONYMOUS_DATA,
+ .callback = api_v1_function,
+ .allow_subpaths = 0
+ },
+ {
+ .api = "functions",
+ .hash = 0,
+ .acl = HTTP_ACL_FUNCTIONS,
+ .access = HTTP_ACCESS_ANONYMOUS_DATA,
+ .callback = api_v2_functions,
+ .allow_subpaths = 0
+ },
+
+ // dyncfg APIs
+ {
+ .api = "config",
+ .hash = 0,
+ .acl = HTTP_ACL_DYNCFG,
+ .access = HTTP_ACCESS_ANONYMOUS_DATA,
+ .callback = api_v1_config,
+ .allow_subpaths = 0
+ },
+
+ // settings APIs
+ {
+ .api = "settings",
+ .hash = 0,
+ .acl = HTTP_ACL_NOCHECK,
+ .access = HTTP_ACCESS_ANONYMOUS_DATA,
+ .callback = api_v3_settings,
+ .allow_subpaths = 0
+ },
+
+ // WebRTC APIs
+ {
+ .api = "rtc_offer",
+ .hash = 0,
+ .acl = HTTP_ACL_ACLK | ACL_DEV_OPEN_ACCESS,
+ .access = HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE,
+ .callback = api_v2_webrtc,
+ .allow_subpaths = 0
+ },
+
+ // management APIs
+ {
+ .api = "claim",
+ .hash = 0,
+ .acl = HTTP_ACL_NOCHECK,
+ .access = HTTP_ACCESS_NONE,
+ .callback = api_v3_claim,
+ .allow_subpaths = 0
+ },
+ {
+ .api = "bearer_protection",
+ .hash = 0,
+ .acl = HTTP_ACL_ACLK | ACL_DEV_OPEN_ACCESS,
+ .access = HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_VIEW_AGENT_CONFIG | HTTP_ACCESS_EDIT_AGENT_CONFIG,
+ .callback = api_v2_bearer_protection,
+ .allow_subpaths = 0
+ },
+ {
+ .api = "bearer_get_token",
+ .hash = 0,
+ .acl = HTTP_ACL_ACLK | ACL_DEV_OPEN_ACCESS,
+ .access = HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE,
+ .callback = api_v2_bearer_get_token,
+ .allow_subpaths = 0
+ },
+ {
+ .api = "me",
+ .hash = 0,
+ .acl = HTTP_ACL_NOCHECK,
+ .access = HTTP_ACCESS_NONE,
+ .callback = api_v3_me,
+ .allow_subpaths = 0
+ },
+
+ // Netdata branding APIs
+ {
+ .api = "ilove.svg",
+ .hash = 0,
+ .acl = HTTP_ACL_NOCHECK,
+ .access = HTTP_ACCESS_NONE,
+ .callback = api_v2_ilove,
+ .allow_subpaths = 0
+ },
+
+ {// terminator
+ .api = NULL,
+ .hash = 0,
+ .acl = HTTP_ACL_NONE,
+ .access = HTTP_ACCESS_NONE,
+ .callback = NULL,
+ .allow_subpaths = 0
+ },
+};
+
+inline int web_client_api_request_v3(RRDHOST *host, struct web_client *w, char *url_path_endpoint) {
+ static int initialized = 0;
+
+ if(unlikely(initialized == 0)) {
+ initialized = 1;
+
+ for(int i = 0; api_commands_v3[i].api ; i++)
+ api_commands_v3[i].hash = simple_hash(api_commands_v3[i].api);
+ }
+
+ return web_client_api_request_vX(host, w, url_path_endpoint, api_commands_v3);
+}
diff --git a/src/web/api/web_api_v3.h b/src/web/api/web_api_v3.h
new file mode 100644
index 00000000..32fa4cd1
--- /dev/null
+++ b/src/web/api/web_api_v3.h
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_WEB_API_V3_H
+#define NETDATA_WEB_API_V3_H
+
+#include "web_api.h"
+
+struct web_client;
+
+int web_client_api_request_v3(RRDHOST *host, struct web_client *w, char *url_path_endpoint);
+
+#endif //NETDATA_WEB_API_V3_H