diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2019-08-04 08:56:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2019-08-04 08:56:44 +0000 |
commit | 34f488f41ee820371159111bf621f11d0f54f669 (patch) | |
tree | 13eea1c3aa3d905ec929691bbf23d8b90bef1dcb /web | |
parent | Adding upstream version 1.16.0. (diff) | |
download | netdata-upstream/1.16.1.tar.xz netdata-upstream/1.16.1.zip |
Adding upstream version 1.16.1.upstream/1.16.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | web/api/badges/web_buffer_svg.c | 216 | ||||
-rw-r--r-- | web/api/health/README.md | 49 | ||||
-rw-r--r-- | web/api/health/health_cmdapi.c | 1 | ||||
-rw-r--r-- | web/api/web_api_v1.c | 14 | ||||
-rw-r--r-- | web/gui/console.html | 2 | ||||
-rw-r--r-- | web/gui/dashboard.js | 8 | ||||
-rw-r--r-- | web/gui/dashboard_info.js | 2 | ||||
-rw-r--r-- | web/gui/demosites.html | 37 | ||||
-rw-r--r-- | web/gui/images/packaging-beta-tag.svg | 42 | ||||
-rw-r--r-- | web/gui/index.html | 59 | ||||
-rw-r--r-- | web/gui/infographic.html | 8 | ||||
-rw-r--r-- | web/gui/main.css | 344 | ||||
-rw-r--r-- | web/gui/main.js | 11 | ||||
-rw-r--r-- | web/gui/src/dashboard.js/main.js | 8 | ||||
-rw-r--r-- | web/server/README.md | 60 | ||||
-rw-r--r-- | web/server/web_client.c | 315 | ||||
-rw-r--r-- | web/server/web_client.h | 32 |
17 files changed, 646 insertions, 562 deletions
diff --git a/web/api/badges/web_buffer_svg.c b/web/api/badges/web_buffer_svg.c index b24fddedf..4f9826fb2 100644 --- a/web/api/badges/web_buffer_svg.c +++ b/web/api/badges/web_buffer_svg.c @@ -11,7 +11,7 @@ * https://github.com/badges/shields/blob/master/measure-text.js */ -static double verdana11_widths[256] = { +static double verdana11_widths[128] = { [0] = 0.0, [1] = 0.0, [2] = 0.0, @@ -139,157 +139,36 @@ static double verdana11_widths[256] = { [124] = 4.9951171875, // | [125] = 6.982421875, // } [126] = 9.001953125, // ~ - [127] = 0.0, - [128] = 0.0, - [129] = 0.0, - [130] = 0.0, - [131] = 0.0, - [132] = 0.0, - [133] = 0.0, - [134] = 0.0, - [135] = 0.0, - [136] = 0.0, - [137] = 0.0, - [138] = 0.0, - [139] = 0.0, - [140] = 0.0, - [141] = 0.0, - [142] = 0.0, - [143] = 0.0, - [144] = 0.0, - [145] = 0.0, - [146] = 0.0, - [147] = 0.0, - [148] = 0.0, - [149] = 0.0, - [150] = 0.0, - [151] = 0.0, - [152] = 0.0, - [153] = 0.0, - [154] = 0.0, - [155] = 0.0, - [156] = 0.0, - [157] = 0.0, - [158] = 0.0, - [159] = 0.0, - [160] = 0.0, - [161] = 0.0, - [162] = 0.0, - [163] = 0.0, - [164] = 0.0, - [165] = 0.0, - [166] = 0.0, - [167] = 0.0, - [168] = 0.0, - [169] = 0.0, - [170] = 0.0, - [171] = 0.0, - [172] = 0.0, - [173] = 0.0, - [174] = 0.0, - [175] = 0.0, - [176] = 0.0, - [177] = 0.0, - [178] = 0.0, - [179] = 0.0, - [180] = 0.0, - [181] = 0.0, - [182] = 0.0, - [183] = 0.0, - [184] = 0.0, - [185] = 0.0, - [186] = 0.0, - [187] = 0.0, - [188] = 0.0, - [189] = 0.0, - [190] = 0.0, - [191] = 0.0, - [192] = 0.0, - [193] = 0.0, - [194] = 0.0, - [195] = 0.0, - [196] = 0.0, - [197] = 0.0, - [198] = 0.0, - [199] = 0.0, - [200] = 0.0, - [201] = 0.0, - [202] = 0.0, - [203] = 0.0, - [204] = 0.0, - [205] = 0.0, - [206] = 0.0, - [207] = 0.0, - [208] = 0.0, - [209] = 0.0, - [210] = 0.0, - [211] = 0.0, - [212] = 0.0, - [213] = 0.0, - [214] = 0.0, - [215] = 0.0, - [216] = 0.0, - [217] = 0.0, - [218] = 0.0, - [219] = 0.0, - [220] = 0.0, - [221] = 0.0, - [222] = 0.0, - [223] = 0.0, - [224] = 0.0, - [225] = 0.0, - [226] = 0.0, - [227] = 0.0, - [228] = 0.0, - [229] = 0.0, - [230] = 0.0, - [231] = 0.0, - [232] = 0.0, - [233] = 0.0, - [234] = 0.0, - [235] = 0.0, - [236] = 0.0, - [237] = 0.0, - [238] = 0.0, - [239] = 0.0, - [240] = 0.0, - [241] = 0.0, - [242] = 0.0, - [243] = 0.0, - [244] = 0.0, - [245] = 0.0, - [246] = 0.0, - [247] = 0.0, - [248] = 0.0, - [249] = 0.0, - [250] = 0.0, - [251] = 0.0, - [252] = 0.0, - [253] = 0.0, - [254] = 0.0, - [255] = 0.0 + [127] = 0.0 }; // find the width of the string using the verdana 11points font -// re-write the string in place, skiping zero-length characters -static inline double verdana11_width(char *s) { +static inline double verdana11_width(const char *s, float em_size) { double w = 0.0; - char *d = s; while(*s) { - double t = verdana11_widths[(unsigned char)*s]; - if(t == 0.0) + // if UTF8 multibyte char found and guess it's width equal 1em + // as label width will be updated with JavaScript this is not so important + + // TODO: maybe move UTF8 functions from url.c to separate util in libnetdata + // then use url_utf8_get_byte_length etc. + if(IS_UTF8_STARTBYTE(*s)) { s++; + while(IS_UTF8_BYTE(*s) && !IS_UTF8_STARTBYTE(*s)){ + s++; + } + w += em_size; + } else { - w += t + VERDANA_KERNING; - if(d != s) - *d++ = *s++; - else - d = ++s; + if(likely(!(*s & 0x80))){ // Byte 1XXX XXXX is not valid in UTF8 + double t = verdana11_widths[(unsigned char)*s]; + if(t != 0.0) + w += t + VERDANA_KERNING; + } + s++; } } - *d = '\0'; w -= VERDANA_KERNING; w += VERDANA_PADDING; return w; @@ -810,8 +689,7 @@ static inline void calc_colorz(const char *color, char *final, size_t len, calcu #define COLOR_STRING_SIZE 100 void buffer_svg(BUFFER *wb, const char *label, calculated_number value, const char *units, const char *label_color, const char *value_color, int precision, int scale, uint32_t options) { - char label_buffer[LABEL_STRING_SIZE + 1] - , value_color_buffer[COLOR_STRING_SIZE + 1] + char value_color_buffer[COLOR_STRING_SIZE + 1] , value_string[VALUE_STRING_SIZE + 1] , label_escaped[LABEL_STRING_SIZE + 1] , value_escaped[VALUE_STRING_SIZE + 1] @@ -831,14 +709,11 @@ void buffer_svg(BUFFER *wb, const char *label, calculated_number value, const ch calc_colorz(value_color, value_color_buffer, COLOR_STRING_SIZE, value); format_value_and_unit(value_string, VALUE_STRING_SIZE, (options & RRDR_OPTION_DISPLAY_ABS)?calculated_number_fabs(value):value, units, precision); - // we need to copy the label, since verdana11_width may write to it - strncpyz(label_buffer, label, LABEL_STRING_SIZE); - - label_width = verdana11_width(label_buffer) + (BADGE_HORIZONTAL_PADDING * 2); - value_width = verdana11_width(value_string) + (BADGE_HORIZONTAL_PADDING * 2); + label_width = verdana11_width(label, font_size) + (BADGE_HORIZONTAL_PADDING * 2); + value_width = verdana11_width(value_string, font_size) + (BADGE_HORIZONTAL_PADDING * 2); total_width = label_width + value_width; - escape_xmlz(label_escaped, label_buffer, LABEL_STRING_SIZE); + escape_xmlz(label_escaped, label, LABEL_STRING_SIZE); escape_xmlz(value_escaped, value_string, VALUE_STRING_SIZE); escape_xmlz(label_color_escaped, color_map(label_color), COLOR_STRING_SIZE); escape_xmlz(value_color_escaped, color_map(value_color_buffer), COLOR_STRING_SIZE); @@ -862,19 +737,43 @@ void buffer_svg(BUFFER *wb, const char *label, calculated_number value, const ch "<stop offset=\"1\" stop-opacity=\".1\"/>" "</linearGradient>" "<mask id=\"round\">" - "<rect width=\"%0.2f\" height=\"%0.2f\" rx=\"%0.2f\" fill=\"#fff\"/>" + "<rect class=\"bdge-ttl-width\" width=\"%0.2f\" height=\"%0.2f\" rx=\"%0.2f\" fill=\"#fff\"/>" "</mask>" "<g mask=\"url(#round)\">" - "<rect width=\"%0.2f\" height=\"%0.2f\" fill=\"%s\"/>" - "<rect x=\"%0.2f\" width=\"%0.2f\" height=\"%0.2f\" fill=\"%s\"/>" - "<rect width=\"%0.2f\" height=\"%0.2f\" fill=\"url(#smooth)\"/>" + "<rect class=\"bdge-rect-lbl\" width=\"%0.2f\" height=\"%0.2f\" fill=\"%s\"/>" + "<rect class=\"bdge-rect-val\" x=\"%0.2f\" width=\"%0.2f\" height=\"%0.2f\" fill=\"%s\"/>" + "<rect class=\"bdge-ttl-width\" width=\"%0.2f\" height=\"%0.2f\" fill=\"url(#smooth)\"/>" "</g>" "<g fill=\"#fff\" text-anchor=\"middle\" font-family=\"DejaVu Sans,Verdana,Geneva,sans-serif\" font-size=\"%0.2f\">" - "<text x=\"%0.2f\" y=\"%0.0f\" fill=\"#010101\" fill-opacity=\".3\">%s</text>" - "<text x=\"%0.2f\" y=\"%0.0f\">%s</text>" - "<text x=\"%0.2f\" y=\"%0.0f\" fill=\"#010101\" fill-opacity=\".3\">%s</text>" - "<text x=\"%0.2f\" y=\"%0.0f\">%s</text>" + "<text class=\"bdge-lbl-lbl\" x=\"%0.2f\" y=\"%0.0f\" fill=\"#010101\" fill-opacity=\".3\">%s</text>" + "<text class=\"bdge-lbl-lbl\" x=\"%0.2f\" y=\"%0.0f\">%s</text>" + "<text class=\"bdge-lbl-val\" x=\"%0.2f\" y=\"%0.0f\" fill=\"#010101\" fill-opacity=\".3\">%s</text>" + "<text class=\"bdge-lbl-val\" x=\"%0.2f\" y=\"%0.0f\">%s</text>" "</g>" + "<script type=\"text/javascript\">" + "var bdg_horiz_padding = %d;" + "function netdata_bdge_each(list, attr, value){" + "Array.prototype.forEach.call(list, function(el){" + "el.setAttribute(attr, value);" + "});" + "};" + "var this_svg = document.currentScript.closest(\"svg\");" + "var elem_lbl = this_svg.getElementsByClassName(\"bdge-lbl-lbl\");" + "var elem_val = this_svg.getElementsByClassName(\"bdge-lbl-val\");" + "var lbl_size = elem_lbl[0].getBBox();" + "var val_size = elem_val[0].getBBox();" + "var width_total = lbl_size.width + bdg_horiz_padding*2;" + "this_svg.getElementsByClassName(\"bdge-rect-lbl\")[0].setAttribute(\"width\", width_total);" + "netdata_bdge_each(elem_lbl, \"x\", (lbl_size.width / 2) + bdg_horiz_padding);" + "netdata_bdge_each(elem_val, \"x\", width_total + (val_size.width / 2) + bdg_horiz_padding);" + "var val_rect = this_svg.getElementsByClassName(\"bdge-rect-val\")[0];" + "val_rect.setAttribute(\"width\", val_size.width + bdg_horiz_padding*2);" + "val_rect.setAttribute(\"x\", width_total);" + "width_total += val_size.width + bdg_horiz_padding*2;" + "var width_update_elems = this_svg.getElementsByClassName(\"bdge-ttl-width\");" + "netdata_bdge_each(width_update_elems, \"width\", width_total);" + "this_svg.setAttribute(\"width\", width_total);" + "</script>" "</svg>", total_width, height, total_width, height, round_corner, @@ -885,7 +784,8 @@ void buffer_svg(BUFFER *wb, const char *label, calculated_number value, const ch label_width / 2, ceil(height - text_offset), label_escaped, label_width / 2, ceil(height - text_offset - 1.0), label_escaped, label_width + value_width / 2 -1, ceil(height - text_offset), value_escaped, - label_width + value_width / 2 -1, ceil(height - text_offset - 1.0), value_escaped); + label_width + value_width / 2 -1, ceil(height - text_offset - 1.0), value_escaped, + BADGE_HORIZONTAL_PADDING ); } int web_client_api_request_v1_badge(RRDHOST *host, struct web_client *w, char *url) { diff --git a/web/api/health/README.md b/web/api/health/README.md index 66a80d5f6..0b4f79f38 100644 --- a/web/api/health/README.md +++ b/web/api/health/README.md @@ -50,35 +50,39 @@ From Netdata v1.16.0 and beyond, the configuration controlled via the API comman Specifically, the API allows you to: - Disable health checks completely. Alarm conditions will not be evaluated at all and no entries will be added to the alarm log. - Silence alarm notifications. Alarm conditions will be evaluated, the alarms will appear in the log and the netdata UI will show the alarms as active, but no notifications will be sent. - - Disable or Silence specific alarms that match selectors on alarm/template name, chart, context, host and family. + - Disable or Silence specific alarms that match selectors on alarm/template name, chart, context, host and family. The API is available by default, but it is protected by an `api authorization token` that is stored in the file you will see in the following entry of `http://localhost:19999/netdata.conf`: -```bash +``` [registry] # netdata management api key file = /var/lib/netdata/netdata.api.key ``` -You can access the API via GET requests, by adding the bearer token to an `Authorization` http header, like this: +You can access the API via GET requests, by adding the bearer token to an `Authorization` http header, like this: ``` -curl "http://myserver/api/v1/manage/health?cmd=RESET" -H "X-Auth-Token: Mytoken" +curl "http://myserver/api/v1/manage/health?cmd=RESET" -H "X-Auth-Token: Mytoken" ``` -The command `RESET` just returns netdata to the default operation, with all health checks and notifications enabled. +By default access to the health management API is only allowed from `localhost`. Accessing the API from anything else will return a 403 error with the message `You are not allowed to access this resource.`. You can change permissions by editing the `allow management from` variable in netdata.conf within the [web] section. See [web server access lists](../../server/#access-lists) for more information. + +The command `RESET` just returns netdata to the default operation, with all health checks and notifications enabled. If you've configured and entered your token correclty, you should see the plain text response `All health checks and notifications are enabled`. ### Disable or silence all alarms If all you need is temporarily disable all health checks, then you issue the following before your maintenance period starts: + ``` -curl "http://myserver/api/v1/manage/health?cmd=DISABLE ALL" -H "X-Auth-Token: Mytoken" +curl "http://myserver/api/v1/manage/health?cmd=DISABLE ALL" -H "X-Auth-Token: Mytoken" ``` + The effect of disabling health checks is that the alarm criteria are not evaluated at all and nothing is written in the alarm log. If you want the health checks to be running but to not receive any notifications during your maintenance period, you can instead use this: ``` -curl "http://myserver/api/v1/manage/health?cmd=SILENCE ALL" -H "X-Auth-Token: Mytoken" +curl "http://myserver/api/v1/manage/health?cmd=SILENCE ALL" -H "X-Auth-Token: Mytoken" ``` Alarms may then still be raised and logged in netdata, so you'll be able to see them via the UI. @@ -86,44 +90,44 @@ Alarms may then still be raised and logged in netdata, so you'll be able to see Regardless of the option you choose, at the end of your maintenance period you revert to the normal state via the RESET command. ``` - curl "http://myserver/api/v1/manage/health?cmd=RESET" -H "X-Auth-Token: Mytoken" + curl "http://myserver/api/v1/manage/health?cmd=RESET" -H "X-Auth-Token: Mytoken" ``` ### Disable or silence specific alarms -If you do not wish to disable/silence all alarms, then the `DISABLE ALL` and `SILENCE ALL` commands can't be used. +If you do not wish to disable/silence all alarms, then the `DISABLE ALL` and `SILENCE ALL` commands can't be used. Instead, the following commands expect that one or more alarm selectors will be added, so that only alarms that match the selectors are disabled or silenced. -- `DISABLE` : Set the mode to disable health checks. -- `SILENCE` : Set the mode to silence notifications. +- `DISABLE` : Set the mode to disable health checks. +- `SILENCE` : Set the mode to silence notifications. -You will normally put one of these commands in the same request with your first alarm selector, but it's possible to issue them separately as well. -You will get a warning in the response, if a selector was added without a SILENCE/DISABLE command, or vice versa. +You will normally put one of these commands in the same request with your first alarm selector, but it's possible to issue them separately as well. +You will get a warning in the response, if a selector was added without a SILENCE/DISABLE command, or vice versa. -Each request can specify a single alarm `selector`, with one or more `selection criteria`. -A single alarm will match a `selector` if all selection criteria match the alarm. +Each request can specify a single alarm `selector`, with one or more `selection criteria`. +A single alarm will match a `selector` if all selection criteria match the alarm. You can add as many selectors as you like. In essence, the rule is: IF (alarm matches all the criteria in selector1 OR all the criteria in selector2 OR ...) THEN apply the DISABLE or SILENCE command. To clear all selectors and reset the mode to default, use the `RESET` command. -The following example silences notifications for all the alarms with context=load: +The following example silences notifications for all the alarms with context=load: ``` -curl "http://myserver/api/v1/manage/health?cmd=SILENCE&context=load" -H "X-Auth-Token: Mytoken" +curl "http://myserver/api/v1/manage/health?cmd=SILENCE&context=load" -H "X-Auth-Token: Mytoken" ``` -#### Selection criteria +#### Selection criteria -The `selection criteria` are key/value pairs, in the format `key : value`, where value is a netdata [simple pattern](../../../libnetdata/simple_pattern/). This means that you can create very powerful selectors (you will rarely need more than one or two). +The `selection criteria` are key/value pairs, in the format `key : value`, where value is a netdata [simple pattern](../../../libnetdata/simple_pattern/). This means that you can create very powerful selectors (you will rarely need more than one or two). The accepted keys for the `selection criteria` are the following: -- `alarm` : The expression provided will match both `alarm` and `template` names. +- `alarm` : The expression provided will match both `alarm` and `template` names. - `chart` : Chart ids/names, as shown on the dashboard. These will match the `on` entry of a configured `alarm`. - `context` : Chart context, as shown on the dashboard. These will match the `on` entry of a configured `template`. - `hosts` : The hostnames that will need to match. - `families` : The alarm families. -You can add any of the selection criteria you need on the request, to ensure that only the alarms you are interested in are matched and disabled/silenced. e.g. there is no reason to add `hosts: *`, if you want the criteria to be applied to alarms for all hosts. +You can add any of the selection criteria you need on the request, to ensure that only the alarms you are interested in are matched and disabled/silenced. e.g. there is no reason to add `hosts: *`, if you want the criteria to be applied to alarms for all hosts. Example 1: Disable all health checks for context = `random` @@ -152,6 +156,7 @@ The command `LIST` was added in netdata v1.16.0 and returns a JSON with the curr ``` As an example, the following response shows that we have two silencers configured, one for an alarm called `samplealarm` and one for alarms with context `random` on host `myhost` + ``` json { @@ -178,7 +183,7 @@ json "type": "DISABLE", "silencers": [] } - +``` ### Responses diff --git a/web/api/health/health_cmdapi.c b/web/api/health/health_cmdapi.c index 468054c67..94293dbe6 100644 --- a/web/api/health/health_cmdapi.c +++ b/web/api/health/health_cmdapi.c @@ -179,6 +179,7 @@ int web_client_api_request_v1_mgmt_health(RRDHOST *host, struct web_client *w, c silencer = health_silencers_addparam(silencer, key, value); } } + if (likely(silencer)) { health_silencers_add(silencer); buffer_strcat(wb, HEALTH_CMDAPI_MSG_ADDED); diff --git a/web/api/web_api_v1.c b/web/api/web_api_v1.c index 7c0d728bf..2273224bb 100644 --- a/web/api/web_api_v1.c +++ b/web/api/web_api_v1.c @@ -797,23 +797,23 @@ inline int web_client_api_request_v1(RRDHOST *host, struct web_client *w, char * } // get the command - char *tok = mystrsep(&url, "?"); - if(tok && *tok) { - debug(D_WEB_CLIENT, "%llu: Searching for API v1 command '%s'.", w->id, tok); - uint32_t hash = simple_hash(tok); + if(url) { + debug(D_WEB_CLIENT, "%llu: Searching for API v1 command '%s'.", w->id, url); + uint32_t hash = simple_hash(url); for(i = 0; api_commands[i].command ;i++) { - if(unlikely(hash == api_commands[i].hash && !strcmp(tok, api_commands[i].command))) { + if(unlikely(hash == api_commands[i].hash && !strcmp(url, api_commands[i].command))) { if(unlikely(api_commands[i].acl != WEB_CLIENT_ACL_NOCHECK) && !(w->acl & api_commands[i].acl)) return web_client_permission_denied(w); - return api_commands[i].callback(host, w, url); + //return api_commands[i].callback(host, w, url); + return api_commands[i].callback(host, w, (w->decoded_query_string + 1)); } } buffer_flush(w->response.data); buffer_strcat(w->response.data, "Unsupported v1 API command: "); - buffer_strcat_htmlescape(w->response.data, tok); + buffer_strcat_htmlescape(w->response.data, url); return 404; } else { diff --git a/web/gui/console.html b/web/gui/console.html index 942c8c3cd..9b172644e 100644 --- a/web/gui/console.html +++ b/web/gui/console.html @@ -10,7 +10,7 @@ <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> - <link rel="icon" href="favicon.ico" /> + <link rel="icon" href="data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAP9JREFUeNpiYBgFo+A/w34gpiZ8DzWzAYgNiHGAA5UdgA73g+2gcyhgg/0DGQoweB6IBQYyFCCOGOBQwBMd/xnW09ERDtgcoEBHB+zHFQrz6egIBUasocDAcJ9OxWAhE4YQI8MDILmATg7wZ8QRDfQKhQf4Cie6pAVGPA4AhQKo0BCgZRAw4ZSBpIWJNI6CD4wEKikBaFqgVSgcYMIrzcjwgcahcIGRiPYCLUPBkNhWUwP9akVcoQBpatG4MsLviAIqWj6f3Absfdq2igg7IIEKDVQKEzN5ofAenJCp1I8gJRTug5tfkGIdR1FDniMI+QZUjF8Amn5htOdHCAAEGACE6B0cS6mrEwAAAABJRU5ErkJggg==" /> <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" /> <!-- Google Tag Manager --> <script>(function (w, d, s, l, i) { diff --git a/web/gui/dashboard.js b/web/gui/dashboard.js index e7d99a151..a32a29be7 100644 --- a/web/gui/dashboard.js +++ b/web/gui/dashboard.js @@ -8118,10 +8118,10 @@ let chartState = function (element) { }; this.chartDataUniqueID = function () { - return this.id + ',' + this.library_name + ',' + this.dimensions + ',' + this.chartURLOptions(); + return this.id + ',' + this.library_name + ',' + this.dimensions + ',' + this.chartURLOptions(true); }; - this.chartURLOptions = function () { + this.chartURLOptions = function (isForUniqueId) { let ret = ''; if (this.override_options !== null) { @@ -8136,7 +8136,9 @@ let chartState = function (element) { ret += '%7C' + 'jsonwrap'; - if (NETDATA.options.current.eliminate_zero_dimensions) { + // always add `nonzero` when it's used to create a chartDataUniqueID + // we cannot just remove `nonzero` because of backwards compatibility with old snapshots + if (isForUniqueId || NETDATA.options.current.eliminate_zero_dimensions) { ret += '%7C' + 'nonzero'; } diff --git a/web/gui/dashboard_info.js b/web/gui/dashboard_info.js index 0013311e8..df9242646 100644 --- a/web/gui/dashboard_info.js +++ b/web/gui/dashboard_info.js @@ -154,7 +154,7 @@ netdataDashboard.menu = { 'zfs': { title: 'ZFS filesystem', icon: '<i class="fas fa-folder-open"></i>', - info: 'Performance metrics of the ZFS filesystem. The following charts visualize all metrics reported by <a href="https://github.com/zfsonlinux/zfs/blob/master/cmd/arcstat/arcstat.py" target="_blank">arcstat.py</a> and <a href="https://github.com/zfsonlinux/zfs/blob/master/cmd/arc_summary/arc_summary.py" target="_blank">arc_summary.py</a>.' + info: 'Performance metrics of the ZFS filesystem. The following charts visualize all metrics reported by <a href="https://github.com/zfsonlinux/zfs/blob/master/cmd/arcstat/arcstat" target="_blank">arcstat.py</a> and <a href="https://github.com/zfsonlinux/zfs/blob/master/cmd/arc_summary/arc_summary3" target="_blank">arc_summary.py</a>.' }, 'btrfs': { diff --git a/web/gui/demosites.html b/web/gui/demosites.html index e00fbbfdd..f5d263807 100644 --- a/web/gui/demosites.html +++ b/web/gui/demosites.html @@ -10,7 +10,7 @@ <meta name=viewport content="width=device-width,initial-scale=1"> <link rel=apple-touch-icon href=apple-touch-icon.png> - <link rel="icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAA7EAAAOxAGVKw4bAAACyklEQVRYhcWXz0sUYRjHP8+wLItImEhnEfEgEVJk+56CbCECQWezOkTQTTp6kejgKUSoEAoK/wAhaaeDBkLoIahZIWIJo0U8hHQJJBYJERGfDrOju7PjuO5u6/c0PM/7zvfzvvP+eEaoVu6wBZIA3cW82wtvY7cD1xEWSTpb1bxWjm2RtbtQxoF7iLSA7gO/UL4A74G3mBIzNz0Peg1hFmUK46zXBuDaCYQJlDFE4ke/QreAZyhPMc42rt2PyEoxt4cyDUxgnO3qAVy7C8gg0hdFHwBZA0ZIOt9w7e+I9B6mdBVhmGTlbFQCuPYFhA8g56o3P3D6CwyiDCIyFshtgqRIZnKlUStg3lW7OYC0AvPATkiuA9U3uOlEOEDWTgCZ2s3LIMbCU9ID3A8HUCZO9s2jGCRxdFIflDUF/EX3I3q1N0iq+8BZf+v6MzDeFHMAEQvoJpuOAwiu3Qr89g6ZJkm5CnoHeBwDbjTV3CPoRRhF+WQBA801B+AJiAUMWMDFptuLtBef+iygs+kAh+q0gDOnCNBmETyOmywLKJyifyEG/ATqPP+LUt0FZoAlIA6MgNyKqDo2YkAO6G+QeQrjfCyJzuHaoyCvjuiVs4q0jdBMwNyTcV6j+jm0h7BkAYuohpZLJ1TUQJYrIqo7KAtW8VaabQBA1GVWmROZwzgFfwtOoRpealevdGg0a8eAoUB0D9VJ8M8A46yDTNdlL9wmaz8MMX9RrIQOpfoS4+S9br7cdAvoCiLn6wLxFtwyQhxlKMQ8j3CJpFeml+9QN90N6iLSURfEkXD8AQwms+aHyo9hk1kHSaG6+Z/MU6XmlQAeRA7EoLraQPM83si/BlPhF5E3E1dQfY5S++5Q/9dMLwdH7uv4n1PX7gEeAXejy+0y4x2QOWASk8lHNT0e4AAk3QZ6E6+E68MrZNqK2QKwgXevLCEskHSqumX/AUXU5QBtOC5FAAAAAElFTkSuQmCC"> + <link rel="icon" href="data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAP9JREFUeNpiYBgFo+A/w34gpiZ8DzWzAYgNiHGAA5UdgA73g+2gcyhgg/0DGQoweB6IBQYyFCCOGOBQwBMd/xnW09ERDtgcoEBHB+zHFQrz6egIBUasocDAcJ9OxWAhE4YQI8MDILmATg7wZ8QRDfQKhQf4Cie6pAVGPA4AhQKo0BCgZRAw4ZSBpIWJNI6CD4wEKikBaFqgVSgcYMIrzcjwgcahcIGRiPYCLUPBkNhWUwP9akVcoQBpatG4MsLviAIqWj6f3Absfdq2igg7IIEKDVQKEzN5ofAenJCp1I8gJRTug5tfkGIdR1FDniMI+QZUjF8Amn5htOdHCAAEGACE6B0cS6mrEwAAAABJRU5ErkJggg==" /> <meta property="og:url" content="https://my-netdata.io" /> <meta property="og:type" content="website" /> @@ -1100,40 +1100,7 @@ p { </div> </div> </div> - <div class="mygauge-combo"> - <div class="mygauge"> - <div style="padding-bottom: 20px; font-size: 10px; color: #676b70;"> - <b>EU - France</b> - </div> - <div class="mysparkline"> - <div class="mysparkline-overchart-label2"> - requests/s - </div> - <div class="mysparkline-overchart-value" id="ventureer.requests.netdata" > - </div> - <div data-netdata="netdata.requests" - data-dimensions="requests" - data-host="//ventureer.my-netdata.io" - data-common-max="top-gauges" - data-decimal-digits="0" - data-chart-library="dygraph" - data-dygraph-theme="sparkline" - data-dygraph-type="area" - data-width="100%" - data-height="100%" - data-after="-300" - data-colors="#FF4B91" - data-show-value-of-requests-at="ventureer.requests.netdata" - ></div> - </div> - </div> - <div class="mygauge-button"> - <a class="btn btn-alt mygauge-legend-button" href=//ventureer.my-netdata.io/default.html data-ga-category="Outbound links" data-ga-action="Nav click" data-ga-label=DemoVentureer><strong>Enter Roubaix!</strong></a> - <div class="mygause-donation"> - Donated by ventureer.com - </div> - </div> - </div> + <div class="mygauge-combo"> <div class="mygauge"> <div style="padding-bottom: 20px; font-size: 10px; color: #676b70;"> diff --git a/web/gui/images/packaging-beta-tag.svg b/web/gui/images/packaging-beta-tag.svg new file mode 100644 index 000000000..cebdc0847 --- /dev/null +++ b/web/gui/images/packaging-beta-tag.svg @@ -0,0 +1,42 @@ +<svg width="127" height="16" viewBox="0 0 127 16" fill="none" xmlns="http://www.w3.org/2000/svg"> +<rect width="127" height="16" rx="2" fill="url(#paint0_linear)"/> +<path d="M86 0H125C126.105 0 127 0.895431 127 2V14C127 15.1046 126.105 16 125 16H86V0Z" fill="url(#paint1_linear)"/> +<path d="M6.5 4V10" stroke="white" stroke-linecap="round" stroke-linejoin="round"/> +<path d="M12.5 7C13.3284 7 14 6.32843 14 5.5C14 4.67157 13.3284 4 12.5 4C11.6716 4 11 4.67157 11 5.5C11 6.32843 11.6716 7 12.5 7Z" stroke="white" stroke-linecap="round" stroke-linejoin="round"/> +<path d="M6.5 13C7.32843 13 8 12.3284 8 11.5C8 10.6716 7.32843 10 6.5 10C5.67157 10 5 10.6716 5 11.5C5 12.3284 5.67157 13 6.5 13Z" stroke="white" stroke-linecap="round" stroke-linejoin="round"/> +<path d="M12.5 7C12.5 8.19347 12.0259 9.33807 11.182 10.182C10.3381 11.0259 9.19347 11.5 8 11.5" stroke="white" stroke-linecap="round" stroke-linejoin="round"/> +<g filter="url(#filter0_d)"> +<path d="M23.939 7.93848V11H22.9077V3.17969H25.792C26.6478 3.17969 27.3174 3.39811 27.8008 3.83496C28.2878 4.27181 28.5312 4.8501 28.5312 5.56982C28.5312 6.32894 28.2931 6.91439 27.8169 7.32617C27.3442 7.73438 26.6657 7.93848 25.7812 7.93848H23.939ZM23.939 7.09521H25.792C26.3434 7.09521 26.766 6.96631 27.0596 6.7085C27.3532 6.4471 27.5 6.07113 27.5 5.58057C27.5 5.11507 27.3532 4.74268 27.0596 4.46338C26.766 4.18408 26.3631 4.03906 25.8511 4.02832H23.939V7.09521ZM33.6548 11C33.5976 10.8854 33.551 10.6813 33.5152 10.3877C33.0533 10.8675 32.5018 11.1074 31.8609 11.1074C31.288 11.1074 30.8171 10.9463 30.4483 10.624C30.0831 10.2982 29.9004 9.88639 29.9004 9.38867C29.9004 8.78353 30.1296 8.31445 30.5879 7.98145C31.0499 7.64486 31.698 7.47656 32.5323 7.47656H33.4991V7.02002C33.4991 6.67269 33.3952 6.39697 33.1876 6.19287C32.9799 5.98519 32.6737 5.88135 32.2691 5.88135C31.9146 5.88135 31.6174 5.97087 31.3775 6.1499C31.1376 6.32894 31.0176 6.54557 31.0176 6.7998H30.0186C30.0186 6.50977 30.1207 6.23047 30.3248 5.96191C30.5324 5.68978 30.8117 5.47493 31.1627 5.31738C31.5171 5.15983 31.9057 5.08105 32.3282 5.08105C32.9978 5.08105 33.5224 5.24935 33.9019 5.58594C34.2815 5.91895 34.4784 6.37907 34.4927 6.96631V9.64111C34.4927 10.1746 34.5608 10.599 34.6968 10.9141V11H33.6548ZM32.0059 10.2427C32.3174 10.2427 32.6129 10.1621 32.8921 10.001C33.1714 9.83984 33.3738 9.63037 33.4991 9.37256V8.18018H32.7203C31.5028 8.18018 30.8941 8.53646 30.8941 9.24902C30.8941 9.56055 30.9979 9.80404 31.2056 9.97949C31.4133 10.1549 31.6801 10.2427 32.0059 10.2427ZM38.8214 10.2964C39.1759 10.2964 39.4856 10.189 39.7506 9.97412C40.0156 9.75928 40.1624 9.49072 40.191 9.16846H41.131C41.1131 9.50146 40.9985 9.81836 40.7872 10.1191C40.576 10.4199 40.2931 10.6598 39.9386 10.8389C39.5877 11.0179 39.2153 11.1074 38.8214 11.1074C38.0301 11.1074 37.3999 10.8442 36.9308 10.3179C36.4653 9.78792 36.2325 9.06462 36.2325 8.14795V7.98145C36.2325 7.41569 36.3364 6.9126 36.5441 6.47217C36.7517 6.03174 37.0489 5.68978 37.4357 5.44629C37.826 5.2028 38.2861 5.08105 38.816 5.08105C39.4677 5.08105 40.0084 5.2762 40.4381 5.6665C40.8714 6.0568 41.1023 6.56348 41.131 7.18652H40.191C40.1624 6.81055 40.0192 6.5026 39.7613 6.2627C39.5071 6.01921 39.192 5.89746 38.816 5.89746C38.3112 5.89746 37.9191 6.08008 37.6398 6.44531C37.364 6.80697 37.2262 7.33154 37.2262 8.01904V8.20703C37.2262 8.87663 37.364 9.39225 37.6398 9.75391C37.9155 10.1156 38.3094 10.2964 38.8214 10.2964ZM44.3102 8.30908L43.6872 8.95898V11H42.6935V2.75H43.6872V7.73975L44.2189 7.10059L46.029 5.18848H47.2375L44.9762 7.61621L47.5007 11H46.3351L44.3102 8.30908ZM52.2913 11C52.234 10.8854 52.1874 10.6813 52.1516 10.3877C51.6897 10.8675 51.1383 11.1074 50.4973 11.1074C49.9244 11.1074 49.4535 10.9463 49.0847 10.624C48.7195 10.2982 48.5369 9.88639 48.5369 9.38867C48.5369 8.78353 48.766 8.31445 49.2244 7.98145C49.6863 7.64486 50.3344 7.47656 51.1687 7.47656H52.1355V7.02002C52.1355 6.67269 52.0316 6.39697 51.824 6.19287C51.6163 5.98519 51.3101 5.88135 50.9055 5.88135C50.551 5.88135 50.2538 5.97087 50.0139 6.1499C49.774 6.32894 49.654 6.54557 49.654 6.7998H48.655C48.655 6.50977 48.7571 6.23047 48.9612 5.96191C49.1689 5.68978 49.4482 5.47493 49.7991 5.31738C50.1536 5.15983 50.5421 5.08105 50.9646 5.08105C51.6342 5.08105 52.1588 5.24935 52.5383 5.58594C52.9179 5.91895 53.1148 6.37907 53.1291 6.96631V9.64111C53.1291 10.1746 53.1972 10.599 53.3332 10.9141V11H52.2913ZM50.6423 10.2427C50.9538 10.2427 51.2493 10.1621 51.5286 10.001C51.8079 9.83984 52.0102 9.63037 52.1355 9.37256V8.18018H51.3567C50.1392 8.18018 49.5305 8.53646 49.5305 9.24902C49.5305 9.56055 49.6343 9.80404 49.842 9.97949C50.0497 10.1549 50.3165 10.2427 50.6423 10.2427ZM54.8904 8.0459C54.8904 7.13997 55.0999 6.42025 55.5188 5.88672C55.9378 5.34961 56.4928 5.08105 57.1839 5.08105C57.8929 5.08105 58.4461 5.33171 58.8436 5.83301L58.8919 5.18848H59.7996V10.8604C59.7996 11.6123 59.5758 12.2049 59.1282 12.6382C58.6842 13.0715 58.0862 13.2881 57.3343 13.2881C56.9153 13.2881 56.5053 13.1986 56.1043 13.0195C55.7033 12.8405 55.3971 12.5952 55.1858 12.2837L55.7015 11.6875C56.1276 12.2139 56.6486 12.4771 57.2645 12.4771C57.7479 12.4771 58.1238 12.341 58.3924 12.0688C58.6645 11.7967 58.8006 11.4136 58.8006 10.9194V10.4199C58.4031 10.8783 57.8606 11.1074 57.1731 11.1074C56.4928 11.1074 55.9414 10.8335 55.5188 10.2856C55.0999 9.73779 54.8904 8.99121 54.8904 8.0459ZM55.8895 8.15869C55.8895 8.81396 56.0237 9.32959 56.2923 9.70557C56.5608 10.078 56.9368 10.2642 57.4202 10.2642C58.0468 10.2642 58.507 9.97949 58.8006 9.41016V6.75684C58.4962 6.20182 58.0397 5.92432 57.431 5.92432C56.9476 5.92432 56.5698 6.1123 56.2977 6.48828C56.0255 6.86426 55.8895 7.42106 55.8895 8.15869ZM62.8231 11H61.8295V5.18848H62.8231V11ZM61.7489 3.64697C61.7489 3.48584 61.7972 3.34977 61.8939 3.23877C61.9942 3.12777 62.141 3.07227 62.3343 3.07227C62.5277 3.07227 62.6745 3.12777 62.7748 3.23877C62.875 3.34977 62.9252 3.48584 62.9252 3.64697C62.9252 3.80811 62.875 3.94238 62.7748 4.0498C62.6745 4.15723 62.5277 4.21094 62.3343 4.21094C62.141 4.21094 61.9942 4.15723 61.8939 4.0498C61.7972 3.94238 61.7489 3.80811 61.7489 3.64697ZM65.7983 5.18848L65.8305 5.91895C66.2745 5.36035 66.8546 5.08105 67.5707 5.08105C68.7989 5.08105 69.4184 5.77393 69.4291 7.15967V11H68.4355V7.1543C68.4319 6.73535 68.3352 6.42562 68.1454 6.2251C67.9592 6.02458 67.6674 5.92432 67.2699 5.92432C66.9477 5.92432 66.6648 6.01025 66.4213 6.18213C66.1778 6.354 65.988 6.57959 65.852 6.85889V11H64.8583V5.18848H65.7983ZM71.1313 8.0459C71.1313 7.13997 71.3408 6.42025 71.7597 5.88672C72.1787 5.34961 72.7337 5.08105 73.4248 5.08105C74.1338 5.08105 74.687 5.33171 75.0845 5.83301L75.1328 5.18848H76.0405V10.8604C76.0405 11.6123 75.8167 12.2049 75.3691 12.6382C74.9251 13.0715 74.3271 13.2881 73.5752 13.2881C73.1562 13.2881 72.7462 13.1986 72.3452 13.0195C71.9442 12.8405 71.638 12.5952 71.4267 12.2837L71.9424 11.6875C72.3685 12.2139 72.8895 12.4771 73.5054 12.4771C73.9888 12.4771 74.3647 12.341 74.6333 12.0688C74.9054 11.7967 75.0415 11.4136 75.0415 10.9194V10.4199C74.644 10.8783 74.1015 11.1074 73.414 11.1074C72.7337 11.1074 72.1823 10.8335 71.7597 10.2856C71.3408 9.73779 71.1313 8.99121 71.1313 8.0459ZM72.1304 8.15869C72.1304 8.81396 72.2646 9.32959 72.5332 9.70557C72.8017 10.078 73.1777 10.2642 73.6611 10.2642C74.2877 10.2642 74.7479 9.97949 75.0415 9.41016V6.75684C74.7371 6.20182 74.2806 5.92432 73.6719 5.92432C73.1885 5.92432 72.8107 6.1123 72.5386 6.48828C72.2664 6.86426 72.1304 7.42106 72.1304 8.15869Z" fill="white"/> +</g> +<g filter="url(#filter1_d)"> +<path d="M93.7949 12V4.17969H96.4751C97.3595 4.17969 98.0327 4.35693 98.4946 4.71143C98.9565 5.06592 99.1875 5.59408 99.1875 6.2959C99.1875 6.65397 99.0908 6.97624 98.8975 7.2627C98.7041 7.54915 98.4212 7.77116 98.0488 7.92871C98.4714 8.04329 98.7972 8.25993 99.0264 8.57861C99.2591 8.89372 99.3755 9.27327 99.3755 9.71729C99.3755 10.4513 99.1392 11.0153 98.6665 11.4092C98.1974 11.8031 97.5243 12 96.647 12H93.7949ZM95.1538 8.47119V10.915H96.6631C97.0892 10.915 97.4222 10.8094 97.6621 10.5981C97.902 10.3869 98.022 10.0933 98.022 9.71729C98.022 8.90446 97.6066 8.4891 96.7759 8.47119H95.1538ZM95.1538 7.47217H96.4858C96.9084 7.47217 97.2378 7.37728 97.4741 7.1875C97.714 6.99414 97.834 6.72201 97.834 6.37109C97.834 5.98438 97.723 5.70508 97.501 5.5332C97.2826 5.36133 96.9406 5.27539 96.4751 5.27539H95.1538V7.47217ZM105.745 8.50879H102.533V10.915H106.288V12H101.174V4.17969H106.25V5.27539H102.533V7.43457H105.745V8.50879ZM113.592 5.27539H111.153V12H109.805V5.27539H107.388V4.17969H113.592V5.27539ZM118.796 10.1792H115.767L115.133 12H113.72L116.674 4.17969H117.894L120.853 12H119.435L118.796 10.1792ZM116.148 9.0835H118.415L117.281 5.83936L116.148 9.0835Z" fill="white"/> +</g> +<defs> +<filter id="filter0_d" x="21.9077" y="2.75" width="55.1328" height="12.5381" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/> +<feOffset dy="1"/> +<feGaussianBlur stdDeviation="0.5"/> +<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"/> +<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/> +<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/> +</filter> +<filter id="filter1_d" x="92.7949" y="4.17969" width="29.0583" height="9.82031" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/> +<feOffset dy="1"/> +<feGaussianBlur stdDeviation="0.5"/> +<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"/> +<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/> +<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/> +</filter> +<linearGradient id="paint0_linear" x1="63.5" y1="0" x2="63.5" y2="16" gradientUnits="userSpaceOnUse"> +<stop stop-color="#606060"/> +<stop offset="1" stop-color="#4D4D4D"/> +</linearGradient> +<linearGradient id="paint1_linear" x1="106.5" y1="0" x2="106.5" y2="16" gradientUnits="userSpaceOnUse"> +<stop stop-color="#BFBFBF"/> +<stop offset="1" stop-color="#7F7F7F"/> +</linearGradient> +</defs> +</svg> diff --git a/web/gui/index.html b/web/gui/index.html index 4a8647dd9..58b074404 100644 --- a/web/gui/index.html +++ b/web/gui/index.html @@ -16,23 +16,6 @@ <link rel="stylesheet" type="text/css" href="main.css?v=5"> <link rel="icon" href="data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAP9JREFUeNpiYBgFo+A/w34gpiZ8DzWzAYgNiHGAA5UdgA73g+2gcyhgg/0DGQoweB6IBQYyFCCOGOBQwBMd/xnW09ERDtgcoEBHB+zHFQrz6egIBUasocDAcJ9OxWAhE4YQI8MDILmATg7wZ8QRDfQKhQf4Cie6pAVGPA4AhQKo0BCgZRAw4ZSBpIWJNI6CD4wEKikBaFqgVSgcYMIrzcjwgcahcIGRiPYCLUPBkNhWUwP9akVcoQBpatG4MsLviAIqWj6f3Absfdq2igg7IIEKDVQKEzN5ofAenJCp1I8gJRTug5tfkGIdR1FDniMI+QZUjF8Amn5htOdHCAAEGACE6B0cS6mrEwAAAABJRU5ErkJggg==" /> - <!-- <link rel="apple-touch-icon" sizes="57x57" href="images/apple-icon-57x57.png"> - <link rel="apple-touch-icon" sizes="60x60" href="images/apple-icon-60x60.png"> - <link rel="apple-touch-icon" sizes="72x72" href="images/apple-icon-72x72.png"> - <link rel="apple-touch-icon" sizes="76x76" href="images/apple-icon-76x76.png"> - <link rel="apple-touch-icon" sizes="114x114" href="images/apple-icon-114x114.png"> - <link rel="apple-touch-icon" sizes="120x120" href="images/apple-icon-120x120.png"> - <link rel="apple-touch-icon" sizes="144x144" href="images/apple-icon-144x144.png"> - <link rel="apple-touch-icon" sizes="152x152" href="images/apple-icon-152x152.png"> - <link rel="apple-touch-icon" sizes="180x180" href="images/apple-icon-180x180.png"> - <link rel="icon" type="image/png" sizes="192x192" href="images/android-icon-192x192.png"> - <link rel="icon" type="image/png" sizes="32x32" href="images/favicon-32x32.png"> - <link rel="icon" type="image/png" sizes="96x96" href="images/favicon-96x96.png"> - <link rel="icon" type="image/png" sizes="16x16" href="images/favicon-16x16.png"> - <link rel="manifest" href="manifest.json"> - <meta name="msapplication-TileColor" content="#ffffff"> - <meta name="msapplication-TileImage" content="images/ms-icon-144x144.png"> - <meta name="theme-color" content="#ffffff"> --> <meta property="og:locale" content="en_US" /> <meta property="og:url" content="https://my-netdata.io" /> @@ -80,23 +63,6 @@ </script> <nav class="navbar navbar-default navbar-fixed-top" role="banner"> <div class="container"> - <!-- <nav id="mynetdata_nav" class="collapse navbar-collapse navbar-left" role="navigation" style="padding-right: 20px;"> - <ul class="nav navbar-nav"> - <li data-placement="right" style="line-height: 50px; margin-right: 15px" title="Netdata Agent"> - <img src="images/netdata-logomark.svg" height="32"/> - </li> - <li class="dropdown" id="myNetdataDropdownParent" title="your other netdata servers" data-toggle="tooltip" data-placement="right"> - <a href="#" id="hostname" class="dropdown-toggle" data-toggle="dropdown">my-netdata <strong class="caret"></strong></a> - <div id="my-netdata-dropdown-content" class="dropdown-menu scrollable-menu inpagemenu"> - <div class="agent-item" style="white-space: nowrap"> - <i class="fas fa-hourglass-half"></i> - Loading, please wait... - <div></div> - </div> - </div> - </li> - </ul> - </nav> --> <div class="navbar-header"> <button class="navbar-toggle" type="button" data-toggle="collapse" data-target=".navbar-collapse"> <span class="sr-only">Toggle navigation</span> @@ -104,13 +70,14 @@ <span class="icon-bar"></span> <span class="icon-bar"></span> </button> - <!-- <a href="/" class="navbar-brand" id="1hostname" title="server hostname<br/>click it to reload the dashboard" data-toggle="tooltip" data-placement="bottom">netdata</a> --> <ul class="nav navbar-nav" style="display: inline-block"> <li data-placement="right" class="hidden-xs hidden-sm" style="line-height: 50px; margin-right: 15px" title="Netdata Agent"> <img src="images/netdata-logomark.svg" height="32"/> </li> <li class="dropdown" id="myNetdataDropdownParent" title="your other netdata servers" data-toggle="tooltip" data-placement="right"> - <a href="#" id="hostname" class="dropdown-toggle" data-toggle="dropdown">my-netdata <strong class="caret"></strong></a> + <a href="#" id="hostname" class="dropdown-toggle" data-toggle="dropdown"> + my-netdata + <strong class="caret"></strong></a> <div id="my-netdata-dropdown-content" class="dropdown-menu scrollable-menu inpagemenu"> <div class="agent-item" style="white-space: nowrap"> <i class="fas fa-hourglass-half"></i> @@ -135,26 +102,6 @@ <li title="print this dashboard to PDF" data-toggle="tooltip" data-placement="bottom" id="printButton"><a href="#" class="btn" data-toggle="modal" data-target="#printPreflightModal"><i class="fas fa-print"></i> <span class="hidden-sm hidden-md hidden-lg">Print</span></a></li> <li title="get help on using the charts" data-toggle="tooltip" data-placement="bottom" class="hidden-sm"><a href="#" class="btn" data-toggle="modal" data-target="#helpModal"><i class="fas fa-question-circle"></i> <span class="hidden-sm hidden-md">Help</span></a></li> <li id="account-menu-container" class="dropdown" data-toggle="tooltip"></li> - <!-- - <li class="dropdown hidden-sm hidden-md hidden-lg" id="myNetdataDropdownParent" title="your other netdata servers" data-toggle="tooltip" data-placement="right"> - <a href="#" class="dropdown-toggle" data-toggle="dropdown">my-netdata <strong class="caret"></strong></a> - <div id="my-netdata-dropdown-content" class="dropdown-menu scrollable-menu inpagemenu"> - <div class="agent-item" style="white-space: nowrap"> - <i class="fas fa-hourglass-half"></i> - Loading, please wait... - <div></div> - </div> - </div> - </li> - --> - <!-- - <li class="dropdown hidden-sm hidden-md hidden-lg"> - <a href="#" class="dropdown-toggle" data-toggle="dropdown">My Nodes <strong class="caret"></strong></a> - <ul id="mynetdata_servers2" class="dropdown-menu scrollable-menu inpagemenu" role="menu"> - <li><a href="#" onclick="return false;" style="color: #999;">loading...</a></li> - </ul> - </li> - --> </ul> </nav> </div> diff --git a/web/gui/infographic.html b/web/gui/infographic.html index b3112781b..24ff8f4e6 100644 --- a/web/gui/infographic.html +++ b/web/gui/infographic.html @@ -9,13 +9,13 @@ <meta name=viewport content="width=device-width,initial-scale=1"> <link rel=apple-touch-icon href=apple-touch-icon.png> - <link rel="icon" type="image/png" sizes="32x32" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACNklEQVRYhcXXv2tUQRAH8M+FEIJISBHCIWIhIQSUILERi4AiiqCggiIiomAjlhaC4j+ghYWISgqNohZaCBZBC8Ei8QdEUCutFBsxCBqDYkgci/cunkfuJffjJQPD8mZm5/vd2WV2HzlJ0Bs8CvrywsgCHwy+BpGOg0sJfjj4nYKX9FdwKG9gwZlgtgK8pLOpPxfw1mCoCnClDgWtzQTvCEYWCV7SkWAlFBoEb8dlDKBF8t2bMWUSH/AHr3CiEfz5CPUusPJLkRCdk5ZqyeqUrQv4R7E5TwK7M3zTeIKduRAIitiWEfIY69GdCwGcRFuG/xqONRkzkaA7+J5x+MaDtWmHvJ4HgeEM8Nn0bridfv9HoOFyBAdwJCPkqqTzHWwUaz7wgeBHxupfBKuCj2W25mxBsCGYyAB/FxTT27HcPlyep64tCLbjKbqqhLzBlgKfF8pVE4FgRXABI+ioEnYfOyzcFWsCbg+OV+xlpU4ER4O+4HVwL51b3xYEXcGu4Ao+YQhr5gmdxHmsQyfG0b/YxbWmLfRWmnxa0s06VbTMCpnBS9zFzQKTwR5cXCzwHIE02Sl8wSZsRI/kgLVJqjSd+t9LVjiG1diPszhdK3A5gR48k5zYMTwscC59sfT799CYKvA8EttbSeXgTr3gJQKl91kR+yTlvyG5uUbLYh9gb+ovltkb6qYtNSRo3kOygsBSzGlKsubf43USWLYK5CLLXoFWyU/CtzLbVDpW2n+m40yN9ukqdvAX9ac/EIgOapcAAAAASUVORK5CYII="> + <link rel="icon" href="data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAP9JREFUeNpiYBgFo+A/w34gpiZ8DzWzAYgNiHGAA5UdgA73g+2gcyhgg/0DGQoweB6IBQYyFCCOGOBQwBMd/xnW09ERDtgcoEBHB+zHFQrz6egIBUasocDAcJ9OxWAhE4YQI8MDILmATg7wZ8QRDfQKhQf4Cie6pAVGPA4AhQKo0BCgZRAw4ZSBpIWJNI6CD4wEKikBaFqgVSgcYMIrzcjwgcahcIGRiPYCLUPBkNhWUwP9akVcoQBpatG4MsLviAIqWj6f3Absfdq2igg7IIEKDVQKEzN5ofAenJCp1I8gJRTug5tfkGIdR1FDniMI+QZUjF8Amn5htOdHCAAEGACE6B0cS6mrEwAAAABJRU5ErkJggg==" /> <meta property="og:url" content="https://my-netdata.io/infographic.html" /> <meta property="og:type" content="website" /> <meta property="og:title" content="netdata infographic" /> <meta property="og:description" content="Unparalleled insights, in real-time, of everything happening on your Linux systems and applications, with stunning, interactive web dashboards and powerful performance and health alarms." /> - <meta property="og:image" content="https://cloud.githubusercontent.com/assets/2662304/25580009/bf7016a4-2e85-11e7-9a7a-b36c57db7b91.png" /> + <meta property="og:image" content="https://user-images.githubusercontent.com/43294513/60951037-8ba5d180-a2f8-11e9-906e-e27356f168bc.png" /> <meta property="og:image:type" content="image/png" /> <meta property="fb:app_id" content="1200089276712916" /> @@ -91,8 +91,8 @@ "toolbar":"", "auto-fit":true, "check-visible-state":false, - "edit":"https://raw.githubusercontent.com/ktsaou/netdata/master/diagrams/netdata-overview.xml", - "url":"https://raw.githubusercontent.com/ktsaou/netdata/master/diagrams/netdata-overview.xml" + "edit":"https://raw.githubusercontent.com/netdata/netdata/master/diagrams/netdata-overview.xml", + "url":"https://raw.githubusercontent.com/netdata/netdata/master/diagrams/netdata-overview.xml" }; document.getElementById("drawing").dataset.mxgraph = JSON.stringify(opts); </script> diff --git a/web/gui/main.css b/web/gui/main.css index 2ddb776e5..b6ba95910 100644 --- a/web/gui/main.css +++ b/web/gui/main.css @@ -151,12 +151,6 @@ body.modal-open { /*width: 220px;*/ } -/* -.affix-top { - width: 220px; -} -*/ - .dashboard-sidebar { max-height: calc(100% - 70px) !important; overflow-y: auto; @@ -168,12 +162,6 @@ body.modal-open { position: static; } -@media (min-width: 768px) { - .dashboard-sidebar { - padding-left: 20px; - } -} - /* First level of nav */ .dashboard-sidenav { margin-top: 20px; @@ -353,146 +341,6 @@ body.modal-open { user-select: none; } -@media print { - body { - overflow: visible !important; - -webkit-print-color-adjust: exact; - page-break-inside: auto; - page-break-before: auto; - page-break-after: auto; - } - - .dashboard-section { - page-break-inside: auto; - page-break-before: auto; - page-break-after: auto; - } - - .dashboard-subsection { - page-break-before: avoid; - page-break-after: auto; - page-break-inside: auto; - } - - .charts-body { - padding-left: 0%; - padding-right: 0%; - display: block; - page-break-inside: auto; - page-break-before: auto; - page-break-after: auto; - } - - .back-to-top, - .dashboard-theme-toggle { - display: block; - } -} - -@media (min-width: 768px) { - .charts-body { - padding-left: 0%; - padding-right: 0%; - } - - .back-to-top, - .dashboard-theme-toggle { - display: block; - } -} - -/* Show and affix the side nav when space allows it */ -@media (min-width: 992px) { - .container { - padding-left: 0% !important; - } - - .charts-body { - width: calc(100% - 213px) !important; - padding-left: 1% !important; - padding-right: 0% !important; - } - - .sidebar-body { - display: inline-block !important; - width: 213px !important; - } - - .dashboard-sidebar .nav > .active > ul { - display: block; - } - - /* Widen the fixed sidebar */ - .dashboard-sidebar.affix, - .dashboard-sidebar.affix-top, - .dashboard-sidebar.affix-bottom { - width: 213px !important; - } - - .dashboard-sidebar.affix { - position: fixed; /* Undo the static from mobile first approach */ - top: 20px; - } - - .dashboard-sidebar.affix-bottom { - position: absolute; /* Undo the static from mobile first approach */ - } - - .dashboard-sidebar.affix-bottom .dashboard-sidenav, - .dashboard-sidebar.affix .dashboard-sidenav { - margin-top: 0; - margin-bottom: 0; - } -} - -@media (min-width: 1200px) { - .container { - padding-left: 2% !important; - } - - .charts-body { - width: calc(100% - 233px) !important; - padding-left: 1% !important; - padding-right: 1% !important; - } - - .sidebar-body { - display: inline-block !important; - width: 233px !important; - } - - /* Widen the fixed sidebar again */ - .dashboard-sidebar.affix, - .dashboard-sidebar.affix-top, - .dashboard-sidebar.affix-bottom { - width: 233px !important; - } -} - -@media (min-width: 1360px) { - .container { - padding-left: 3% !important; - } - - .charts-body { - width: calc(100% - 263px) !important; - padding-left: 1% !important; - padding-right: 2% !important; - } - - .sidebar-body { - display: inline-block !important; - width: 263px !important; - } - - /* Widen the fixed sidebar again */ - .dashboard-sidebar.affix, - .dashboard-sidebar.affix-top, - .dashboard-sidebar.affix-bottom { - width: 263px !important; - } -} - .action-button { position: relative; display: inline-block; @@ -664,8 +512,23 @@ body.modal-open { right: 19px; } +#myNetdataDropdownParent { + float: left; +} + #hostname { font-size: 18px; + overflow: hidden; + text-overflow: ellipsis; + max-width: 220px; + } + + #hostnametext { + white-space: pre; + float: left; + text-overflow: ellipsis; + overflow: hidden; + max-width: 160px; } .sign-in-btn { @@ -718,3 +581,180 @@ body.modal-open { .beta { color:#FFCC00; } + + +@media (min-width: 1400px) { + #hostname { + max-width: 600px !important; + } + + #hostnametext { + max-width: 540px !important; + } +} + +@media (min-width: 1360px) { + .container { + padding-left: 3% !important; + } + + #hostname { + max-width: 280px !important; + } + + #hostnametext { + max-width: 220px !important; + } + + .charts-body { + width: calc(100% - 263px) !important; + padding-left: 1% !important; + padding-right: 2% !important; + } + + .sidebar-body { + display: inline-block !important; + width: 263px !important; + } + + /* Widen the fixed sidebar again */ + .dashboard-sidebar.affix, + .dashboard-sidebar.affix-top, + .dashboard-sidebar.affix-bottom { + width: 263px !important; + } +} + +@media (min-width: 1200px) { + #hostname { + max-width: 100px; + } + + #hostnametext { + max-width: 40px; + } + .container { + padding-left: 2% !important; + } + + + .charts-body { + width: calc(100% - 233px) !important; + padding-left: 1% !important; + padding-right: 1% !important; + } + + .sidebar-body { + display: inline-block !important; + width: 233px !important; + } + + /* Widen the fixed sidebar again */ + .dashboard-sidebar.affix, + .dashboard-sidebar.affix-top, + .dashboard-sidebar.affix-bottom { + width: 233px !important; + } +} + +@media (min-width: 992px) { + .container { + padding-left: 0% !important; + } + + .charts-body { + width: calc(100% - 213px) !important; + padding-left: 1% !important; + padding-right: 0% !important; + } + + .sidebar-body { + display: inline-block !important; + width: 213px !important; + } + + .dashboard-sidebar .nav > .active > ul { + display: block; + } + + /* Widen the fixed sidebar */ + .dashboard-sidebar.affix, + .dashboard-sidebar.affix-top, + .dashboard-sidebar.affix-bottom { + width: 213px !important; + } + + .dashboard-sidebar.affix { + position: fixed; /* Undo the static from mobile first approach */ + top: 20px; + } + + .dashboard-sidebar.affix-bottom { + position: absolute; /* Undo the static from mobile first approach */ + } + + .dashboard-sidebar.affix-bottom .dashboard-sidenav, + .dashboard-sidebar.affix .dashboard-sidenav { + margin-top: 0; + margin-bottom: 0; + } +} + +@media (min-width: 860px) { + .dashboard-sidebar { + padding-left: 20px; + } + +} + +@media (min-width: 768px) { + .dashboard-sidebar { + padding-left: 20px; + } + + .charts-body { + padding-left: 0%; + padding-right: 0%; + } + + .back-to-top, + .dashboard-theme-toggle { + display: block; + } +} + +@media print { + body { + overflow: visible !important; + -webkit-print-color-adjust: exact; + page-break-inside: auto; + page-break-before: auto; + page-break-after: auto; + } + + .dashboard-section { + page-break-inside: auto; + page-break-before: auto; + page-break-after: auto; + } + + .dashboard-subsection { + page-break-before: avoid; + page-break-after: auto; + page-break-inside: auto; + } + + .charts-body { + padding-left: 0%; + padding-right: 0%; + display: block; + page-break-inside: auto; + page-break-before: auto; + page-break-after: auto; + } + + .back-to-top, + .dashboard-theme-toggle { + display: block; + } +} diff --git a/web/gui/main.js b/web/gui/main.js index 65c4d4a88..1214eba6f 100644 --- a/web/gui/main.js +++ b/web/gui/main.js @@ -704,11 +704,11 @@ function restrictMyNetdataMenu() { </div>`); } -function openAuthenticatedUrl(url) { +function openAuthenticatedUrl(url) { if (isSignedIn()) { window.open(url); } else { - window.open(`${NETDATA.registry.cloudBaseURL}/account/sign-in-agent?id=${NETDATA.registry.machine_guid}&name=${encodeURIComponent(NETDATA.registry.hostname)}&origin=${encodeURIComponent(window.location.origin + "/")}`); + window.open(`${NETDATA.registry.cloudBaseURL}/account/sign-in-agent?id=${NETDATA.registry.machine_guid}&name=${encodeURIComponent(NETDATA.registry.hostname)}&origin=${encodeURIComponent(window.location.origin + "/")}&redirectUrl=${encodeURIComponent(window.location.origin + "/" + url)}`); } } @@ -1775,8 +1775,6 @@ function renderPage(menus, data) { if (urlOptions.mode === 'print') { chtml += '</div>'; } - - // console.log(' \------- ' + chart.id + ' (' + chart.priority + '): ' + chart.context + ' height: ' + menus[menu].submenus[submenu].height); } head += '</div>'; @@ -2747,7 +2745,7 @@ function initializeDynamicDashboardWithData(data) { } // update the dashboard hostname - document.getElementById('hostname').innerHTML = options.hostname + ((netdataSnapshotData !== null) ? ' (snap)' : '').toString() + ' <strong class="caret">'; + document.getElementById('hostname').innerHTML = '<span id="hostnametext">' + options.hostname + ((netdataSnapshotData !== null) ? ' (snap)' : '').toString() + '</span> <strong class="caret">'; document.getElementById('hostname').href = NETDATA.serverDefault; document.getElementById('netdataVersion').innerHTML = options.version; @@ -4899,6 +4897,9 @@ function handleSignInMessage(e) { cloudToken = e.data.token; netdataRegistryCallback(registryAgents); + if (e.data.redirectUrl) { + window.location.replace(e.data.redirectUrl); + } } function handleSignOutMessage(e) { diff --git a/web/gui/src/dashboard.js/main.js b/web/gui/src/dashboard.js/main.js index 13f3b4c7d..564ee7d4e 100644 --- a/web/gui/src/dashboard.js/main.js +++ b/web/gui/src/dashboard.js/main.js @@ -3071,10 +3071,10 @@ let chartState = function (element) { }; this.chartDataUniqueID = function () { - return this.id + ',' + this.library_name + ',' + this.dimensions + ',' + this.chartURLOptions(); + return this.id + ',' + this.library_name + ',' + this.dimensions + ',' + this.chartURLOptions(true); }; - this.chartURLOptions = function () { + this.chartURLOptions = function (isForUniqueId) { let ret = ''; if (this.override_options !== null) { @@ -3089,7 +3089,9 @@ let chartState = function (element) { ret += '%7C' + 'jsonwrap'; - if (NETDATA.options.current.eliminate_zero_dimensions) { + // always add `nonzero` when it's used to create a chartDataUniqueID + // we cannot just remove `nonzero` because of backwards compatibility with old snapshots + if (isForUniqueId || NETDATA.options.current.eliminate_zero_dimensions) { ret += '%7C' + 'nonzero'; } diff --git a/web/server/README.md b/web/server/README.md index df29f331f..173e89596 100644 --- a/web/server/README.md +++ b/web/server/README.md @@ -59,42 +59,43 @@ The API requests are serviced as follows: ### Enabling TLS support +Since v1.16.0, Netdata supports encrypted HTTP connections to the web server, plus encryption of streaming data between a slave and its master, via the TLS 1.2 protocol. -Netdata since version 1.16 supports encrypted HTTP connections to the web server and encryption of the data stream between a slave and a master. -Inbound unix socket connections are unaffected, regardless of the SSL settings. -To enable SSL, provide the path to your certificate and private key in the `[web]` section of `netdata.conf`: +Inbound unix socket connections are unaffected, regardless of the TLS settings. +??? info "Differences in TLS and SSL terminology" + While Netdata uses Transport Layer Security (TLS) 1.2 to encrypt communications rather than the obsolete SSL protocol, it's still common practice to refer to encrypted web connections as `SSL`. Many vendors, like Nginx and even Netdata itself, use `SSL` in configuration files, whereas documentation will always refer to encrypted communications as `TLS` or `TLS/SSL`. -``` +To enable TLS, provide the path to your certificate and private key in the `[web]` section of `netdata.conf`: + +``` conf [web] ssl key = /etc/netdata/ssl/key.pem ssl certificate = /etc/netdata/ssl/cert.pem ``` -Both files must be readable by the netdata user. If any of the two files does not exist or is unreadable, Netdata falls back to HTTP. - -For a master/slave connection, only the master needs these settings. +Both files must be readable by the `netdata` user. If either of these files do not exist or are unreadable, Netdata will fall back to HTTP. For a master/slave connection, only the master needs these settings. For test purposes, you can generate self-signed certificates with the following command: -``` +``` bash $ openssl req -newkey rsa:2048 -nodes -sha512 -x509 -days 365 -keyout key.pem -out cert.pem ``` -TIP: If you use 4096 bits for the key and the certificate, netdata will need more CPU to process the whole communication. -rsa4096 can be until 4 times slower than rsa2048, so we recommend using 2048 bits. You can verify the difference by running - -``` -$ openssl speed rsa2048 rsa4096 -``` +!!! note + If you use 4096 bits for your key and the certificate, Netdata will need more CPU to process the communication. `rsa4096` can be up to 4 times slower than `rsa2048`, so we recommend using 2048 bits. You can verify the difference by running: + + ``` + $ openssl speed rsa2048 rsa4096 + ``` -#### SSL enforcement +#### TLS/SSL enforcement When the certificates are defined and unless any other options are provided, a Netdata server will: + - Redirect all incoming HTTP web server requests to HTTPS. Applies to the dashboard, the API, netdata.conf and badges. - Allow incoming slave connections to use both unencrypted and encrypted communications for streaming. -To change this behavior, you need to modify the `bind to` setting in the `[web]` section of `netdata.conf`. -At the end of each port definition, you can append `^SSL=force` or `^SSL=optional`. What happens with these settings differs, depending on whether the port is used for HTTP/S requests, or for streaming. +To change this behavior, you need to modify the `bind to` setting in the `[web]` section of `netdata.conf`. At the end of each port definition, you can append `^SSL=force` or `^SSL=optional`. What happens with these settings differs, depending on whether the port is used for HTTP/S requests, or for streaming. SSL setting | HTTP requests | HTTPS requests | Unencrypted Streams | Encrypted Streams :------:|:-----:|:-----:|:-----:|:-------- @@ -109,12 +110,29 @@ Example: bind to = *=dashboard|registry|badges|management|streaming|netdata.conf^SSL=force ``` -For information how to configure the slaves to use TLS, check [securing the communication](../../streaming#securing-the-communication) in the streaming documentation. -You will find there additional details on the expected behavior for client and server nodes, when their respective SSL options are enabled. +For information how to configure the slaves to use TLS, check [securing the communication](../../streaming#securing-streaming-communications) in the streaming documentation. There you will find additional details on the expected behavior for client and server nodes, when their respective TLS options are enabled. + +When we define the use of SSL in a Netdata agent for different ports, Netdata will apply the behavior specified on each port. For example, using the configuration line below: + +``` +[web] + bind to = *=dashboard|registry|badges|management|streaming|netdata.conf^SSL=force *:20000=netdata.conf^SSL=optional *:20001=dashboard|registry +``` + +Netdata will: + +- Force all HTTP requests to the default port to be redirected to HTTPS (same port). +- Refuse unencrypted streaming connections from slaves on the default port. +- Allow both HTTP and HTTPS requests to port 20000 for netdata.conf +- Force HTTP requests to port 20001 to be redirected to HTTPS (same port). Only allow requests for the dashboard, the read API and the registry on port 20001. + +#### TLS/SSL errors + +When you start using Netdata with TLS, you may find errors in the Netdata log, which is stored at `/var/log/netdata/error.log` by default. -#### SSL error +Most of the time, these errors are due to incompatibilities between your browser's options related to TLS/SSL protocols and Netdata's internal configuration. The most common error is `error:00000006:lib(0):func(0):EVP lib`. -It is possible that when you start to use the Netdata with SSL some erros will be register in the logs, this happens due possible incompatibilities between the browser options related to SSL like Ciphers and TLS/SSL version and the Netdata internal configuration. The most common error would be `error:00000006:lib(0):func(0):EVP lib`. In a near future the Netdata will allow our users to change the internal configuration to avoid errors like this, but until there we are setting the most common and safety options to the communication. +In the near future, Netdata will allow our users to change the internal configuration to avoid similar errors. Until then, we're recommending only the most common and safe encryption protocols, which you can find above. ### Access lists diff --git a/web/server/web_client.c b/web/server/web_client.c index bd275f5e5..2da6c1dec 100644 --- a/web/server/web_client.c +++ b/web/server/web_client.c @@ -16,8 +16,8 @@ inline int web_client_permission_denied(struct web_client *w) { w->response.data->contenttype = CT_TEXT_PLAIN; buffer_flush(w->response.data); buffer_strcat(w->response.data, "You are not allowed to access this resource."); - w->response.code = 403; - return 403; + w->response.code = HTTP_RESP_FORBIDDEN; + return HTTP_RESP_FORBIDDEN; } static inline int web_client_crock_socket(struct web_client *w) { @@ -337,7 +337,7 @@ static inline int access_to_file_is_not_permitted(struct web_client *w, const ch w->response.data->contenttype = CT_TEXT_HTML; buffer_strcat(w->response.data, "Access to file is not permitted: "); buffer_strcat_htmlescape(w->response.data, filename); - return 403; + return HTTP_RESP_FORBIDDEN; } int mysendfile(struct web_client *w, char *filename) { @@ -357,7 +357,7 @@ int mysendfile(struct web_client *w, char *filename) { w->response.data->contenttype = CT_TEXT_HTML; buffer_sprintf(w->response.data, "Filename contains invalid characters: "); buffer_strcat_htmlescape(w->response.data, filename); - return 400; + return HTTP_RESP_BAD_REQUEST; } } @@ -367,7 +367,7 @@ int mysendfile(struct web_client *w, char *filename) { w->response.data->contenttype = CT_TEXT_HTML; buffer_strcat(w->response.data, "Relative filenames are not supported: "); buffer_strcat_htmlescape(w->response.data, filename); - return 400; + return HTTP_RESP_BAD_REQUEST; } // find the physical file on disk @@ -383,7 +383,7 @@ int mysendfile(struct web_client *w, char *filename) { w->response.data->contenttype = CT_TEXT_HTML; buffer_strcat(w->response.data, "File does not exist, or is not accessible: "); buffer_strcat_htmlescape(w->response.data, webfilename); - return 404; + return HTTP_RESP_NOT_FOUND; } if ((statbuf.st_mode & S_IFMT) == S_IFDIR) { @@ -422,14 +422,14 @@ int mysendfile(struct web_client *w, char *filename) { buffer_sprintf(w->response.header, "Location: /%s\r\n", filename); buffer_strcat(w->response.data, "File is currently busy, please try again later: "); buffer_strcat_htmlescape(w->response.data, webfilename); - return 307; + return HTTP_RESP_REDIR_TEMP; } else { error("%llu: Cannot open file '%s'.", w->id, webfilename); w->response.data->contenttype = CT_TEXT_HTML; buffer_strcat(w->response.data, "Cannot open file: "); buffer_strcat_htmlescape(w->response.data, webfilename); - return 404; + return HTTP_RESP_NOT_FOUND; } } @@ -451,7 +451,7 @@ int mysendfile(struct web_client *w, char *filename) { #endif /* __APPLE__ */ buffer_cacheable(w->response.data); - return 200; + return HTTP_RESP_OK; } @@ -570,7 +570,7 @@ static inline int check_host_and_call(RRDHOST *host, struct web_client *w, char //if(unlikely(host->rrd_memory_mode == RRD_MEMORY_MODE_NONE)) { // buffer_flush(w->response.data); // buffer_strcat(w->response.data, "This host does not maintain a database"); - // return 400; + // return HTTP_RESP_BAD_REQUEST; //} return func(host, w, url); @@ -603,13 +603,13 @@ int web_client_api_request(RRDHOST *host, struct web_client *w, char *url) w->response.data->contenttype = CT_TEXT_HTML; buffer_strcat(w->response.data, "Unsupported API version: "); buffer_strcat_htmlescape(w->response.data, tok); - return 404; + return HTTP_RESP_NOT_FOUND; } } else { buffer_flush(w->response.data); buffer_sprintf(w->response.data, "Which API version?"); - return 400; + return HTTP_RESP_BAD_REQUEST; } } @@ -687,25 +687,25 @@ const char *web_content_type_to_string(uint8_t contenttype) { const char *web_response_code_to_string(int code) { switch(code) { - case 200: + case HTTP_RESP_OK: return "OK"; - case 301: + case HTTP_RESP_MOVED_PERM: return "Moved Permanently"; - case 307: + case HTTP_RESP_REDIR_TEMP: return "Temporary Redirect"; - case 400: + case HTTP_RESP_BAD_REQUEST: return "Bad Request"; - case 403: + case HTTP_RESP_FORBIDDEN: return "Forbidden"; - case 404: + case HTTP_RESP_NOT_FOUND: return "Not Found"; - case 412: + case HTTP_RESP_PRECOND_FAIL: return "Preconditions Failed"; default: @@ -772,7 +772,6 @@ static inline char *http_header_parse(struct web_client *w, char *s, int parse_u // terminate the value *ve = '\0'; - // fprintf(stderr, "HEADER: '%s' = '%s'\n", s, v); uint32_t hash = simple_uhash(s); if(hash == hash_origin && !strcasecmp(s, "Origin")) @@ -812,65 +811,35 @@ static inline char *http_header_parse(struct web_client *w, char *s, int parse_u return ve; } -// http_request_validate() -// returns: -// = 0 : all good, process the request -// > 0 : request is not supported -// < 0 : request is incomplete - wait for more data - -typedef enum { - HTTP_VALIDATION_OK, - HTTP_VALIDATION_NOT_SUPPORTED, -#ifdef ENABLE_HTTPS - HTTP_VALIDATION_INCOMPLETE, - HTTP_VALIDATION_REDIRECT -#else - HTTP_VALIDATION_INCOMPLETE -#endif -} HTTP_VALIDATION; - -static inline HTTP_VALIDATION http_request_validate(struct web_client *w) { - char *s = (char *)buffer_tostring(w->response.data), *encoded_url = NULL; - - size_t last_pos = w->header_parse_last_size; - if(last_pos > 4) last_pos -= 4; // allow searching for \r\n\r\n - else last_pos = 0; - - w->header_parse_tries++; - w->header_parse_last_size = buffer_strlen(w->response.data); - - if(w->header_parse_tries > 1) { - if(w->header_parse_last_size < last_pos) - last_pos = 0; - - if(strstr(&s[last_pos], "\r\n\r\n") == NULL) { - if(w->header_parse_tries > 10) { - info("Disabling slow client after %zu attempts to read the request (%zu bytes received)", w->header_parse_tries, buffer_strlen(w->response.data)); - w->header_parse_tries = 0; - w->header_parse_last_size = 0; - web_client_disable_wait_receive(w); - return HTTP_VALIDATION_NOT_SUPPORTED; - } - - return HTTP_VALIDATION_INCOMPLETE; - } - } - +/** + * Valid Method + * + * Netdata accepts only three methods, including one of these three(STREAM) is an internal method. + * + * @param w is the structure with the client request + * @param s is the start string to parse + * + * @return it returns the next address to parse case the method is valid and NULL otherwise. + */ +static inline char *web_client_valid_method(struct web_client *w, char *s) { // is is a valid request? if(!strncmp(s, "GET ", 4)) { - encoded_url = s = &s[4]; + s = &s[4]; w->mode = WEB_CLIENT_MODE_NORMAL; } else if(!strncmp(s, "OPTIONS ", 8)) { - encoded_url = s = &s[8]; + s = &s[8]; w->mode = WEB_CLIENT_MODE_OPTIONS; } else if(!strncmp(s, "STREAM ", 7)) { + s = &s[7]; + #ifdef ENABLE_HTTPS - if ( (w->ssl.flags) && (netdata_use_ssl_on_stream & NETDATA_SSL_FORCE)){ + if (w->ssl.flags && web_client_is_using_ssl_force(w)){ w->header_parse_tries = 0; w->header_parse_last_size = 0; web_client_disable_wait_receive(w); + char hostname[256]; char *copyme = strstr(s,"hostname="); if ( copyme ){ @@ -891,29 +860,150 @@ static inline HTTP_VALIDATION http_request_validate(struct web_client *w) { hostname[13] = 0x00; } error("The server is configured to always use encrypt connection, please enable the SSL on slave with hostname '%s'.",hostname); - return HTTP_VALIDATION_NOT_SUPPORTED; + s = NULL; } #endif - encoded_url = s = &s[7]; w->mode = WEB_CLIENT_MODE_STREAM; } else { + s = NULL; + } + + return s; +} + +/** + * Set Path Query + * + * Set the pointers to the path and query string according to the input. + * + * @param w is the structure with the client request + * @param s is the first address of the string. + * @param ptr is the address of the separator. + */ +static void web_client_set_path_query(struct web_client *w, char *s, char *ptr) { + w->url_path_length = (size_t)(ptr -s); + + w->url_search_path = ptr; +} + +/** + * Split path query + * + * Do the separation between path and query string + * + * @param w is the structure with the client request + * @param s is the string to parse + */ +void web_client_split_path_query(struct web_client *w, char *s) { + //I am assuming here that the separator character(?) is not encoded + char *ptr = strchr(s, '?'); + if(ptr) { + w->separator = '?'; + web_client_set_path_query(w, s, ptr); + return; + } + + //Here I test the second possibility, the URL is completely encoded by the user. + //I am not using the strcasestr, because it is fastest to check %3f and compare + //the next character. + //We executed some tests with "encodeURI(uri);" described in https://www.w3schools.com/jsref/jsref_encodeuri.asp + //on July 1st, 2019, that show us that URLs won't have '?','=' and '&' encoded, but we decided to move in front + //with the next part, because users can develop their own encoded that won't follow this rule. + char *moveme = s; + while (moveme) { + ptr = strchr(moveme, '%'); + if(ptr) { + char *test = (ptr+1); + if (!strncmp(test, "3f", 2) || !strncmp(test, "3F", 2)) { + w->separator = *ptr; + web_client_set_path_query(w, s, ptr); + return; + } + ptr++; + } + + moveme = ptr; + } + + w->separator = 0x00; + w->url_path_length = strlen(s); + w->url_search_path = NULL; +} + +/** + * Request validate + * + * @param w is the structure with the client request + * + * @return It returns HTTP_VALIDATION_OK on success and another code present + * in the enum HTTP_VALIDATION otherwise. + */ +static inline HTTP_VALIDATION http_request_validate(struct web_client *w) { + char *s = (char *)buffer_tostring(w->response.data), *encoded_url = NULL; + + size_t last_pos = w->header_parse_last_size; + + w->header_parse_tries++; + w->header_parse_last_size = buffer_strlen(w->response.data); + + int is_it_valid; + if(w->header_parse_tries > 1) { + if(last_pos > 4) last_pos -= 4; // allow searching for \r\n\r\n + else last_pos = 0; + + if(w->header_parse_last_size < last_pos) + last_pos = 0; + + is_it_valid = url_is_request_complete(s, &s[last_pos], w->header_parse_last_size); + if(!is_it_valid) { + if(w->header_parse_tries > 10) { + info("Disabling slow client after %zu attempts to read the request (%zu bytes received)", w->header_parse_tries, buffer_strlen(w->response.data)); + w->header_parse_tries = 0; + w->header_parse_last_size = 0; + web_client_disable_wait_receive(w); + return HTTP_VALIDATION_NOT_SUPPORTED; + } + + return HTTP_VALIDATION_INCOMPLETE; + } + + is_it_valid = 1; + } else { + last_pos = w->header_parse_last_size; + is_it_valid = url_is_request_complete(s, &s[last_pos], w->header_parse_last_size); + } + + s = web_client_valid_method(w, s); + if (!s) { w->header_parse_tries = 0; w->header_parse_last_size = 0; web_client_disable_wait_receive(w); + return HTTP_VALIDATION_NOT_SUPPORTED; + } else if (!is_it_valid) { + //Invalid request, we have more data after the end of message + char *check = strstr((char *)buffer_tostring(w->response.data), "\r\n\r\n"); + if(check) { + check += 4; + if (*check) { + w->header_parse_tries = 0; + w->header_parse_last_size = 0; + web_client_disable_wait_receive(w); + return HTTP_VALIDATION_NOT_SUPPORTED; + } + } + + web_client_enable_wait_receive(w); + return HTTP_VALIDATION_INCOMPLETE; } - // find the SPACE + "HTTP/" - while(*s) { - // find the next space - while (*s && *s != ' ') s++; + //After the method we have the path and query string together + encoded_url = s; - // is it SPACE + "HTTP/" ? - if(*s && !strncmp(s, " HTTP/", 6)) break; - else s++; - } + //we search for the position where we have " HTTP/", because it finishes the user request + s = url_find_protocol(s); // incomplete requests if(unlikely(!*s)) { @@ -924,6 +1014,10 @@ static inline HTTP_VALIDATION http_request_validate(struct web_client *w) { // we have the end of encoded_url - remember it char *ue = s; + //Variables used to map the variables in the query string case it is present + int total_variables; + char *ptr_variables[WEB_FIELDS_MAX]; + // make sure we have complete request // complete requests contain: \r\n\r\n while(*s) { @@ -941,15 +1035,41 @@ static inline HTTP_VALIDATION http_request_validate(struct web_client *w) { // a valid complete HTTP request found *ue = '\0'; - url_decode_r(w->decoded_url, encoded_url, NETDATA_WEB_REQUEST_URL_SIZE + 1); + if(w->mode != WEB_CLIENT_MODE_NORMAL) { + if(!url_decode_r(w->decoded_url, encoded_url, NETDATA_WEB_REQUEST_URL_SIZE + 1)) + return HTTP_VALIDATION_MALFORMED_URL; + } else { + web_client_split_path_query(w, encoded_url); + + if (w->separator) { + *w->url_search_path = 0x00; + } + + if(!url_decode_r(w->decoded_url, encoded_url, NETDATA_WEB_REQUEST_URL_SIZE + 1)) + return HTTP_VALIDATION_MALFORMED_URL; + + if (w->separator) { + *w->url_search_path = w->separator; + + char *from = (encoded_url + w->url_path_length); + total_variables = url_map_query_string(ptr_variables, from); + + if (url_parse_query_string(w->decoded_query_string, NETDATA_WEB_REQUEST_URL_SIZE + 1, ptr_variables, total_variables)) { + return HTTP_VALIDATION_MALFORMED_URL; + } + } + } *ue = ' '; - + // copy the URL - we are going to overwrite parts of it // TODO -- ideally we we should avoid copying buffers around strncpyz(w->last_url, w->decoded_url, NETDATA_WEB_REQUEST_URL_SIZE); + if (w->separator) { + *w->url_search_path = 0x00; + } #ifdef ENABLE_HTTPS if ( (!web_client_check_unix(w)) && (netdata_srv_ctx) ) { - if ((w->ssl.conn) && ((w->ssl.flags & NETDATA_SSL_NO_HANDSHAKE) && (netdata_use_ssl_on_http & NETDATA_SSL_FORCE) && (w->mode != WEB_CLIENT_MODE_STREAM)) ) { + if ((w->ssl.conn) && ((w->ssl.flags & NETDATA_SSL_NO_HANDSHAKE) && (web_client_is_using_ssl_force(w) || web_client_is_using_ssl_default(w)) && (w->mode != WEB_CLIENT_MODE_STREAM)) ) { w->header_parse_tries = 0; w->header_parse_last_size = 0; web_client_disable_wait_receive(w); @@ -997,7 +1117,7 @@ static inline ssize_t web_client_send_data(struct web_client *w,const void *buf, } static inline void web_client_send_http_header(struct web_client *w) { - if(unlikely(w->response.code != 200)) + if(unlikely(w->response.code != HTTP_RESP_OK)) buffer_no_cacheable(w->response.data); // set a proper expiration date, if not already set @@ -1027,7 +1147,7 @@ static inline void web_client_send_http_header(struct web_client *w) { } char headerbegin[8328]; - if (w->response.code == 301) { + if (w->response.code == HTTP_RESP_MOVED_PERM) { memcpy(headerbegin,"\r\nLocation: https://",20); size_t headerlength = strlen(w->host); memcpy(&headerbegin[20],w->host,headerlength); @@ -1210,7 +1330,7 @@ static inline int web_client_switch_host(RRDHOST *host, struct web_client *w, ch if(host != localhost) { buffer_flush(w->response.data); buffer_strcat(w->response.data, "Nesting of hosts is not allowed."); - return 400; + return HTTP_RESP_BAD_REQUEST; } char *tok = mystrsep(&url, "/"); @@ -1234,7 +1354,7 @@ static inline int web_client_switch_host(RRDHOST *host, struct web_client *w, ch w->response.data->contenttype = CT_TEXT_HTML; buffer_strcat(w->response.data, "This netdata does not maintain a database for host: "); buffer_strcat_htmlescape(w->response.data, tok?tok:""); - return 404; + return HTTP_RESP_NOT_FOUND; } static inline int web_client_process_url(RRDHOST *host, struct web_client *w, char *url) { @@ -1279,7 +1399,7 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch w->response.data->contenttype = CT_TEXT_PLAIN; buffer_flush(w->response.data); config_generate(w->response.data, 0); - return 200; + return HTTP_RESP_OK; } #ifdef NETDATA_INTERNAL_CHECKS else if(unlikely(hash == hash_exit && strcmp(tok, "exit") == 0)) { @@ -1296,7 +1416,7 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch error("web request to exit received."); netdata_cleanup_and_exit(0); - return 200; + return HTTP_RESP_OK; } else if(unlikely(hash == hash_debug && strcmp(tok, "debug") == 0)) { if(unlikely(!web_client_can_access_netdataconf(w))) @@ -1317,7 +1437,7 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch buffer_strcat(w->response.data, "Chart is not found: "); buffer_strcat_htmlescape(w->response.data, tok); debug(D_WEB_CLIENT_ACCESS, "%llu: %s is not found.", w->id, tok); - return 404; + return HTTP_RESP_NOT_FOUND; } debug_flags |= D_RRD_STATS; @@ -1331,12 +1451,12 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch buffer_sprintf(w->response.data, "Chart has now debug %s: ", rrdset_flag_check(st, RRDSET_FLAG_DEBUG)?"enabled":"disabled"); buffer_strcat_htmlescape(w->response.data, tok); debug(D_WEB_CLIENT_ACCESS, "%llu: debug for %s is %s.", w->id, tok, rrdset_flag_check(st, RRDSET_FLAG_DEBUG)?"enabled":"disabled"); - return 200; + return HTTP_RESP_OK; } buffer_flush(w->response.data); buffer_strcat(w->response.data, "debug which chart?\r\n"); - return 400; + return HTTP_RESP_BAD_REQUEST; } else if(unlikely(hash == hash_mirror && strcmp(tok, "mirror") == 0)) { if(unlikely(!web_client_can_access_netdataconf(w))) @@ -1350,7 +1470,7 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch // just leave the buffer as is // it will be copied back to the client - return 200; + return HTTP_RESP_OK; } #endif /* NETDATA_INTERNAL_CHECKS */ } @@ -1395,7 +1515,7 @@ void web_client_process_request(struct web_client *w) { w->response.data->contenttype = CT_TEXT_PLAIN; buffer_flush(w->response.data); buffer_strcat(w->response.data, "OK"); - w->response.code = 200; + w->response.code = HTTP_RESP_OK; break; case WEB_CLIENT_MODE_FILECOPY: @@ -1424,7 +1544,7 @@ void web_client_process_request(struct web_client *w) { buffer_flush(w->response.data); buffer_sprintf(w->response.data, "Received request is too big (%zu bytes).\r\n", w->response.data->len); - w->response.code = 400; + w->response.code = HTTP_RESP_BAD_REQUEST; } else { // wait for more data @@ -1437,16 +1557,23 @@ void web_client_process_request(struct web_client *w) { buffer_flush(w->response.data); w->response.data->contenttype = CT_TEXT_HTML; buffer_strcat(w->response.data, "<!DOCTYPE html><!-- SPDX-License-Identifier: GPL-3.0-or-later --><html><body onload=\"window.location.href ='https://'+ window.location.hostname + ':' + window.location.port + window.location.pathname\">Redirecting to safety connection, case your browser does not support redirection, please click <a onclick=\"window.location.href ='https://'+ window.location.hostname + ':' + window.location.port + window.location.pathname\">here</a>.</body></html>"); - w->response.code = 301; + w->response.code = HTTP_RESP_MOVED_PERM; break; } #endif + case HTTP_VALIDATION_MALFORMED_URL: + debug(D_WEB_CLIENT_ACCESS, "%llu: URL parsing failed (malformed URL). Cannot understand '%s'.", w->id, w->response.data->buffer); + + buffer_flush(w->response.data); + buffer_strcat(w->response.data, "URL not valid. I don't understand you...\r\n"); + w->response.code = HTTP_RESP_BAD_REQUEST; + break; case HTTP_VALIDATION_NOT_SUPPORTED: debug(D_WEB_CLIENT_ACCESS, "%llu: Cannot understand '%s'.", w->id, w->response.data->buffer); buffer_flush(w->response.data); buffer_strcat(w->response.data, "I don't understand you...\r\n"); - w->response.code = 400; + w->response.code = HTTP_RESP_BAD_REQUEST; break; } diff --git a/web/server/web_client.h b/web/server/web_client.h index 0a57e8d8e..7cab46fc2 100644 --- a/web/server/web_client.h +++ b/web/server/web_client.h @@ -11,6 +11,21 @@ extern int web_enable_gzip, web_gzip_strategy; #endif /* NETDATA_WITH_ZLIB */ +// HTTP_CODES 2XX Success +#define HTTP_RESP_OK 200 + +// HTTP_CODES 3XX Redirections +#define HTTP_RESP_MOVED_PERM 301 +#define HTTP_RESP_REDIR_TEMP 307 +#define HTTP_RESP_REDIR_PERM 308 + +// HTTP_CODES 4XX Client Errors +#define HTTP_RESP_BAD_REQUEST 400 +#define HTTP_RESP_FORBIDDEN 403 +#define HTTP_RESP_NOT_FOUND 404 +#define HTTP_RESP_PRECOND_FAIL 412 + + extern int respect_web_browser_do_not_track_policy; extern char *web_x_frame_options; @@ -21,6 +36,18 @@ typedef enum web_client_mode { WEB_CLIENT_MODE_STREAM = 3 } WEB_CLIENT_MODE; +typedef enum { + HTTP_VALIDATION_OK, + HTTP_VALIDATION_NOT_SUPPORTED, + HTTP_VALIDATION_MALFORMED_URL, +#ifdef ENABLE_HTTPS + HTTP_VALIDATION_INCOMPLETE, + HTTP_VALIDATION_REDIRECT +#else + HTTP_VALIDATION_INCOMPLETE +#endif +} HTTP_VALIDATION; + typedef enum web_client_flags { WEB_CLIENT_FLAG_DEAD = 1 << 1, // if set, this client is dead @@ -128,8 +155,12 @@ struct web_client { char client_port[NI_MAXSERV+1]; char decoded_url[NETDATA_WEB_REQUEST_URL_SIZE + 1]; // we decode the URL in this buffer + char decoded_query_string[NETDATA_WEB_REQUEST_URL_SIZE + 1]; // we decode the Query String in this buffer char last_url[NETDATA_WEB_REQUEST_URL_SIZE+1]; // we keep a copy of the decoded URL here char host[256]; + size_t url_path_length; + char separator; // This value can be either '?' or 'f' + char *url_search_path; //A pointer to the search path sent by the client struct timeval tv_in, tv_ready; @@ -159,6 +190,7 @@ struct web_client { #endif }; + extern uid_t web_files_uid(void); extern uid_t web_files_gid(void); |